Skip to content
Snippets Groups Projects
Commit 37c35677 authored by mechanik20051988's avatar mechanik20051988 Committed by Kirill Yukhin
Browse files

uri: implement ability to parse URIs passed in different ways

Previously, URI can be passed as a string, which contains one URI
or several URIs separated by commas. Now URIs can be passed in
different ways: as before, as a table which contains URI and it's
parameters in "param" table, as a table which contains URI strings
and URI tables. Also there are different ways to specify properties
for URI: in a string which contains URI, after '?' delimiter, in a
table which contains URI in "params" table, in "default_params" table
if it is default parameters for all URIs.
For this purposes new method `parse_many` was implemented in tarantool
`uri` library. Also `parse` method was updated to make possible the
same as new `parse_many` method but only for single URI.
```lua
uri = require('uri')
-- Single URI, passed as before
uri.parse_many("/tmp/unix.sock")
-- Single URI, with query paramters
uri.parse_many("/tmp/unix.sock?q1=v1&q2=v2")
-- Several URIs with parameters in one string, separated by commas
uri.parse_many("/tmp/unix.sock_1?q=v, /tmp/unix.sock_2?q=v")
-- Single URI passed in table, with additional parameters, passed
-- in "params" table. This parameters overwrite parameters from
-- URI string (q1 = "v2" in example below).
uri.parse_many({"/tmp/unix.sock?q1=v1", params = {q1 = "v2"}})
-- For parse it's also works now
uri.parse({"/tmp/unix.sock?q1=v1", params = {q1 = "v2"}})
-- Several URIs passed in table with default parameters, passed
-- in "default_params" table, which are used for parameters, which
-- not specified for URI (q3 parameter with "v3" value corresponds
-- to all URIs, and used if there is no such parameter in URI).
uri.parse_many({
    "/tmp/unix.sock_1?q1=v1",
    { uri = "/tmp/unix.sock_2", params = { q2 = "v2" } },
    default_params = { q3 = "v3" }
})
```
parent 3661bc3a
No related branches found
No related tags found
No related merge requests found
......@@ -463,10 +463,8 @@ tt_uuid_from_string
tt_uuid_is_equal
tt_uuid_is_nil
tt_uuid_to_string
uri_create
uri_destroy
uri_format
uri_set_create
uri_set_destroy
uuid_nil
uuid_unpack
......
......@@ -148,6 +148,7 @@ set (server_sources
lua/string.c
lua/swim.c
lua/decimal.c
lua/uri.c
${lua_sources}
${PROJECT_SOURCE_DIR}/third_party/lua-yaml/lyaml.cc
${PROJECT_SOURCE_DIR}/third_party/lua-yaml/b64.c
......
......@@ -66,11 +66,20 @@ uri_param_create(struct uri_param *param, const char *name)
param->value_count = 0;
}
/**
* Appends @a value to @a uri parameter with given @a name,
* creating one if it doesn't exist.
*/
static void
void
uri_remove_param(struct uri *uri, const char *name)
{
struct uri_param *param = uri_find_param(uri, name);
if (param == NULL)
return;
int idx = param - uri->params;
uri_param_destroy(param);
for (int i = idx; i < uri->param_count - 1; i++)
uri->params[i] = uri->params[i + 1];
uri->param_count--;
}
void
uri_add_param(struct uri *uri, const char *name, const char *value)
{
struct uri_param *param = uri_find_param(uri, name);
......@@ -170,6 +179,38 @@ uri_create(struct uri *uri, const char *str)
return 0;
}
static int
uri_format_param(char *str, int len, const struct uri_param *param)
{
int total = 0;
if (param->value_count == 0) {
SNPRINT(total, snprintf, str, len, "%s", param->name);
return total;
}
for (int i = 0; i < param->value_count; i++) {
SNPRINT(total, snprintf, str, len, "%s=%s",
param->name, param->values[i]);
if (i != param->value_count - 1)
SNPRINT(total, snprintf, str, len, "&");
}
return total;
}
static int
uri_format_params(char *str, int len, const struct uri *uri)
{
if (uri->param_count == 0)
return 0;
int total = 0;
SNPRINT(total, snprintf, str, len, "?");
for (int i = 0; i < uri->param_count; i++) {
SNPRINT(total, uri_format_param, str, len, &uri->params[i]);
if (i != uri->param_count - 1)
SNPRINT(total, snprintf, str, len, "&");
}
return total;
}
int
uri_format(char *str, int len, const struct uri *uri, bool write_password)
{
......@@ -193,8 +234,8 @@ uri_format(char *str, int len, const struct uri *uri, bool write_password)
if (uri->path != NULL) {
SNPRINT(total, snprintf, str, len, "%s", uri->path);
}
if (uri->query != NULL) {
SNPRINT(total, snprintf, str, len, "?%s", uri->query);
if (uri->params != NULL) {
SNPRINT(total, uri_format_params, str, len, uri);
}
if (uri->fragment != NULL) {
SNPRINT(total, snprintf, str, len, "#%s", uri->fragment);
......
......@@ -42,6 +42,18 @@ struct uri_set {
#define URI_MAXHOST NI_MAXHOST
#define URI_MAXSERVICE _POSIX_PATH_MAX /* _POSIX_PATH_MAX always > NI_MAXSERV */
/**
* Appends @a value to @a uri parameter with given @a name,
* creating one if it doesn't exist.
*/
void
uri_add_param(struct uri *uri, const char *name, const char *value);
/**
* Remove @a uri parameter and all its values.
*/
void
uri_remove_param(struct uri *uri, const char *name);
/**
* Creates new @a uri structure according to passed @a str.
......
......@@ -62,6 +62,7 @@
#include "lua/utf8.h"
#include "lua/swim.h"
#include "lua/decimal.h"
#include "lua/uri.h"
#include "digest.h"
#include "errinj.h"
#include <small/ibuf.h>
......@@ -669,6 +670,7 @@ tarantool_lua_init(const char *tarantool_bin, int argc, char **argv)
lua_call(L, 0, 0);
lua_register(L, "tonumber64", lbox_tonumber64);
tarantool_lua_uri_init(L);
tarantool_lua_utf8_init(L);
tarantool_lua_utils_init(L);
tarantool_lua_fiber_init(L);
......
/*
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright 2010-2021, Tarantool AUTHORS, please see AUTHORS file.
*/
#include "lua/uri.h"
#include "lua/utils.h"
#include "uri/uri.h"
#include "diag.h"
/**
* Add or overwrite (depends on @a overwrite) URI parameter to @a uri.
* Parameter value is located at the top of the lua stack, parameter name is
* in the position next to it. Allowed types for URI parameter values
* are LUA_TSTRING, LUA_TNUMBER and LUA_TTABLE. URI parameter name should
* be a string.
*/
static int
uri_add_param_from_lua(struct uri *uri, struct lua_State *L, bool overwrite)
{
if (lua_type(L, -2) != LUA_TSTRING) {
diag_set(IllegalParams, "Incorrect type for URI "
"parameter name: should be a string");
return -1;
}
const char *name = lua_tostring(L, -2);
if (overwrite) {
uri_remove_param(uri, name);
} else if (uri_param_count(uri, name) != 0) {
return 0;
}
int rc = 0;
switch (lua_type(L, -1)) {
case LUA_TSTRING:
case LUA_TNUMBER:
uri_add_param(uri, name, lua_tostring(L, -1));
break;
case LUA_TTABLE:
for (unsigned i = 0; i < lua_objlen(L, -1) && rc == 0; i++) {
lua_rawgeti(L, -1, i + 1);
const char *value = lua_tostring(L, -1);
if (value != NULL) {
uri_add_param(uri, name, value);
} else {
diag_set(IllegalParams, "Incorrect type for "
"URI parameter value: should "
"be string or number");
rc = -1;
}
lua_pop(L, 1);
}
break;
default:
diag_set(IllegalParams, "Incorrect type for URI "
"parameter value: should be string, number or table");
rc = -1;
}
return rc;
}
/**
* Add or overwrite (depends on @a overwrite) URI parameters in @a uri.
* Table with parameters or nil value should be located at the top of the lua
* stack.
*/
static int
uri_add_params_from_lua(struct uri *uri, struct lua_State *L, bool overwrite)
{
if (lua_type(L, -1) == LUA_TNIL) {
return 0;
} else if (lua_type(L, -1) != LUA_TTABLE) {
diag_set(IllegalParams, "Incorrect type for URI "
"parameters: should be a table");
return -1;
}
int rc = 0;
lua_pushnil(L);
while (lua_next(L, -2) != 0 && rc == 0) {
rc = uri_add_param_from_lua(uri, L, overwrite);
assert(rc == 0 || !diag_is_empty(diag_get()));
lua_pop(L, 1);
}
return rc;
}
/**
* Returns the type of the field, which located at the given valid
* @a index in the table which located at the given valid @a table_idx.
*/
static int
field_type(struct lua_State *L, int table_idx, int index)
{
assert(lua_type(L, table_idx) == LUA_TTABLE);
lua_rawgeti(L, table_idx, index);
int rc = lua_type(L, -1);
lua_pop(L, 1);
return rc;
}
/**
* Check if there is a field with the name @a name in the table,
* which located at the given valid @a idx, which should be a
* positive value.
*/
static bool
is_field_present(struct lua_State *L, int idx, const char *name)
{
assert(idx > 0);
assert(lua_type(L, idx) == LUA_TTABLE);
lua_pushstring(L, name);
lua_rawget(L, idx);
bool field_is_present = (lua_type(L, -1) != LUA_TNIL);
lua_pop(L, 1);
return field_is_present;
}
/**
* Create @a uri from the table, which located at the given valid @a idx,
* which should be a positive value.
*/
static int
uri_create_from_lua_table(struct lua_State *L, int idx, struct uri *uri)
{
assert(idx > 0);
assert(lua_type(L, idx) == LUA_TTABLE);
/* There should be exactly one URI in the table */
int size = lua_objlen(L, idx);
int uri_count = size + is_field_present(L, idx, "uri");
if (uri_count != 1) {
diag_set(IllegalParams, "Invalid URI table: "
"expected {uri = string, params = table} "
"or {string, params = table}");
return -1;
}
/* Table "default_params" is not allowed for single URI */
if (is_field_present(L, idx, "default_params")) {
diag_set(IllegalParams, "Default URI parameters are "
"not allowed for single URI");
return -1;
}
int rc = 0;
if (size == 1) {
lua_rawgeti(L, idx, 1);
} else {
lua_pushstring(L, "uri");
lua_rawget(L, idx);
}
const char *uristr = lua_tostring(L, -1);
if (uristr != NULL) {
rc = uri_create(uri, uristr);
if (rc != 0) {
diag_set(IllegalParams, "Incorrect URI: expected "
"host:service or /unix.socket");
}
} else {
diag_set(IllegalParams, "Incorrect type for URI in nested "
"table: should be string, number");
rc = -1;
}
lua_pop(L, 1);
if (rc != 0)
return rc;
lua_pushstring(L, "params");
lua_rawget(L, idx);
rc = uri_add_params_from_lua(uri, L, true);
lua_pop(L, 1);
if (rc != 0)
uri_destroy(uri);
return rc;
}
/**
* Create @a uri from the value at the given valid @a idx.
*/
static int
luaT_uri_create(struct lua_State *L, int idx, struct uri *uri)
{
int rc = 0;
if (idx < 0)
idx = lua_gettop(L) + idx + 1;
assert(idx > 0);
if (lua_isstring(L, idx)) {
rc = uri_create(uri, lua_tostring(L, idx));
if (rc != 0) {
diag_set(IllegalParams, "Incorrect URI: "
"expected host:service or "
"/unix.socket");
}
} else if (lua_istable(L, idx)) {
rc = uri_create_from_lua_table(L, idx, uri);
} else if (lua_isnil(L, idx)) {
uri_create(uri, NULL);
} else {
diag_set(IllegalParams, "Incorrect type for URI: "
"should be string, number or table");
rc = -1;
}
assert(rc == 0 || !diag_is_empty(diag_get()));
return rc;
}
/**
* Create @a uri_set from the table, which located at the given valid @a idx,
* which should be a positive value.
*/
static int
uri_set_create_from_lua_table(struct lua_State *L, int idx,
struct uri_set *uri_set)
{
int rc = 0;
assert(idx > 0);
assert(lua_type(L, idx) == LUA_TTABLE);
int size = lua_objlen(L, idx);
struct uri uri;
uri_set_create(uri_set, NULL);
if (is_field_present(L, idx, "uri") ||
(size == 1 && field_type(L, idx, 1) != LUA_TTABLE)) {
rc = luaT_uri_create(L, idx, &uri);
if (rc == 0) {
uri_set_add(uri_set, &uri);
uri_destroy(&uri);
}
return rc;
} else if (size == 0) {
return 0;
}
/*
* All numeric keys corresponds to URIs in string or table
* format.
*/
for (int i = 0; i < size && rc == 0; i++) {
lua_rawgeti(L, idx, i + 1);
rc = luaT_uri_create(L, -1, &uri);
if (rc == 0) {
uri_set_add(uri_set, &uri);
uri_destroy(&uri);
}
lua_pop(L, 1);
}
if (rc != 0)
goto fail;
/*
* Here we are only in case when it is an URI array, so it
* shouldn't be "params" field here.
*/
if (is_field_present(L, idx, "params")) {
diag_set(IllegalParams, "URI parameters are "
"not allowed for multiple URIs");
goto fail;
}
lua_pushstring(L, "default_params");
lua_rawget(L, idx);
if (!lua_isnil(L, -1)) {
for (int i = 0; i < uri_set->uri_count && rc == 0; i++) {
struct uri *uri = &uri_set->uris[i];
rc = uri_add_params_from_lua(uri, L, false);
assert(rc == 0 || !diag_is_empty(diag_get()));
}
}
lua_pop(L, 1);
if (rc != 0)
goto fail;
return 0;
fail:
uri_set_destroy(uri_set);
return -1;
}
/**
* Create @a uri_set from the value at the given valid @a idx.
*/
static int
luaT_uri_set_create(struct lua_State *L, int idx, struct uri_set *uri_set)
{
int rc = 0;
if (idx < 0)
idx = lua_gettop(L) + idx + 1;
assert(idx > 0);
if (lua_isstring(L, idx)) {
rc = uri_set_create(uri_set, lua_tostring(L, idx));
if (rc != 0) {
diag_set(IllegalParams, "Incorrect URI: "
"expected host:service or "
"/unix.socket");
}
} else if (lua_istable(L, idx)) {
rc = uri_set_create_from_lua_table(L, idx, uri_set);
} else if (lua_isnil(L, idx)) {
uri_set_create(uri_set, NULL);
} else {
diag_set(IllegalParams, "Incorrect type for URI: "
"should be string, number or table");
rc = -1;
}
assert(rc == 0 || !diag_is_empty(diag_get()));
return rc;
}
static int
luaT_uri_create_internal(lua_State *L)
{
struct uri *uri = (struct uri *)lua_topointer(L, 1);
if (uri == NULL)
luaL_error(L, "Usage: uri.internal.uri_create(string|table)");
if (luaT_uri_create(L, 2, uri) != 0)
luaT_error(L);
return 0;
}
static int
luaT_uri_set_create_internal(lua_State *L)
{
struct uri_set *uri_set = (struct uri_set *)lua_topointer(L, 1);
if (uri_set == NULL)
luaL_error(L, "Usage: uri.internal.uri_set_create(string|table)");
if (luaT_uri_set_create(L, 2, uri_set) != 0)
luaT_error(L);
return 0;
}
void
tarantool_lua_uri_init(struct lua_State *L)
{
static const struct luaL_Reg uri_methods[] = {
{NULL, NULL}
};
luaL_register_module(L, "uri", uri_methods);
/* internal table */
lua_pushliteral(L, "internal");
lua_newtable(L);
static const struct luaL_Reg uri_internal_methods[] = {
{"uri_create", luaT_uri_create_internal},
{"uri_set_create", luaT_uri_set_create_internal},
{NULL, NULL}
};
luaL_register(L, NULL, uri_internal_methods);
lua_settable(L, -3);
lua_pop(L, 1);
};
/*
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright 2010-2021, Tarantool AUTHORS, please see AUTHORS file.
*/
#pragma once
#if defined(__cplusplus)
extern "C" {
#endif /* defined(__cplusplus) */
struct lua_State;
/**
* Initialize box.uri system
*/
void
tarantool_lua_uri_init(struct lua_State *L);
#if defined(__cplusplus)
} /* extern "C" */
#endif /* defined(__cplusplus) */
......@@ -2,6 +2,7 @@
local ffi = require('ffi')
local buffer = require('buffer')
local uri = require('uri')
ffi.cdef[[
struct uri_param {
......@@ -36,15 +37,9 @@ struct uri_set {
struct uri *uris;
};
int
uri_create(struct uri *uri, const char *str);
void
uri_destroy(struct uri *uri);
int
uri_set_create(struct uri_set *uri_set, const char *str);
void
uri_set_destroy(struct uri_set *uri_set);
......@@ -93,12 +88,13 @@ end
local function parse(str)
if str == nil then
error("Usage: uri.parse(string)")
error("Usage: uri.parse(string|table)")
end
local uribuf = uri_stash_take()
if builtin.uri_create(uribuf, str) ~= 0 then
local status, errmsg = pcall(uri.internal.uri_create, uribuf, str)
if not status then
uri_stash_put(uribuf)
return nil
return nil, errmsg
end
local result = parse_uribuf(uribuf)
builtin.uri_destroy(uribuf)
......@@ -108,12 +104,13 @@ end
local function parse_many(str)
if str == nil then
error("Usage: uri.parse_many(string)")
error("Usage: uri.parse_many(string|table)")
end
local uri_set_buf = uri_set_stash_take()
if builtin.uri_set_create(uri_set_buf, str) ~= 0 then
local status, errmsg = pcall(uri.internal.uri_set_create, uri_set_buf, str)
if not status then
uri_set_stash_put(uri_set_buf)
return nil
return nil, errmsg
end
local result = {}
for i = 0, uri_set_buf.uri_count - 1 do
......@@ -124,6 +121,25 @@ local function parse_many(str)
return result
end
local function fill_uribuf_params(uribuf, uri)
uribuf.param_count = 0
for _, _ in pairs(uri.params) do
uribuf.param_count = uribuf.param_count + 1
end
uribuf.params = ffi.new("struct uri_param[?]", uribuf.param_count)
local i = 0
for param_name, param in pairs(uri.params) do
uribuf.params[i].value_count = #param
uribuf.params[i].name = param_name
uribuf.params[i].values =
ffi.new("const char *[?]", uribuf.params[i].value_count)
for j = 1, uribuf.params[i].value_count do
uribuf.params[i].values[j - 1] = param[j]
end
i = i + 1
end
end
local function format(uri, write_password)
local uribuf = uri_stash_take()
uribuf.scheme = uri.scheme
......@@ -134,6 +150,7 @@ local function format(uri, write_password)
uribuf.path = uri.path
uribuf.query = uri.query
uribuf.fragment = uri.fragment
fill_uribuf_params(uribuf, uri)
local ibuf = cord_ibuf_take()
local str = ibuf:alloc(1024)
local len = builtin.uri_format(str, 1024, uribuf, write_password and 1 or 0)
......
......@@ -63,10 +63,8 @@ local check_symbols = {
'tt_uuid_to_string',
'log_level',
'log_format',
'uri_create',
'uri_destroy',
'uri_format',
'uri_set_create',
'uri_set_destroy',
'PMurHash32',
'PMurHash32_Process',
......
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment