[Tarantool-patches] [PATCH 2/2] httpc: introduce format_query and parse_query functions
olegrok at tarantool.org
olegrok at tarantool.org
Thu Jan 9 11:04:14 MSK 2020
From: Oleg Babin <babinoleg at 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
More information about the Tarantool-patches
mailing list