From: olegrok@tarantool.org To: tarantool-patches@dev.tarantool.org, alexander.turenko@tarantool.org Cc: Oleg Babin <babinoleg@mail.ru> Subject: [Tarantool-patches] [PATCH 2/2] httpc: introduce format_query and parse_query functions Date: Thu, 9 Jan 2020 11:04:14 +0300 [thread overview] Message-ID: <86c6e75e1a4e4c612306832fa1846266546074a7.1577881257.git.babinoleg@mail.ru> (raw) In-Reply-To: <cover.1577881257.git.babinoleg@mail.ru> From: Oleg Babin <babinoleg@mail.ru> Previous patch introduced url_escape and url_unescape. However it's quite poorly to give our users too low-level functions. The main use case is to form and parse http query parameters This patch introduces format_query and parse_query functions Closes #3682 @TarantoolBot document Title: New http.client functions Four new functions are available in http.client module: - url_escape - simple wrapper over curl_easy_escape (https://curl.haxx.se/libcurl/c/curl_easy_escape.html) - url_unescape - simple wrapper over curl_easy_unescape (https://curl.haxx.se/libcurl/c/curl_easy_unescape.html) Examples: ```lua -- According RFC3986 tarantool> httpc.url_escape('hello') --- - hello ... tarantool> httpc.url_escape('Привет') --- - '%D0%9F%D1%80%D0%B8%D0%B2%D0%B5%D1%82' ... tarantool> httpc.url_escape('$&+,:;=?@-._~') --- - '%24%26%2B%2C%3A%3B%3D%3F%40-._~' ... ``` - format_query - format query arguments from key-value string pairs for HTTP request - parse_query - parse query string into table Example: ```lua -- The query string is composed of a series of field-value pairs -- Within each pair, the field name and value are -- separated by an equals sign, "=" -- The series of pairs is separated by the ampersand, "&" tarantool> httpc.format_query({['hello'] = 'world', ['привет'] = 'мир'}) --- - '%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82=%D0%BC%D0%B8%D1%80&hello=world' ... tarantool> httpc.parse_query('%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82=' .. '%D0%BC%D0%B8%D1%80&hello=world') --- - привет: мир hello: world ... ``` --- Issue: https://github.com/tarantool/tarantool/issues/3682 src/lua/httpc.lua | 60 ++++++++++++++++++++++++++++++- test/app-tap/http_client.test.lua | 39 +++++++++++++++++++- 2 files changed, 97 insertions(+), 2 deletions(-) diff --git a/src/lua/httpc.lua b/src/lua/httpc.lua index 33cb78f3c..7d40b3bcc 100644 --- a/src/lua/httpc.lua +++ b/src/lua/httpc.lua @@ -449,10 +449,68 @@ local function url_unescape(str) return driver.unescape(http_default.curl, str) end +-- +-- This function format query params from table with arguments +-- +-- Parameters: +-- +-- args - table with key-value pairs that will be +-- encoded as string "key1=value1&key2=value2" +-- each element of pair is encoded with url_escape +-- +local function format_query(args) + if type(args) ~= "table" then + error("httpc.format_query expected table with args") + end + local encoded = {} + for k, v in pairs(args) do + k = tostring(k) + v = tostring(v) + k = url_escape(k) + v = url_escape(v) + table.insert(encoded, k .. '=' .. v) + end + + return table.concat(encoded, '&') +end + +-- +-- This function parse query params from string to table +-- +-- Parameters: +-- +-- str - string in format "key1=value1&key2=value2" +-- that will be parsed into {key1 = value1, key2 = value2} +-- each element of pair is decoded with url_unescape +-- +local function parse_query(str) + if type(str) ~= "string" then + error("httpc.parse_query expected string with args") + end + local args = string.split(str, '&') + + local decoded = {} + for _, arg in ipairs(args) do + local k_v = string.split(arg, '=', 1) + if #k_v ~= 2 then + error("httpc.parse_query expected arguments in format 'key=value'" + .. " separated by '&', got: " .. tostring(arg)) + end + local key = url_unescape(k_v[1]) + local value = url_unescape(k_v[2]) + + decoded[key] = value + end + + return decoded +end + local this_module = { new = http_new, url_escape = url_escape, - url_unescape = url_unescape + url_unescape = url_unescape, + format_query = format_query, + parse_query = parse_query, } local function http_default_wrap(fname) diff --git a/test/app-tap/http_client.test.lua b/test/app-tap/http_client.test.lua index 3676f56b4..4b5e6bbc5 100755 --- a/test/app-tap/http_client.test.lua +++ b/test/app-tap/http_client.test.lua @@ -611,7 +611,7 @@ function run_tests(test, sock_family, sock_addr) stop_server(test, server) end -test:plan(3) +test:plan(5) test:test("http over AF_INET", function(test) local s = socketlib('AF_INET', 'SOCK_STREAM', 0) @@ -653,4 +653,41 @@ test:test("url_escape/url_unescape", function(test) test:is('-._~', client.url_escape('-._~'), '-._~ are not escaped according RFC 3986') end) +test:test("format_query", function(test) + test:plan(6) + test:is(client.format_query({['hello'] = 'world'}), 'hello=world', 'correct formation for one eng arg') + test:is(client.format_query({['привет'] = 'мир'}), + '%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82=%D0%BC%D0%B8%D1%80', 'correct formation for one rus arg') + test:is(client.format_query({['key=key'] = 'value;value'}), 'key%3Dkey=value%3Bvalue', + 'correct formation for args with special characters') + test:is(client.format_query({['key1'] = 'value1', ['ключ2'] = 'значение'}), + 'key1=value1&%D0%BA%D0%BB%D1%8E%D1%872=%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5', + 'correct formation for several args') + + local ok, err = pcall(client.format_query, 'str') + test:is(ok, false, 'only table could be passed as argument') + test:ok(err:endswith('httpc.format_query expected table with args'), 'correct error message') +end) + +test:test("parse_query", function(test) + test:plan(8) + test:is_deeply(client.parse_query('hello=world'), {['hello'] = 'world'}, 'correct parsing for one eng arg') + test:is_deeply(client.parse_query('%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82=%D0%BC%D0%B8%D1%80'), + {['привет'] = 'мир'}, 'correct parsing for one rus arg') + test:is_deeply(client.parse_query('key%3Dkey=value%3Bvalue'), {['key=key'] = 'value;value'}, + 'correct formation for args with special characters') + test:is_deeply(client.parse_query( + 'key1=value1&%D0%BA%D0%BB%D1%8E%D1%872=%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5'), + {['key1'] = 'value1', ['ключ2'] = 'значение'}, 'correct parsing for several args') + + local ok, err = pcall(client.parse_query, {}) + test:is(ok, false, 'only string could be passed as argument') + test:ok(err:endswith('httpc.parse_query expected string with args'), 'correct error message') + + local ok, err = pcall(client.parse_query, '1=2&3&4') + test:is(ok, false, 'unexpected input format error') + test:ok(err:endswith('httpc.parse_query expected arguments in format \'key=value\'' .. + ' separated by \'&\', got: 3'), 'correct error message') +end) + os.exit(test:check() == true and 0 or -1) -- 2.23.0
prev parent reply other threads:[~2020-01-09 8:04 UTC|newest] Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top 2020-01-09 8:04 [Tarantool-patches] [PATCH 0/2] Borrow encoding/decoding functions from cURL olegrok 2020-01-09 8:04 ` [Tarantool-patches] [PATCH 1/2] httpc: introduce url_escape/url_unescape functions olegrok 2020-01-09 8:04 ` olegrok [this message]
Reply instructions: You may reply publicly to this message via plain-text email using any one of the following methods: * Save the following mbox file, import it into your mail client, and reply-to-all from there: mbox Avoid top-posting and favor interleaved quoting: https://en.wikipedia.org/wiki/Posting_style#Interleaved_style * Reply using the --to, --cc, and --in-reply-to switches of git-send-email(1): git send-email \ --in-reply-to=86c6e75e1a4e4c612306832fa1846266546074a7.1577881257.git.babinoleg@mail.ru \ --to=olegrok@tarantool.org \ --cc=alexander.turenko@tarantool.org \ --cc=babinoleg@mail.ru \ --cc=tarantool-patches@dev.tarantool.org \ --subject='Re: [Tarantool-patches] [PATCH 2/2] httpc: introduce format_query and parse_query functions' \ /path/to/YOUR_REPLY https://kernel.org/pub/software/scm/git/docs/git-send-email.html * If your mail client supports setting the In-Reply-To header via mailto: links, try the mailto: link
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox