From 47683c4704572bbe0efb3b989b35a3912b65ac83 Mon Sep 17 00:00:00 2001 From: Andrew Clayton Date: Mon, 15 May 2023 22:48:31 +0100 Subject: Python: Fix ASGI applications accessed over IPv6. There are a couple of reports on GitHub about issues accessing Python ASGI based applications over IPv6. A request over IPv6 would result in an error like 2023/05/13 17:49:12 [alert] 47202#47202 [unit] #10: Python failed to create 'client' pair 2023/05/13 17:49:12 [alert] 47202#47202 [unit] Python failed to call 'loop.call_soon' ValueError: invalid literal for int() with base 10: 'db8:1:1:1ee7:dead:beef:cafe' The above error was the direct cause of the following exception: Traceback (most recent call last): File "/usr/lib64/python3.11/asyncio/base_events.py", line 765, in call_soon handle = self._call_soon(callback, args, context) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib64/python3.11/asyncio/base_events.py", line 781, in _call_soon handle = events.Handle(callback, args, self, context) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SystemError: returned a result with an exception set This issue occurred in the nxt_py_asgi_create_ip_address() function where it tries to create an IP address / port number pair. It does this by looking for the first ':' in the address and taking everything after it as the port number. Like in the above error message, if we tried to access the server @ 2001:db8:1:1:1ee7:dead:beef:cafe, then we'd end up with the port number as 'db8:1:1:1ee7:dead:beef:cafe'. There are two issues with this 1) The IP address and port number are already flowed through separately. 2) Even if (1) wasn't true, it would still be broken for IPv6 as we'd expect to a get an address literal like [2001:db8:1:1:1ee7:dead:beef:cafe]:8080, however there was no code to handle the []'s. The fix is to simply not try looking for a port number. We pass a port number into this function to use in the case where we don't find a port number, we never will... A further cleanup would be to flow through the server port number when creating the 'server pair' PyTuple, rather than just using the hard coded 80. Closes: Closes: Reviewed-by: Alejandro Colomar Signed-off-by: Andrew Clayton --- src/python/nxt_python_asgi.c | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/python/nxt_python_asgi.c b/src/python/nxt_python_asgi.c index adf03e2b..9f6cde3b 100644 --- a/src/python/nxt_python_asgi.c +++ b/src/python/nxt_python_asgi.c @@ -828,7 +828,7 @@ nxt_py_asgi_create_address(nxt_unit_sptr_t *sptr, uint8_t len, uint16_t port) static PyObject * nxt_py_asgi_create_ip_address(nxt_unit_sptr_t *sptr, uint8_t len, uint16_t port) { - char *p, *s; + char *p; PyObject *pair, *v; pair = PyTuple_New(2); @@ -837,9 +837,8 @@ nxt_py_asgi_create_ip_address(nxt_unit_sptr_t *sptr, uint8_t len, uint16_t port) } p = nxt_unit_sptr_get(sptr); - s = memchr(p, ':', len); - v = PyString_FromStringAndSize(p, s == NULL ? len : s - p); + v = PyString_FromStringAndSize(p, len); if (nxt_slow_path(v == NULL)) { Py_DECREF(pair); @@ -848,14 +847,7 @@ nxt_py_asgi_create_ip_address(nxt_unit_sptr_t *sptr, uint8_t len, uint16_t port) PyTuple_SET_ITEM(pair, 0, v); - if (s != NULL) { - p += len; - v = PyLong_FromString(s + 1, &p, 10); - - } else { - v = PyLong_FromLong(port); - } - + v = PyLong_FromLong(port); if (nxt_slow_path(v == NULL)) { Py_DECREF(pair); -- cgit From f32858dcb76a35513c035729d1c44ce01520e84c Mon Sep 17 00:00:00 2001 From: Alejandro Colomar Date: Wed, 17 May 2023 10:00:47 +0200 Subject: Added back deprecated options to unitd. We renamed the options recently, with the intention of keeping the old names as supported but deprecated for some time, before removal. This was done with the configure script options, but in the unitd binary, we accidentally removed the old names, causing some unintended breakage. Keep support for the old names, albeit with a deprecation message to stderr, for some time, until we decide to remove them. Fixes: 5a37171f733f ("Added default values for pathnames.") Closes: Reported-by: El RIDO Acked-by: Liam Crilly Acked-by: Artem Konev Acked-by: Timo Stark Reviewed-by: Andrew Clayton Cc: Andrei Zeliankou Signed-off-by: Alejandro Colomar --- src/nxt_runtime.c | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) (limited to 'src') diff --git a/src/nxt_runtime.c b/src/nxt_runtime.c index 96f801fb..9bfabc75 100644 --- a/src/nxt_runtime.c +++ b/src/nxt_runtime.c @@ -966,6 +966,13 @@ nxt_runtime_conf_read_cmd(nxt_task_t *task, nxt_runtime_t *rt) "option \"--statedir\" requires directory\n"; static const char no_tmp[] = "option \"--tmpdir\" requires directory\n"; + static const char modules_deprecated[] = + "option \"--modules\" is deprecated; use \"--modulesdir\" instead\n"; + static const char state_deprecated[] = + "option \"--state\" is deprecated; use \"--statedir\" instead\n"; + static const char tmp_deprecated[] = + "option \"--tmp\" is deprecated; use \"--tmpdir\" instead\n"; + static const char help[] = "\n" "unit options:\n" @@ -992,6 +999,10 @@ nxt_runtime_conf_read_cmd(nxt_task_t *task, nxt_runtime_t *rt) " --tmpdir DIR set tmp directory name\n" " default: \"" NXT_TMPDIR "\"\n" "\n" + " --modules DIR [deprecated] synonym for --modulesdir\n" + " --state DIR [deprecated] synonym for --statedir\n" + " --tmp DIR [deprecated] synonym for --tmpdir\n" + "\n" " --user USER set non-privileged processes to run" " as specified user\n" " default: \"" NXT_USER "\"\n" @@ -1073,7 +1084,14 @@ nxt_runtime_conf_read_cmd(nxt_task_t *task, nxt_runtime_t *rt) continue; } + if (nxt_strcmp(p, "--modules") == 0) { + write(STDERR_FILENO, modules_deprecated, + nxt_length(modules_deprecated)); + goto modulesdir; + } + if (nxt_strcmp(p, "--modulesdir") == 0) { +modulesdir: if (*argv == NULL) { write(STDERR_FILENO, no_modules, nxt_length(no_modules)); return NXT_ERROR; @@ -1086,7 +1104,14 @@ nxt_runtime_conf_read_cmd(nxt_task_t *task, nxt_runtime_t *rt) continue; } + if (nxt_strcmp(p, "--state") == 0) { + write(STDERR_FILENO, state_deprecated, + nxt_length(state_deprecated)); + goto statedir; + } + if (nxt_strcmp(p, "--statedir") == 0) { +statedir: if (*argv == NULL) { write(STDERR_FILENO, no_state, nxt_length(no_state)); return NXT_ERROR; @@ -1099,7 +1124,13 @@ nxt_runtime_conf_read_cmd(nxt_task_t *task, nxt_runtime_t *rt) continue; } + if (nxt_strcmp(p, "--tmp") == 0) { + write(STDERR_FILENO, tmp_deprecated, nxt_length(tmp_deprecated)); + goto tmpdir; + } + if (nxt_strcmp(p, "--tmpdir") == 0) { +tmpdir: if (*argv == NULL) { write(STDERR_FILENO, no_tmp, nxt_length(no_tmp)); return NXT_ERROR; -- cgit From 47cdfb6f30f7d56bffb806cc860e20806ea62f50 Mon Sep 17 00:00:00 2001 From: Alejandro Colomar Date: Mon, 22 May 2023 02:37:01 +0200 Subject: Tests: fixed incorrect pointer assignment. If we don't update the pointer before copying the request body, then we get the behavior shown below. After this patch, "foo\n" is rightly appended at the end of the response body. Request: "GET / HTTP/1.1\r\nHost: _\nContent-Length: 4\n\nfoo\n" Response body: """ Hello world! foo est data: Method: GET Protocol: HTTP/1.1 Remote addr: 127.0.0.1 Local addr: 127.0.0.1 Target: / Path: / Fields: Host: _ Content-Length: 4 Body: """ Fixes: 1bb22d1e922c ("Unit application library.") Reviewed-by: Andrew Clayton Signed-off-by: Alejandro Colomar --- src/test/nxt_unit_app_test.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/test/nxt_unit_app_test.c b/src/test/nxt_unit_app_test.c index d83bd83a..5dcebe18 100644 --- a/src/test/nxt_unit_app_test.c +++ b/src/test/nxt_unit_app_test.c @@ -257,8 +257,8 @@ greeting_app_request_handler(nxt_unit_request_info_t *req) if (r->content_length > 0) { p = copy(p, BODY, nxt_length(BODY)); - res = nxt_unit_request_read(req, buf->free, buf->end - buf->free); - buf->free += res; + res = nxt_unit_request_read(req, p, buf->end - p); + p += res; } -- cgit From 93ed66958e11b40cc06dcb32fc8e623967af6347 Mon Sep 17 00:00:00 2001 From: synodriver Date: Sat, 27 May 2023 22:18:46 +0800 Subject: Python: Add ASGI lifespan state support. Lifespan state is a special dict in asgi lifespan scope, which allow applications to persist data from the lifespan cycle to request/response handling. The scope["state"] namespace provides a place to store these sorts of things. The server will ensure that a shallow copy of the namespace is passed into each subsequent request/response call into the application. Some frameworks are already taking advantage of this feature, for example, starlette, and without this feature they wouldn't work properly. Signed-off-by: synodriver Reviewed-by: Andrew Clayton [ Minor code tweaks to avoid lines > 80 chars, static a function and re-work the PyMemberDef structure initialisation for Python <3.7 and -Wwrite-strings compatibility - Andrew ] Tested-by: Tested-by: Closes: Signed-off-by: Andrew Clayton --- src/python/nxt_python_asgi.c | 30 +++++++++++++++++-- src/python/nxt_python_asgi_lifespan.c | 54 ++++++++++++++++++++++++++++++++++- src/python/nxt_python_asgi_str.c | 2 ++ src/python/nxt_python_asgi_str.h | 1 + 4 files changed, 84 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/python/nxt_python_asgi.c b/src/python/nxt_python_asgi.c index 9f6cde3b..5882491b 100644 --- a/src/python/nxt_python_asgi.c +++ b/src/python/nxt_python_asgi.c @@ -450,6 +450,7 @@ static void nxt_py_asgi_request_handler(nxt_unit_request_info_t *req) { PyObject *scope, *res, *task, *receive, *send, *done, *asgi; + PyObject *state, *newstate, *lifespan; PyObject *stage2; nxt_python_target_t *target; nxt_py_asgi_ctx_data_t *ctx_data; @@ -493,15 +494,41 @@ nxt_py_asgi_request_handler(nxt_unit_request_info_t *req) } req->data = asgi; + ctx_data = req->ctx->data; target = &nxt_py_targets->target[req->request->app_target]; + lifespan = ctx_data->target_lifespans[req->request->app_target]; + state = PyObject_GetAttr(lifespan, nxt_py_state_str); + if (nxt_slow_path(state == NULL)) { + nxt_unit_req_alert(req, "Python failed to get 'state' attribute"); + nxt_unit_request_done(req, NXT_UNIT_ERROR); + + goto release_done; + } + + newstate = PyDict_Copy(state); + if (nxt_slow_path(newstate == NULL)) { + nxt_unit_req_alert(req, "Python failed to call state.copy()"); + nxt_unit_request_done(req, NXT_UNIT_ERROR); + Py_DECREF(state); + goto release_done; + } + Py_DECREF(state); scope = nxt_py_asgi_create_http_scope(req, target); if (nxt_slow_path(scope == NULL)) { nxt_unit_request_done(req, NXT_UNIT_ERROR); - + Py_DECREF(newstate); goto release_done; } + if (nxt_slow_path(PyDict_SetItem(scope, nxt_py_state_str, newstate) + == -1)) + { + Py_DECREF(newstate); + goto release_scope; + } + Py_DECREF(newstate); + if (!target->asgi_legacy) { nxt_unit_req_debug(req, "Python call ASGI 3.0 application"); @@ -555,7 +582,6 @@ nxt_py_asgi_request_handler(nxt_unit_request_info_t *req) goto release_scope; } - ctx_data = req->ctx->data; task = PyObject_CallFunctionObjArgs(ctx_data->loop_create_task, res, NULL); if (nxt_slow_path(task == NULL)) { diff --git a/src/python/nxt_python_asgi_lifespan.c b/src/python/nxt_python_asgi_lifespan.c index 1fc0e6b7..041cca21 100644 --- a/src/python/nxt_python_asgi_lifespan.c +++ b/src/python/nxt_python_asgi_lifespan.c @@ -12,6 +12,8 @@ #include #include +#include + typedef struct { PyObject_HEAD @@ -25,6 +27,7 @@ typedef struct { PyObject *startup_future; PyObject *shutdown_future; PyObject *receive_future; + PyObject *state; } nxt_py_asgi_lifespan_t; static PyObject *nxt_py_asgi_lifespan_target_startup( @@ -41,6 +44,7 @@ static PyObject *nxt_py_asgi_lifespan_send_shutdown( nxt_py_asgi_lifespan_t *lifespan, int v, PyObject *dict); static PyObject *nxt_py_asgi_lifespan_disable(nxt_py_asgi_lifespan_t *lifespan); static PyObject *nxt_py_asgi_lifespan_done(PyObject *self, PyObject *future); +static void nxt_py_asgi_lifespan_dealloc(PyObject *self); static PyMethodDef nxt_py_asgi_lifespan_methods[] = { @@ -50,6 +54,26 @@ static PyMethodDef nxt_py_asgi_lifespan_methods[] = { { NULL, NULL, 0, 0 } }; +static PyMemberDef nxt_py_asgi_lifespan_members[] = { + { +#if PY_VERSION_HEX >= NXT_PYTHON_VER(3, 7) + .name = "state", +#else + .name = (char *)"state", +#endif + .type = T_OBJECT_EX, + .offset = offsetof(nxt_py_asgi_lifespan_t, state), + .flags = READONLY, +#if PY_VERSION_HEX >= NXT_PYTHON_VER(3, 7) + .doc = PyDoc_STR("lifespan.state") +#else + .doc = (char *)PyDoc_STR("lifespan.state") +#endif + }, + + { NULL, 0, 0, 0, NULL } +}; + static PyAsyncMethods nxt_py_asgi_async_methods = { .am_await = nxt_py_asgi_await, }; @@ -59,13 +83,14 @@ static PyTypeObject nxt_py_asgi_lifespan_type = { .tp_name = "unit._asgi_lifespan", .tp_basicsize = sizeof(nxt_py_asgi_lifespan_t), - .tp_dealloc = nxt_py_asgi_dealloc, + .tp_dealloc = nxt_py_asgi_lifespan_dealloc, .tp_as_async = &nxt_py_asgi_async_methods, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = "unit ASGI Lifespan object", .tp_iter = nxt_py_asgi_iter, .tp_iternext = nxt_py_asgi_next, .tp_methods = nxt_py_asgi_lifespan_methods, + .tp_members = nxt_py_asgi_lifespan_members, }; @@ -163,12 +188,29 @@ nxt_py_asgi_lifespan_target_startup(nxt_py_asgi_ctx_data_t *ctx_data, lifespan->shutdown_called = 0; lifespan->shutdown_future = NULL; lifespan->receive_future = NULL; + lifespan->state = NULL; scope = nxt_py_asgi_new_scope(NULL, nxt_py_lifespan_str, nxt_py_2_0_str); if (nxt_slow_path(scope == NULL)) { goto release_future; } + lifespan->state = PyDict_New(); + if (nxt_slow_path(lifespan->state == NULL)) { + nxt_unit_req_error(NULL, + "Python failed to create 'state' dict"); + goto release_future; + } + + if (nxt_slow_path(PyDict_SetItem(scope, nxt_py_state_str, + lifespan->state) == -1)) + { + nxt_unit_req_error(NULL, + "Python failed to set 'scope.state' item"); + Py_CLEAR(lifespan->state); + goto release_future; + } + if (!target->asgi_legacy) { nxt_unit_req_debug(NULL, "Python call ASGI 3.0 application"); @@ -604,4 +646,14 @@ nxt_py_asgi_lifespan_done(PyObject *self, PyObject *future) } +static void +nxt_py_asgi_lifespan_dealloc(PyObject *self) +{ + nxt_py_asgi_lifespan_t *lifespan = (nxt_py_asgi_lifespan_t *)self; + + Py_CLEAR(lifespan->state); + PyObject_Del(self); +} + + #endif /* NXT_HAVE_ASGI */ diff --git a/src/python/nxt_python_asgi_str.c b/src/python/nxt_python_asgi_str.c index 7171d52b..3bea87d5 100644 --- a/src/python/nxt_python_asgi_str.c +++ b/src/python/nxt_python_asgi_str.c @@ -55,6 +55,7 @@ PyObject *nxt_py_subprotocol_str; PyObject *nxt_py_subprotocols_str; PyObject *nxt_py_text_str; PyObject *nxt_py_type_str; +PyObject *nxt_py_state_str; PyObject *nxt_py_version_str; PyObject *nxt_py_websocket_str; PyObject *nxt_py_websocket_accept_str; @@ -110,6 +111,7 @@ static nxt_python_string_t nxt_py_asgi_strings[] = { { nxt_string("subprotocols"), &nxt_py_subprotocols_str }, { nxt_string("text"), &nxt_py_text_str }, { nxt_string("type"), &nxt_py_type_str }, + { nxt_string("state"), &nxt_py_state_str }, { nxt_string("version"), &nxt_py_version_str }, { nxt_string("websocket"), &nxt_py_websocket_str }, { nxt_string("websocket.accept"), &nxt_py_websocket_accept_str }, diff --git a/src/python/nxt_python_asgi_str.h b/src/python/nxt_python_asgi_str.h index 92969fd2..3c7a3ed9 100644 --- a/src/python/nxt_python_asgi_str.h +++ b/src/python/nxt_python_asgi_str.h @@ -50,6 +50,7 @@ extern PyObject *nxt_py_subprotocol_str; extern PyObject *nxt_py_subprotocols_str; extern PyObject *nxt_py_text_str; extern PyObject *nxt_py_type_str; +extern PyObject *nxt_py_state_str; extern PyObject *nxt_py_version_str; extern PyObject *nxt_py_websocket_str; extern PyObject *nxt_py_websocket_accept_str; -- cgit From b84f6ecad42f4217b6eafb0ceb1e66a75b34e948 Mon Sep 17 00:00:00 2001 From: synodriver Date: Sat, 27 May 2023 23:18:41 +0800 Subject: Python: Fix error checks in nxt_py_asgi_request_handler(). Signed-off-by: synodriver Reviewed-by: Andrew Clayton [ Re-word commit subject - Andrew ] Fixes: c4c2f90c5b53 ("Python: ASGI server introduced.") Closes: Signed-off-by: Andrew Clayton --- src/python/nxt_python_asgi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/python/nxt_python_asgi.c b/src/python/nxt_python_asgi.c index 5882491b..8f300b53 100644 --- a/src/python/nxt_python_asgi.c +++ b/src/python/nxt_python_asgi.c @@ -478,7 +478,7 @@ nxt_py_asgi_request_handler(nxt_unit_request_info_t *req) } send = PyObject_GetAttrString(asgi, "send"); - if (nxt_slow_path(receive == NULL)) { + if (nxt_slow_path(send == NULL)) { nxt_unit_req_alert(req, "Python failed to get 'send' method"); nxt_unit_request_done(req, NXT_UNIT_ERROR); @@ -486,7 +486,7 @@ nxt_py_asgi_request_handler(nxt_unit_request_info_t *req) } done = PyObject_GetAttrString(asgi, "_done"); - if (nxt_slow_path(receive == NULL)) { + if (nxt_slow_path(done == NULL)) { nxt_unit_req_alert(req, "Python failed to get '_done' method"); nxt_unit_request_done(req, NXT_UNIT_ERROR); -- cgit From a378f6aa3136ed7ef172b71add31ee62e560df00 Mon Sep 17 00:00:00 2001 From: Zhidao HONG Date: Thu, 25 May 2023 00:27:55 +0800 Subject: HTTP: fixed variable caching. When a variable is accessed in the Unit configuration, the value is cached. This was useful prior to the URI rewrite feature, but now that the URI (more precisely, the request target) can be rewritten, the contents of the variable $uri (which contains the path part of the request target, and is decoded) should not be cached anymore, or at least the cached value should be invalidated after a URI rewrite. Example: { "rewrite": "/prefix$uri", "share": "$uri" } For a request line like GET /foo?bar=baz HTTP/1.1\r\n, the expected file served in the response would be /prefix/foo, but due to the caching issue, Unit currently serves /foo. --- src/nxt_http_variables.c | 16 ++++++++++++++++ src/nxt_var.c | 38 ++++++++++++++++++++++++-------------- src/nxt_var.h | 1 + 3 files changed, 41 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/nxt_http_variables.c b/src/nxt_http_variables.c index b73d9151..71f7ff7f 100644 --- a/src/nxt_http_variables.c +++ b/src/nxt_http_variables.c @@ -47,54 +47,70 @@ static nxt_var_decl_t nxt_http_vars[] = { { .name = nxt_string("dollar"), .handler = nxt_http_var_dollar, + .cacheable = 1, }, { .name = nxt_string("request_time"), .handler = nxt_http_var_request_time, + .cacheable = 1, }, { .name = nxt_string("method"), .handler = nxt_http_var_method, + .cacheable = 1, }, { .name = nxt_string("request_uri"), .handler = nxt_http_var_request_uri, + .cacheable = 1, }, { .name = nxt_string("uri"), .handler = nxt_http_var_uri, + .cacheable = 0, }, { .name = nxt_string("host"), .handler = nxt_http_var_host, + .cacheable = 1, }, { .name = nxt_string("remote_addr"), .handler = nxt_http_var_remote_addr, + .cacheable = 1, }, { .name = nxt_string("time_local"), .handler = nxt_http_var_time_local, + .cacheable = 1, }, { .name = nxt_string("request_line"), .handler = nxt_http_var_request_line, + .cacheable = 1, }, { .name = nxt_string("status"), .handler = nxt_http_var_status, + .cacheable = 1, }, { .name = nxt_string("body_bytes_sent"), .handler = nxt_http_var_body_bytes_sent, + .cacheable = 1, }, { .name = nxt_string("header_referer"), .handler = nxt_http_var_referer, + .cacheable = 1, }, { .name = nxt_string("header_user_agent"), .handler = nxt_http_var_user_agent, + .cacheable = 1, }, { .name = nxt_string("arg"), .handler = nxt_http_var_arg, .field_hash = nxt_http_argument_hash, + .cacheable = 1, }, { .name = nxt_string("header"), .handler = nxt_http_var_header, .field_hash = nxt_http_header_hash, + .cacheable = 1, }, { .name = nxt_string("cookie"), .handler = nxt_http_var_cookie, .field_hash = nxt_http_cookie_hash, + .cacheable = 1, }, }; diff --git a/src/nxt_var.c b/src/nxt_var.c index e113969f..e4299a09 100644 --- a/src/nxt_var.c +++ b/src/nxt_var.c @@ -80,7 +80,7 @@ static const nxt_lvlhsh_proto_t nxt_var_cache_proto nxt_aligned(64) = { static nxt_lvlhsh_t nxt_var_hash; static uint32_t nxt_var_count; -static nxt_var_handler_t *nxt_var_index; +static nxt_var_decl_t **nxt_vars; static nxt_int_t @@ -235,8 +235,11 @@ nxt_var_cache_value(nxt_task_t *task, nxt_var_cache_t *cache, uint32_t index, { nxt_int_t ret; nxt_str_t *value; + nxt_var_decl_t *var; nxt_lvlhsh_query_t lhq; + var = nxt_vars[index >> 16]; + value = cache->spare; if (value == NULL) { @@ -248,6 +251,10 @@ nxt_var_cache_value(nxt_task_t *task, nxt_var_cache_t *cache, uint32_t index, cache->spare = value; } + if (!var->cacheable) { + goto not_cached; + } + lhq.key_hash = nxt_murmur_hash2_uint32(&index); lhq.replace = 0; lhq.key.length = sizeof(uint32_t); @@ -261,16 +268,20 @@ nxt_var_cache_value(nxt_task_t *task, nxt_var_cache_t *cache, uint32_t index, return NULL; } - if (ret == NXT_OK) { - ret = nxt_var_index[index >> 16](task, value, ctx, index & 0xffff); - if (nxt_slow_path(ret != NXT_OK)) { - return NULL; - } + if (ret == NXT_DECLINED) { + return lhq.value; + } + +not_cached: - cache->spare = NULL; + ret = var->handler(task, value, ctx, index & 0xffff); + if (nxt_slow_path(ret != NXT_OK)) { + return NULL; } - return lhq.value; + cache->spare = NULL; + + return value; } @@ -303,12 +314,11 @@ nxt_int_t nxt_var_index_init(void) { nxt_uint_t i; - nxt_var_decl_t *decl; - nxt_var_handler_t *index; + nxt_var_decl_t *decl, **vars; nxt_lvlhsh_each_t lhe; - index = nxt_memalign(64, nxt_var_count * sizeof(nxt_var_handler_t)); - if (index == NULL) { + vars = nxt_memalign(64, nxt_var_count * sizeof(nxt_var_decl_t *)); + if (vars == NULL) { return NXT_ERROR; } @@ -317,10 +327,10 @@ nxt_var_index_init(void) for (i = 0; i < nxt_var_count; i++) { decl = nxt_lvlhsh_each(&nxt_var_hash, &lhe); decl->index = i; - index[i] = decl->handler; + vars[i] = decl; } - nxt_var_index = index; + nxt_vars = vars; return NXT_OK; } diff --git a/src/nxt_var.h b/src/nxt_var.h index ab25800d..d3da26eb 100644 --- a/src/nxt_var.h +++ b/src/nxt_var.h @@ -22,6 +22,7 @@ typedef struct { nxt_var_handler_t handler; nxt_var_field_hash_t field_hash; uint32_t index; + uint8_t cacheable; /* 1 bit */ } nxt_var_decl_t; -- cgit From c185ae7512460b850d97f9e849a577550a3a2797 Mon Sep 17 00:00:00 2001 From: Alejandro Colomar Date: Wed, 14 Jun 2023 08:59:53 +0200 Subject: Fixed indentation. Signed-off-by: Alejandro Colomar --- src/nxt_http_rewrite.c | 4 ++-- src/nxt_tstr.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nxt_http_rewrite.c b/src/nxt_http_rewrite.c index b800a919..fac96339 100644 --- a/src/nxt_http_rewrite.c +++ b/src/nxt_http_rewrite.c @@ -10,8 +10,8 @@ nxt_int_t nxt_http_rewrite_init(nxt_router_conf_t *rtcf, nxt_http_action_t *action, - nxt_http_action_conf_t *acf) - { + nxt_http_action_conf_t *acf) +{ nxt_str_t str; nxt_conf_get_string(acf->rewrite, &str); diff --git a/src/nxt_tstr.c b/src/nxt_tstr.c index 516415d9..0e084bb4 100644 --- a/src/nxt_tstr.c +++ b/src/nxt_tstr.c @@ -42,8 +42,8 @@ struct nxt_tstr_query_s { void *ctx; void *data; - nxt_work_handler_t ready; - nxt_work_handler_t error; + nxt_work_handler_t ready; + nxt_work_handler_t error; }; -- cgit From 18d3637e4bffd5fff09091a536dca3fdecc931d7 Mon Sep 17 00:00:00 2001 From: Zhidao HONG Date: Tue, 11 Jul 2023 09:30:50 +0800 Subject: NJS: supported 0.8.0. --- src/nxt_js.c | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/nxt_js.c b/src/nxt_js.c index df945db6..066d08f6 100644 --- a/src/nxt_js.c +++ b/src/nxt_js.c @@ -386,11 +386,11 @@ nxt_js_call(nxt_task_t *task, nxt_js_conf_t *jcf, nxt_js_cache_t *cache, nxt_js_t *js, nxt_str_t *str, void *ctx) { njs_vm_t *vm; - njs_int_t rc, ret; + njs_int_t ret; njs_str_t res; - njs_value_t *array, *value; + njs_value_t *value; njs_function_t *func; - njs_opaque_value_t opaque_value, arguments[6]; + njs_opaque_value_t retval, opaque_value, arguments[6]; static const njs_str_t uri_str = njs_str("uri"); static const njs_str_t host_str = njs_str("host"); @@ -407,15 +407,13 @@ nxt_js_call(nxt_task_t *task, nxt_js_conf_t *jcf, nxt_js_cache_t *cache, return NXT_ERROR; } - ret = njs_vm_start(vm); + ret = njs_vm_start(vm, njs_value_arg(&retval)); if (ret != NJS_OK) { return NXT_ERROR; } - array = njs_vm_retval(vm); - cache->vm = vm; - cache->array = *array; + cache->array = *njs_value_arg(&retval); } value = njs_vm_array_prop(vm, &cache->array, js->index, &opaque_value); @@ -463,18 +461,20 @@ nxt_js_call(nxt_task_t *task, nxt_js_conf_t *jcf, nxt_js_cache_t *cache, return NXT_ERROR; } - ret = njs_vm_call(vm, func, njs_value_arg(&arguments), 6); - - rc = njs_vm_retval_string(vm, &res); - if (rc != NJS_OK) { - return NXT_ERROR; - } + ret = njs_vm_invoke(vm, func, njs_value_arg(&arguments), 6, + njs_value_arg(&retval)); if (ret != NJS_OK) { - nxt_alert(task, "js exception: %V", &res); + ret = njs_vm_exception_string(vm, &res); + if (ret == NJS_OK) { + nxt_alert(task, "js exception: %V", &res); + } + return NXT_ERROR; } + ret = njs_vm_value_string(vm, &res, njs_value_arg(&retval)); + str->length = res.length; str->start = res.start; @@ -498,7 +498,7 @@ nxt_js_error(njs_vm_t *vm, u_char *error) njs_str_t res; nxt_str_t err; - ret = njs_vm_retval_string(vm, &res); + ret = njs_vm_exception_string(vm, &res); if (nxt_slow_path(ret != NJS_OK)) { return NXT_ERROR; } -- cgit From c61ccec7b4926476b2188092e25990e433332688 Mon Sep 17 00:00:00 2001 From: Zhidao HONG Date: Mon, 19 Jun 2023 16:29:22 +0800 Subject: Variables refactoring. This commit is to reimplement the variables with an unknown field such as $header_{name} to make the parsing more generic, it's a preparation for supporting response header variables. --- src/nxt_http_variables.c | 168 +++++++++++++++++++++++++++++------------------ src/nxt_main.h | 2 + src/nxt_tstr.c | 14 ++-- src/nxt_tstr.h | 6 +- src/nxt_var.c | 166 +++++++++++++++++++--------------------------- src/nxt_var.h | 28 +++++--- 6 files changed, 204 insertions(+), 180 deletions(-) (limited to 'src') diff --git a/src/nxt_http_variables.c b/src/nxt_http_variables.c index 71f7ff7f..04e21e67 100644 --- a/src/nxt_http_variables.c +++ b/src/nxt_http_variables.c @@ -8,39 +8,39 @@ static nxt_int_t nxt_http_var_dollar(nxt_task_t *task, nxt_str_t *str, - void *ctx, uint16_t field); + void *ctx, void *data); static nxt_int_t nxt_http_var_request_time(nxt_task_t *task, nxt_str_t *str, - void *ctx, uint16_t field); + void *ctx, void *data); static nxt_int_t nxt_http_var_method(nxt_task_t *task, nxt_str_t *str, - void *ctx, uint16_t field); + void *ctx, void *data); static nxt_int_t nxt_http_var_request_uri(nxt_task_t *task, nxt_str_t *str, - void *ctx, uint16_t field); + void *ctx, void *data); static nxt_int_t nxt_http_var_uri(nxt_task_t *task, nxt_str_t *str, void *ctx, - uint16_t field); + void *data); static nxt_int_t nxt_http_var_host(nxt_task_t *task, nxt_str_t *str, void *ctx, - uint16_t field); + void *data); static nxt_int_t nxt_http_var_remote_addr(nxt_task_t *task, nxt_str_t *str, - void *ctx, uint16_t field); + void *ctx, void *data); static nxt_int_t nxt_http_var_time_local(nxt_task_t *task, nxt_str_t *str, - void *ctx, uint16_t field); + void *ctx, void *data); static u_char *nxt_http_log_date(u_char *buf, nxt_realtime_t *now, struct tm *tm, size_t size, const char *format); static nxt_int_t nxt_http_var_request_line(nxt_task_t *task, nxt_str_t *str, - void *ctx, uint16_t field); + void *ctx, void *data); static nxt_int_t nxt_http_var_status(nxt_task_t *task, nxt_str_t *str, - void *ctx, uint16_t field); + void *ctx, void *data); static nxt_int_t nxt_http_var_body_bytes_sent(nxt_task_t *task, nxt_str_t *str, - void *ctx, uint16_t field); + void *ctx, void *data); static nxt_int_t nxt_http_var_referer(nxt_task_t *task, nxt_str_t *str, - void *ctx, uint16_t field); + void *ctx, void *data); static nxt_int_t nxt_http_var_user_agent(nxt_task_t *task, nxt_str_t *str, - void *ctx, uint16_t field); + void *ctx, void *data); static nxt_int_t nxt_http_var_arg(nxt_task_t *task, nxt_str_t *str, void *ctx, - uint16_t field); + void *data); static nxt_int_t nxt_http_var_header(nxt_task_t *task, nxt_str_t *str, - void *ctx, uint16_t field); + void *ctx, void *data); static nxt_int_t nxt_http_var_cookie(nxt_task_t *task, nxt_str_t *str, - void *ctx, uint16_t field); + void *ctx, void *data); static nxt_var_decl_t nxt_http_vars[] = { @@ -96,21 +96,6 @@ static nxt_var_decl_t nxt_http_vars[] = { .name = nxt_string("header_user_agent"), .handler = nxt_http_var_user_agent, .cacheable = 1, - }, { - .name = nxt_string("arg"), - .handler = nxt_http_var_arg, - .field_hash = nxt_http_argument_hash, - .cacheable = 1, - }, { - .name = nxt_string("header"), - .handler = nxt_http_var_header, - .field_hash = nxt_http_header_hash, - .cacheable = 1, - }, { - .name = nxt_string("cookie"), - .handler = nxt_http_var_cookie, - .field_hash = nxt_http_cookie_hash, - .cacheable = 1, }, }; @@ -122,8 +107,76 @@ nxt_http_register_variables(void) } +nxt_int_t +nxt_http_unknown_var_ref(nxt_tstr_state_t *state, nxt_var_ref_t *ref, + nxt_str_t *name) +{ + int64_t hash; + nxt_str_t str; + + if (nxt_str_start(name, "header_", 7)) { + ref->handler = nxt_http_var_header; + ref->cacheable = 1; + + str.start = name->start + 7; + str.length = name->length - 7; + + if (str.length == 0) { + return NXT_ERROR; + } + + hash = nxt_http_header_hash(state->pool, &str); + if (nxt_slow_path(hash == -1)) { + return NXT_ERROR; + } + + } else if (nxt_str_start(name, "arg_", 4)) { + ref->handler = nxt_http_var_arg; + ref->cacheable = 1; + + str.start = name->start + 4; + str.length = name->length - 4; + + if (str.length == 0) { + return NXT_ERROR; + } + + hash = nxt_http_argument_hash(state->pool, &str); + if (nxt_slow_path(hash == -1)) { + return NXT_ERROR; + } + + } else if (nxt_str_start(name, "cookie_", 7)) { + ref->handler = nxt_http_var_cookie; + ref->cacheable = 1; + + str.start = name->start + 7; + str.length = name->length - 7; + + if (str.length == 0) { + return NXT_ERROR; + } + + hash = nxt_http_cookie_hash(state->pool, &str); + if (nxt_slow_path(hash == -1)) { + return NXT_ERROR; + } + + } else { + return NXT_ERROR; + } + + ref->data = nxt_var_field_new(state->pool, &str, (uint32_t) hash); + if (nxt_slow_path(ref->data == NULL)) { + return NXT_ERROR; + } + + return NXT_OK; +} + + static nxt_int_t -nxt_http_var_dollar(nxt_task_t *task, nxt_str_t *str, void *ctx, uint16_t field) +nxt_http_var_dollar(nxt_task_t *task, nxt_str_t *str, void *ctx, void *data) { nxt_str_set(str, "$"); @@ -133,7 +186,7 @@ nxt_http_var_dollar(nxt_task_t *task, nxt_str_t *str, void *ctx, uint16_t field) static nxt_int_t nxt_http_var_request_time(nxt_task_t *task, nxt_str_t *str, void *ctx, - uint16_t field) + void *data) { u_char *p; nxt_msec_t ms; @@ -160,7 +213,7 @@ nxt_http_var_request_time(nxt_task_t *task, nxt_str_t *str, void *ctx, static nxt_int_t -nxt_http_var_method(nxt_task_t *task, nxt_str_t *str, void *ctx, uint16_t field) +nxt_http_var_method(nxt_task_t *task, nxt_str_t *str, void *ctx, void *data) { nxt_http_request_t *r; @@ -174,7 +227,7 @@ nxt_http_var_method(nxt_task_t *task, nxt_str_t *str, void *ctx, uint16_t field) static nxt_int_t nxt_http_var_request_uri(nxt_task_t *task, nxt_str_t *str, void *ctx, - uint16_t field) + void *data) { nxt_http_request_t *r; @@ -187,7 +240,7 @@ nxt_http_var_request_uri(nxt_task_t *task, nxt_str_t *str, void *ctx, static nxt_int_t -nxt_http_var_uri(nxt_task_t *task, nxt_str_t *str, void *ctx, uint16_t field) +nxt_http_var_uri(nxt_task_t *task, nxt_str_t *str, void *ctx, void *data) { nxt_http_request_t *r; @@ -200,7 +253,7 @@ nxt_http_var_uri(nxt_task_t *task, nxt_str_t *str, void *ctx, uint16_t field) static nxt_int_t -nxt_http_var_host(nxt_task_t *task, nxt_str_t *str, void *ctx, uint16_t field) +nxt_http_var_host(nxt_task_t *task, nxt_str_t *str, void *ctx, void *data) { nxt_http_request_t *r; @@ -214,7 +267,7 @@ nxt_http_var_host(nxt_task_t *task, nxt_str_t *str, void *ctx, uint16_t field) static nxt_int_t nxt_http_var_remote_addr(nxt_task_t *task, nxt_str_t *str, void *ctx, - uint16_t field) + void *data) { nxt_http_request_t *r; @@ -228,8 +281,7 @@ nxt_http_var_remote_addr(nxt_task_t *task, nxt_str_t *str, void *ctx, static nxt_int_t -nxt_http_var_time_local(nxt_task_t *task, nxt_str_t *str, void *ctx, - uint16_t field) +nxt_http_var_time_local(nxt_task_t *task, nxt_str_t *str, void *ctx, void *data) { nxt_http_request_t *r; @@ -287,7 +339,7 @@ nxt_http_log_date(u_char *buf, nxt_realtime_t *now, struct tm *tm, static nxt_int_t nxt_http_var_request_line(nxt_task_t *task, nxt_str_t *str, void *ctx, - uint16_t field) + void *data) { nxt_http_request_t *r; @@ -301,7 +353,7 @@ nxt_http_var_request_line(nxt_task_t *task, nxt_str_t *str, void *ctx, static nxt_int_t nxt_http_var_body_bytes_sent(nxt_task_t *task, nxt_str_t *str, void *ctx, - uint16_t field) + void *data) { nxt_off_t bytes; nxt_http_request_t *r; @@ -323,7 +375,7 @@ nxt_http_var_body_bytes_sent(nxt_task_t *task, nxt_str_t *str, void *ctx, static nxt_int_t -nxt_http_var_status(nxt_task_t *task, nxt_str_t *str, void *ctx, uint16_t field) +nxt_http_var_status(nxt_task_t *task, nxt_str_t *str, void *ctx, void *data) { nxt_http_request_t *r; @@ -342,8 +394,7 @@ nxt_http_var_status(nxt_task_t *task, nxt_str_t *str, void *ctx, uint16_t field) static nxt_int_t -nxt_http_var_referer(nxt_task_t *task, nxt_str_t *str, void *ctx, - uint16_t field) +nxt_http_var_referer(nxt_task_t *task, nxt_str_t *str, void *ctx, void *data) { nxt_http_request_t *r; @@ -362,8 +413,7 @@ nxt_http_var_referer(nxt_task_t *task, nxt_str_t *str, void *ctx, static nxt_int_t -nxt_http_var_user_agent(nxt_task_t *task, nxt_str_t *str, void *ctx, - uint16_t field) +nxt_http_var_user_agent(nxt_task_t *task, nxt_str_t *str, void *ctx, void *data) { nxt_http_request_t *r; @@ -382,19 +432,15 @@ nxt_http_var_user_agent(nxt_task_t *task, nxt_str_t *str, void *ctx, static nxt_int_t -nxt_http_var_arg(nxt_task_t *task, nxt_str_t *str, void *ctx, uint16_t field) +nxt_http_var_arg(nxt_task_t *task, nxt_str_t *str, void *ctx, void *data) { nxt_array_t *args; nxt_var_field_t *vf; - nxt_router_conf_t *rtcf; nxt_http_request_t *r; nxt_http_name_value_t *nv, *start; r = ctx; - - rtcf = r->conf->socket_conf->router_conf; - - vf = nxt_var_field_get(rtcf->tstr_state->var_fields, field); + vf = data; args = nxt_http_arguments_parse(r); if (nxt_slow_path(args == NULL)) { @@ -426,18 +472,14 @@ nxt_http_var_arg(nxt_task_t *task, nxt_str_t *str, void *ctx, uint16_t field) static nxt_int_t -nxt_http_var_header(nxt_task_t *task, nxt_str_t *str, void *ctx, uint16_t field) +nxt_http_var_header(nxt_task_t *task, nxt_str_t *str, void *ctx, void *data) { nxt_var_field_t *vf; nxt_http_field_t *f; - nxt_router_conf_t *rtcf; nxt_http_request_t *r; r = ctx; - - rtcf = r->conf->socket_conf->router_conf; - - vf = nxt_var_field_get(rtcf->tstr_state->var_fields, field); + vf = data; nxt_list_each(f, r->fields) { @@ -460,19 +502,15 @@ nxt_http_var_header(nxt_task_t *task, nxt_str_t *str, void *ctx, uint16_t field) static nxt_int_t -nxt_http_var_cookie(nxt_task_t *task, nxt_str_t *str, void *ctx, uint16_t field) +nxt_http_var_cookie(nxt_task_t *task, nxt_str_t *str, void *ctx, void *data) { nxt_array_t *cookies; nxt_var_field_t *vf; - nxt_router_conf_t *rtcf; nxt_http_request_t *r; nxt_http_name_value_t *nv, *end; r = ctx; - - rtcf = r->conf->socket_conf->router_conf; - - vf = nxt_var_field_get(rtcf->tstr_state->var_fields, field); + vf = data; cookies = nxt_http_cookies_parse(r); if (nxt_slow_path(cookies == NULL)) { diff --git a/src/nxt_main.h b/src/nxt_main.h index a7e0c283..aa96256e 100644 --- a/src/nxt_main.h +++ b/src/nxt_main.h @@ -68,6 +68,8 @@ typedef uint16_t nxt_port_id_t; #include #include + +typedef struct nxt_tstr_state_s nxt_tstr_state_t; #include #include diff --git a/src/nxt_tstr.c b/src/nxt_tstr.c index 0e084bb4..edf6860a 100644 --- a/src/nxt_tstr.c +++ b/src/nxt_tstr.c @@ -64,8 +64,8 @@ nxt_tstr_state_new(nxt_mp_t *mp, nxt_bool_t test) state->pool = mp; state->test = test; - state->var_fields = nxt_array_create(mp, 4, sizeof(nxt_var_field_t)); - if (nxt_slow_path(state->var_fields == NULL)) { + state->var_refs = nxt_array_create(mp, 4, sizeof(nxt_var_ref_t)); + if (nxt_slow_path(state->var_refs == NULL)) { return NULL; } @@ -133,8 +133,7 @@ nxt_tstr_compile(nxt_tstr_state_t *state, nxt_str_t *str, if (p != NULL) { tstr->type = NXT_TSTR_VAR; - tstr->u.var = nxt_var_compile(&tstr->str, state->pool, - state->var_fields); + tstr->u.var = nxt_var_compile(state, &tstr->str); if (nxt_slow_path(tstr->u.var == NULL)) { return NULL; } @@ -168,7 +167,7 @@ nxt_tstr_test(nxt_tstr_state_t *state, nxt_str_t *str, u_char *error) p = memchr(str->start, '$', str->length); if (p != NULL) { - return nxt_var_test(str, state->var_fields, error); + return nxt_var_test(state, str, error); } } @@ -263,8 +262,9 @@ nxt_tstr_query(nxt_task_t *task, nxt_tstr_query_t *query, nxt_tstr_t *tstr, } if (tstr->type == NXT_TSTR_VAR) { - ret = nxt_var_interpreter(task, &query->cache->var, tstr->u.var, val, - query->ctx, tstr->flags & NXT_TSTR_LOGGING); + ret = nxt_var_interpreter(task, query->state, &query->cache->var, + tstr->u.var, val, query->ctx, + tstr->flags & NXT_TSTR_LOGGING); if (nxt_slow_path(ret != NXT_OK)) { query->failed = 1; diff --git a/src/nxt_tstr.h b/src/nxt_tstr.h index afa7f56d..3e842f81 100644 --- a/src/nxt_tstr.h +++ b/src/nxt_tstr.h @@ -13,14 +13,14 @@ typedef struct nxt_tstr_s nxt_tstr_t; typedef struct nxt_tstr_query_s nxt_tstr_query_t; -typedef struct { +struct nxt_tstr_state_s { nxt_mp_t *pool; - nxt_array_t *var_fields; + nxt_array_t *var_refs; #if (NXT_HAVE_NJS) nxt_js_conf_t *jcf; #endif uint8_t test; /* 1 bit */ -} nxt_tstr_state_t; +}; typedef struct { diff --git a/src/nxt_var.c b/src/nxt_var.c index e4299a09..729de788 100644 --- a/src/nxt_var.c +++ b/src/nxt_var.c @@ -50,14 +50,11 @@ struct nxt_var_query_s { static nxt_int_t nxt_var_hash_test(nxt_lvlhsh_query_t *lhq, void *data); static nxt_var_decl_t *nxt_var_hash_find(nxt_str_t *name); -static nxt_var_decl_t *nxt_var_decl_get(nxt_str_t *name, nxt_array_t *fields, - uint32_t *index); -static nxt_var_field_t *nxt_var_field_add(nxt_array_t *fields, nxt_str_t *name, - uint32_t hash); +static nxt_var_ref_t *nxt_var_ref_get(nxt_tstr_state_t *state, nxt_str_t *name); static nxt_int_t nxt_var_cache_test(nxt_lvlhsh_query_t *lhq, void *data); -static nxt_str_t *nxt_var_cache_value(nxt_task_t *task, nxt_var_cache_t *cache, - uint32_t index, void *ctx); +static nxt_str_t *nxt_var_cache_value(nxt_task_t *task, nxt_tstr_state_t *state, + nxt_var_cache_t *cache, uint32_t index, void *ctx); static u_char *nxt_var_next_part(u_char *start, u_char *end, nxt_str_t *part); @@ -111,95 +108,70 @@ nxt_var_hash_find(nxt_str_t *name) } -static nxt_var_decl_t * -nxt_var_decl_get(nxt_str_t *name, nxt_array_t *fields, uint32_t *index) +static nxt_var_ref_t * +nxt_var_ref_get(nxt_tstr_state_t *state, nxt_str_t *name) { - u_char *p, *end; - int64_t hash; - uint16_t field; - nxt_str_t str; - nxt_var_decl_t *decl; - nxt_var_field_t *f; - - f = NULL; - field = 0; - decl = nxt_var_hash_find(name); + nxt_int_t ret; + nxt_uint_t i; + nxt_var_ref_t *ref; + nxt_var_decl_t *decl; - if (decl == NULL) { - p = name->start; - end = p + name->length; + ref = state->var_refs->elts; - while (p < end) { - if (*p++ == '_') { - break; - } - } + for (i = 0; i < state->var_refs->nelts; i++) { - if (p == end) { - return NULL; + if (nxt_strstr_eq(ref[i].name, name)) { + return &ref[i]; } + } - str.start = name->start; - str.length = p - 1 - name->start; + ref = nxt_array_add(state->var_refs); + if (nxt_slow_path(ref == NULL)) { + return NULL; + } - decl = nxt_var_hash_find(&str); + ref->index = state->var_refs->nelts - 1; - if (decl != NULL) { - str.start = p; - str.length = end - p; + ref->name = nxt_str_dup(state->pool, NULL, name); + if (nxt_slow_path(ref->name == NULL)) { + return NULL; + } - hash = decl->field_hash(fields->mem_pool, &str); - if (nxt_slow_path(hash == -1)) { - return NULL; - } + decl = nxt_var_hash_find(name); - f = nxt_var_field_add(fields, &str, (uint32_t) hash); - if (nxt_slow_path(f == NULL)) { - return NULL; - } + if (decl != NULL) { + ref->handler = decl->handler; + ref->cacheable = decl->cacheable; - field = f->index; - } + return ref; } - if (decl != NULL) { - if (decl->field_hash != NULL && f == NULL) { - return NULL; - } - - if (index != NULL) { - *index = (decl->index << 16) | field; - } + ret = nxt_http_unknown_var_ref(state, ref, name); + if (nxt_slow_path(ret != NXT_OK)) { + return NULL; } - return decl; + return ref; } -static nxt_var_field_t * -nxt_var_field_add(nxt_array_t *fields, nxt_str_t *name, uint32_t hash) +nxt_var_field_t * +nxt_var_field_new(nxt_mp_t *mp, nxt_str_t *name, uint32_t hash) { - nxt_uint_t i; + nxt_str_t *str; nxt_var_field_t *field; - field = fields->elts; - - for (i = 0; i < fields->nelts; i++) { - if (field[i].hash == hash - && nxt_strstr_eq(&field[i].name, name)) - { - return field; - } + field = nxt_mp_alloc(mp, sizeof(nxt_var_field_t)); + if (nxt_slow_path(field == NULL)) { + return NULL; } - field = nxt_array_add(fields); - if (nxt_slow_path(field == NULL)) { + str = nxt_str_dup(mp, &field->name, name); + if (nxt_slow_path(str == NULL)) { return NULL; } - field->name = *name; field->hash = hash; - field->index = fields->nelts - 1; return field; } @@ -230,15 +202,16 @@ nxt_var_cache_test(nxt_lvlhsh_query_t *lhq, void *data) static nxt_str_t * -nxt_var_cache_value(nxt_task_t *task, nxt_var_cache_t *cache, uint32_t index, - void *ctx) +nxt_var_cache_value(nxt_task_t *task, nxt_tstr_state_t *state, + nxt_var_cache_t *cache, uint32_t index, void *ctx) { nxt_int_t ret; nxt_str_t *value; - nxt_var_decl_t *var; + nxt_var_ref_t *ref; nxt_lvlhsh_query_t lhq; - var = nxt_vars[index >> 16]; + ref = state->var_refs->elts; + ref = &ref[index]; value = cache->spare; @@ -251,7 +224,7 @@ nxt_var_cache_value(nxt_task_t *task, nxt_var_cache_t *cache, uint32_t index, cache->spare = value; } - if (!var->cacheable) { + if (!ref->cacheable) { goto not_cached; } @@ -274,7 +247,7 @@ nxt_var_cache_value(nxt_task_t *task, nxt_var_cache_t *cache, uint32_t index, not_cached: - ret = var->handler(task, value, ctx, index & 0xffff); + ret = ref->handler(task, value, ctx, ref->data); if (nxt_slow_path(ret != NXT_OK)) { return NULL; } @@ -326,7 +299,6 @@ nxt_var_index_init(void) for (i = 0; i < nxt_var_count; i++) { decl = nxt_lvlhsh_each(&nxt_var_hash, &lhe); - decl->index = i; vars[i] = decl; } @@ -337,16 +309,15 @@ nxt_var_index_init(void) nxt_var_t * -nxt_var_compile(nxt_str_t *str, nxt_mp_t *mp, nxt_array_t *fields) +nxt_var_compile(nxt_tstr_state_t *state, nxt_str_t *str) { - u_char *p, *end, *next, *src; - size_t size; - uint32_t index; - nxt_var_t *var; - nxt_str_t part; - nxt_uint_t n; - nxt_var_sub_t *subs; - nxt_var_decl_t *decl; + u_char *p, *end, *next, *src; + size_t size; + nxt_var_t *var; + nxt_str_t part; + nxt_uint_t n; + nxt_var_sub_t *subs; + nxt_var_ref_t *ref; n = 0; @@ -366,7 +337,7 @@ nxt_var_compile(nxt_str_t *str, nxt_mp_t *mp, nxt_array_t *fields) size = sizeof(nxt_var_t) + n * sizeof(nxt_var_sub_t) + str->length; - var = nxt_mp_get(mp, size); + var = nxt_mp_get(state->pool, size); if (nxt_slow_path(var == NULL)) { return NULL; } @@ -386,12 +357,12 @@ nxt_var_compile(nxt_str_t *str, nxt_mp_t *mp, nxt_array_t *fields) next = nxt_var_next_part(p, end, &part); if (part.start != NULL) { - decl = nxt_var_decl_get(&part, fields, &index); - if (nxt_slow_path(decl == NULL)) { + ref = nxt_var_ref_get(state, &part); + if (nxt_slow_path(ref == NULL)) { return NULL; } - subs[n].index = index; + subs[n].index = ref->index; subs[n].length = next - p; subs[n].position = p - str->start; @@ -406,11 +377,11 @@ nxt_var_compile(nxt_str_t *str, nxt_mp_t *mp, nxt_array_t *fields) nxt_int_t -nxt_var_test(nxt_str_t *str, nxt_array_t *fields, u_char *error) +nxt_var_test(nxt_tstr_state_t *state, nxt_str_t *str, u_char *error) { - u_char *p, *end, *next; - nxt_str_t part; - nxt_var_decl_t *decl; + u_char *p, *end, *next; + nxt_str_t part; + nxt_var_ref_t *ref; p = str->start; end = p + str->length; @@ -426,9 +397,9 @@ nxt_var_test(nxt_str_t *str, nxt_array_t *fields, u_char *error) } if (part.start != NULL) { - decl = nxt_var_decl_get(&part, fields, NULL); + ref = nxt_var_ref_get(state, &part); - if (decl == NULL) { + if (ref == NULL) { nxt_sprintf(error, error + NXT_MAX_ERROR_STR, "Unknown variable \"%V\"%Z", &part); @@ -514,8 +485,9 @@ nxt_var_next_part(u_char *start, u_char *end, nxt_str_t *part) nxt_int_t -nxt_var_interpreter(nxt_task_t *task, nxt_var_cache_t *cache, nxt_var_t *var, - nxt_str_t *str, void *ctx, nxt_bool_t logging) +nxt_var_interpreter(nxt_task_t *task, nxt_tstr_state_t *state, + nxt_var_cache_t *cache, nxt_var_t *var, nxt_str_t *str, void *ctx, + nxt_bool_t logging) { u_char *p, *src; size_t length, last, next; @@ -532,7 +504,7 @@ nxt_var_interpreter(nxt_task_t *task, nxt_var_cache_t *cache, nxt_var_t *var, length = var->length; for (i = 0; i < var->vars; i++) { - value = nxt_var_cache_value(task, cache, subs[i].index, ctx); + value = nxt_var_cache_value(task, state, cache, subs[i].index, ctx); if (nxt_slow_path(value == NULL)) { return NXT_ERROR; } diff --git a/src/nxt_var.h b/src/nxt_var.h index d3da26eb..fde64f1e 100644 --- a/src/nxt_var.h +++ b/src/nxt_var.h @@ -13,23 +13,29 @@ typedef struct nxt_var_query_s nxt_var_query_t; typedef nxt_int_t (*nxt_var_handler_t)(nxt_task_t *task, nxt_str_t *str, - void *ctx, uint16_t field); + void *ctx, void *data); typedef int64_t (*nxt_var_field_hash_t)(nxt_mp_t *mp, nxt_str_t *str); typedef struct { nxt_str_t name; nxt_var_handler_t handler; - nxt_var_field_hash_t field_hash; - uint32_t index; uint8_t cacheable; /* 1 bit */ } nxt_var_decl_t; +typedef struct { + nxt_str_t *name; + nxt_var_handler_t handler; + void *data; + uint32_t index; + uint8_t cacheable; /* 1 bit */ +} nxt_var_ref_t; + + typedef struct { nxt_str_t name; uint16_t hash; - uint32_t index; } nxt_var_field_t; @@ -44,14 +50,20 @@ nxt_int_t nxt_var_register(nxt_var_decl_t *decl, size_t n); nxt_int_t nxt_var_index_init(void); nxt_var_field_t *nxt_var_field_get(nxt_array_t *fields, uint16_t index); +nxt_var_field_t *nxt_var_field_new(nxt_mp_t *mp, nxt_str_t *name, + uint32_t hash); -nxt_var_t *nxt_var_compile(nxt_str_t *str, nxt_mp_t *mp, nxt_array_t *fields); -nxt_int_t nxt_var_test(nxt_str_t *str, nxt_array_t *fields, u_char *error); +nxt_var_t *nxt_var_compile(nxt_tstr_state_t *state, nxt_str_t *str); +nxt_int_t nxt_var_test(nxt_tstr_state_t *state, nxt_str_t *str, u_char *error); -nxt_int_t nxt_var_interpreter(nxt_task_t *task, nxt_var_cache_t *cache, - nxt_var_t *var, nxt_str_t *str, void *ctx, nxt_bool_t logging); +nxt_int_t nxt_var_interpreter(nxt_task_t *task, nxt_tstr_state_t *state, + nxt_var_cache_t *cache, nxt_var_t *var, nxt_str_t *str, void *ctx, + nxt_bool_t logging); nxt_str_t *nxt_var_get(nxt_task_t *task, nxt_var_cache_t *cache, nxt_str_t *name, void *ctx); +nxt_int_t nxt_http_unknown_var_ref(nxt_tstr_state_t *state, nxt_var_ref_t *ref, + nxt_str_t *name); + #endif /* _NXT_VAR_H_INCLUDED_ */ -- cgit From 458722df55bb9727680b7734e7fff8fb5639db28 Mon Sep 17 00:00:00 2001 From: Zhidao HONG Date: Sat, 1 Jul 2023 12:18:22 +0800 Subject: Var: supported HTTP response header variables. This commit adds the variable $response_header_NAME. --- src/nxt_h1proto.c | 2 +- src/nxt_h1proto.h | 3 + src/nxt_http_variables.c | 217 +++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 216 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/nxt_h1proto.c b/src/nxt_h1proto.c index df1f82f9..1dfe4b6e 100644 --- a/src/nxt_h1proto.c +++ b/src/nxt_h1proto.c @@ -1284,7 +1284,7 @@ nxt_h1p_request_header_send(nxt_task_t *task, nxt_http_request_t *r, size += NXT_WEBSOCKET_ACCEPT_SIZE + 2; } else { - http11 = (h1p->parser.version.s.minor != '0'); + http11 = nxt_h1p_is_http11(h1p); if (r->resp.content_length == NULL || r->resp.content_length->skip) { diff --git a/src/nxt_h1proto.h b/src/nxt_h1proto.h index f8500963..b324db8d 100644 --- a/src/nxt_h1proto.h +++ b/src/nxt_h1proto.h @@ -51,4 +51,7 @@ struct nxt_h1proto_s { nxt_conn_t *conn; }; +#define nxt_h1p_is_http11(h1p) \ + ((h1p)->parser.version.s.minor != '0') + #endif /* _NXT_H1PROTO_H_INCLUDED_ */ diff --git a/src/nxt_http_variables.c b/src/nxt_http_variables.c index 04e21e67..04e24a4b 100644 --- a/src/nxt_http_variables.c +++ b/src/nxt_http_variables.c @@ -5,6 +5,7 @@ #include #include +#include static nxt_int_t nxt_http_var_dollar(nxt_task_t *task, nxt_str_t *str, @@ -35,12 +36,20 @@ static nxt_int_t nxt_http_var_referer(nxt_task_t *task, nxt_str_t *str, void *ctx, void *data); static nxt_int_t nxt_http_var_user_agent(nxt_task_t *task, nxt_str_t *str, void *ctx, void *data); +static nxt_int_t nxt_http_var_response_connection(nxt_task_t *task, + nxt_str_t *str, void *ctx, void *data); +static nxt_int_t nxt_http_var_response_content_length(nxt_task_t *task, + nxt_str_t *str, void *ctx, void *data); +static nxt_int_t nxt_http_var_response_transfer_encoding(nxt_task_t *task, + nxt_str_t *str, void *ctx, void *data); static nxt_int_t nxt_http_var_arg(nxt_task_t *task, nxt_str_t *str, void *ctx, void *data); static nxt_int_t nxt_http_var_header(nxt_task_t *task, nxt_str_t *str, void *ctx, void *data); static nxt_int_t nxt_http_var_cookie(nxt_task_t *task, nxt_str_t *str, void *ctx, void *data); +static nxt_int_t nxt_http_var_response_header(nxt_task_t *task, nxt_str_t *str, + void *ctx, void *data); static nxt_var_decl_t nxt_http_vars[] = { @@ -92,6 +101,18 @@ static nxt_var_decl_t nxt_http_vars[] = { .name = nxt_string("header_referer"), .handler = nxt_http_var_referer, .cacheable = 1, + }, { + .name = nxt_string("response_header_connection"), + .handler = nxt_http_var_response_connection, + .cacheable = 1, + }, { + .name = nxt_string("response_header_content_length"), + .handler = nxt_http_var_response_content_length, + .cacheable = 1, + }, { + .name = nxt_string("response_header_transfer_encoding"), + .handler = nxt_http_var_response_transfer_encoding, + .cacheable = 1, }, { .name = nxt_string("header_user_agent"), .handler = nxt_http_var_user_agent, @@ -112,7 +133,30 @@ nxt_http_unknown_var_ref(nxt_tstr_state_t *state, nxt_var_ref_t *ref, nxt_str_t *name) { int64_t hash; - nxt_str_t str; + nxt_str_t str, *lower; + + if (nxt_str_start(name, "response_header_", 16)) { + ref->handler = nxt_http_var_response_header; + ref->cacheable = 1; + + str.start = name->start + 16; + str.length = name->length - 16; + + if (str.length == 0) { + return NXT_ERROR; + } + + lower = nxt_str_alloc(state->pool, str.length); + if (nxt_slow_path(lower == NULL)) { + return NXT_ERROR; + } + + nxt_memcpy_lowcase(lower->start, str.start, str.length); + + ref->data = lower; + + return NXT_OK; + } if (nxt_str_start(name, "header_", 7)) { ref->handler = nxt_http_var_header; @@ -355,6 +399,7 @@ static nxt_int_t nxt_http_var_body_bytes_sent(nxt_task_t *task, nxt_str_t *str, void *ctx, void *data) { + u_char *p; nxt_off_t bytes; nxt_http_request_t *r; @@ -367,8 +412,9 @@ nxt_http_var_body_bytes_sent(nxt_task_t *task, nxt_str_t *str, void *ctx, bytes = nxt_http_proto[r->protocol].body_bytes_sent(task, r->proto); - str->length = nxt_sprintf(str->start, str->start + NXT_OFF_T_LEN, "%O", - bytes) - str->start; + p = nxt_sprintf(str->start, str->start + NXT_OFF_T_LEN, "%O", bytes); + + str->length = p - str->start; return NXT_OK; } @@ -377,6 +423,7 @@ nxt_http_var_body_bytes_sent(nxt_task_t *task, nxt_str_t *str, void *ctx, static nxt_int_t nxt_http_var_status(nxt_task_t *task, nxt_str_t *str, void *ctx, void *data) { + u_char *p; nxt_http_request_t *r; r = ctx; @@ -386,8 +433,9 @@ nxt_http_var_status(nxt_task_t *task, nxt_str_t *str, void *ctx, void *data) return NXT_ERROR; } - str->length = nxt_sprintf(str->start, str->start + 3, "%03d", r->status) - - str->start; + p = nxt_sprintf(str->start, str->start + 3, "%03d", r->status); + + str->length = p - str->start; return NXT_OK; } @@ -431,6 +479,103 @@ nxt_http_var_user_agent(nxt_task_t *task, nxt_str_t *str, void *ctx, void *data) } +static nxt_int_t +nxt_http_var_response_connection(nxt_task_t *task, nxt_str_t *str, void *ctx, + void *data) +{ + nxt_int_t conn; + nxt_bool_t http11; + nxt_h1proto_t *h1p; + nxt_http_request_t *r; + + static const nxt_str_t connection[3] = { + nxt_string("close"), + nxt_string("keep-alive"), + nxt_string("Upgrade"), + }; + + r = ctx; + h1p = r->proto.h1; + + conn = -1; + + if (r->websocket_handshake && r->status == NXT_HTTP_SWITCHING_PROTOCOLS) { + conn = 2; + + } else { + http11 = nxt_h1p_is_http11(h1p); + + if (http11 ^ h1p->keepalive) { + conn = h1p->keepalive; + } + } + + if (conn >= 0) { + *str = connection[conn]; + + } else { + nxt_str_null(str); + } + + return NXT_OK; +} + + +static nxt_int_t +nxt_http_var_response_content_length(nxt_task_t *task, nxt_str_t *str, + void *ctx, void *data) +{ + u_char *p; + nxt_http_request_t *r; + + r = ctx; + + if (r->resp.content_length != NULL) { + str->length = r->resp.content_length->value_length; + str->start = r->resp.content_length->value; + + return NXT_OK; + } + + if (r->resp.content_length_n >= 0) { + str->start = nxt_mp_nget(r->mem_pool, NXT_OFF_T_LEN); + if (str->start == NULL) { + return NXT_ERROR; + } + + p = nxt_sprintf(str->start, str->start + NXT_OFF_T_LEN, + "%O", r->resp.content_length_n); + + str->length = p - str->start; + + return NXT_OK; + } + + nxt_str_null(str); + + return NXT_OK; +} + + +static nxt_int_t +nxt_http_var_response_transfer_encoding(nxt_task_t *task, nxt_str_t *str, + void *ctx, void *data) +{ + nxt_http_request_t *r; + + r = ctx; + + if (r->proto.h1->chunked) { + nxt_str_set(str, "chunked"); + + } else { + nxt_str_null(str); + } + + return NXT_OK; +} + + static nxt_int_t nxt_http_var_arg(nxt_task_t *task, nxt_str_t *str, void *ctx, void *data) { @@ -539,3 +684,65 @@ nxt_http_var_cookie(nxt_task_t *task, nxt_str_t *str, void *ctx, void *data) return NXT_OK; } + + +static int +nxt_http_field_name_cmp(nxt_str_t *name, nxt_http_field_t *field) +{ + size_t i; + u_char c1, c2; + + if (name->length != field->name_length) { + return 1; + } + + for (i = 0; i < name->length; i++) { + c1 = name->start[i]; + c2 = field->name[i]; + + if (c2 >= 'A' && c2 <= 'Z') { + c2 |= 0x20; + + } else if (c2 == '-') { + c2 = '_'; + } + + if (c1 != c2) { + return 1; + } + } + + return 0; +} + + +static nxt_int_t +nxt_http_var_response_header(nxt_task_t *task, nxt_str_t *str, void *ctx, + void *data) +{ + nxt_str_t *name; + nxt_http_field_t *f; + nxt_http_request_t *r; + + r = ctx; + name = data; + + nxt_list_each(f, r->resp.fields) { + + if (f->skip) { + continue; + } + + if (nxt_http_field_name_cmp(name, f) == 0) { + str->start = f->value; + str->length = f->value_length; + + return NXT_OK; + } + + } nxt_list_loop; + + nxt_str_null(str); + + return NXT_OK; +} -- cgit From d0fdf5971f47a4f12c56f13691abb6e9980b0707 Mon Sep 17 00:00:00 2001 From: Zhidao HONG Date: Wed, 12 Jul 2023 09:31:22 +0800 Subject: NJS: workaround for the warning in nxt_js_call() on Freebsd12 gcc. --- src/nxt_js.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nxt_js.c b/src/nxt_js.c index 066d08f6..74663660 100644 --- a/src/nxt_js.c +++ b/src/nxt_js.c @@ -407,13 +407,12 @@ nxt_js_call(nxt_task_t *task, nxt_js_conf_t *jcf, nxt_js_cache_t *cache, return NXT_ERROR; } - ret = njs_vm_start(vm, njs_value_arg(&retval)); + cache->vm = vm; + + ret = njs_vm_start(vm, &cache->array); if (ret != NJS_OK) { return NXT_ERROR; } - - cache->vm = vm; - cache->array = *njs_value_arg(&retval); } value = njs_vm_array_prop(vm, &cache->array, js->index, &opaque_value); -- cgit From 9f04d6db630a22658581cbe7a9d28ecdb13d8de9 Mon Sep 17 00:00:00 2001 From: Zhidao HONG Date: Wed, 9 Aug 2023 14:36:16 +0800 Subject: HTTP: stored matched action in nxt_http_request_t. No functional changes. --- src/nxt_http.h | 4 ++-- src/nxt_http_request.c | 8 +++----- src/nxt_http_rewrite.c | 10 ++++++++-- src/nxt_http_route.c | 5 +++++ src/nxt_http_static.c | 2 ++ 5 files changed, 20 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/nxt_http.h b/src/nxt_http.h index 08e1fcbe..fc47faa8 100644 --- a/src/nxt_http.h +++ b/src/nxt_http.h @@ -173,6 +173,7 @@ struct nxt_http_request_s { nxt_tstr_query_t *tstr_query; nxt_tstr_cache_t tstr_cache; + nxt_http_action_t *action; void *req_rpc_data; #if (NXT_HAVE_REGEX) @@ -382,8 +383,7 @@ nxt_int_t nxt_upstreams_joint_create(nxt_router_temp_conf_t *tmcf, nxt_int_t nxt_http_rewrite_init(nxt_router_conf_t *rtcf, nxt_http_action_t *action, nxt_http_action_conf_t *acf); -nxt_int_t nxt_http_rewrite(nxt_task_t *task, nxt_http_request_t *r, - nxt_http_action_t *action); +nxt_int_t nxt_http_rewrite(nxt_task_t *task, nxt_http_request_t *r); nxt_int_t nxt_http_return_init(nxt_router_conf_t *rtcf, nxt_http_action_t *action, nxt_http_action_conf_t *acf); diff --git a/src/nxt_http_request.c b/src/nxt_http_request.c index 48f7dbe3..1ede9ffa 100644 --- a/src/nxt_http_request.c +++ b/src/nxt_http_request.c @@ -560,11 +560,9 @@ nxt_http_request_action(nxt_task_t *task, nxt_http_request_t *r, if (nxt_fast_path(action != NULL)) { do { - if (action->rewrite != NULL) { - ret = nxt_http_rewrite(task, r, action); - if (nxt_slow_path(ret != NXT_OK)) { - break; - } + ret = nxt_http_rewrite(task, r); + if (nxt_slow_path(ret != NXT_OK)) { + break; } action = action->handler(task, r, action); diff --git a/src/nxt_http_rewrite.c b/src/nxt_http_rewrite.c index fac96339..ae5c865a 100644 --- a/src/nxt_http_rewrite.c +++ b/src/nxt_http_rewrite.c @@ -26,15 +26,21 @@ nxt_http_rewrite_init(nxt_router_conf_t *rtcf, nxt_http_action_t *action, nxt_int_t -nxt_http_rewrite(nxt_task_t *task, nxt_http_request_t *r, - nxt_http_action_t *action) +nxt_http_rewrite(nxt_task_t *task, nxt_http_request_t *r) { u_char *p; nxt_int_t ret; nxt_str_t str, encoded_path, target; nxt_router_conf_t *rtcf; + nxt_http_action_t *action; nxt_http_request_parse_t rp; + action = r->action; + + if (action == NULL || action->rewrite == NULL) { + return NXT_OK; + } + if (nxt_tstr_is_const(action->rewrite)) { nxt_tstr_str(action->rewrite, &str); diff --git a/src/nxt_http_route.c b/src/nxt_http_route.c index 0935dd4a..8fd6801d 100644 --- a/src/nxt_http_route.c +++ b/src/nxt_http_route.c @@ -1573,6 +1573,11 @@ nxt_http_route_handler(nxt_task_t *task, nxt_http_request_t *r, } if (action != NULL) { + + if (action != NXT_HTTP_ACTION_ERROR) { + r->action = action; + } + return action; } } diff --git a/src/nxt_http_static.c b/src/nxt_http_static.c index 5e44aab4..e51ba6b0 100644 --- a/src/nxt_http_static.c +++ b/src/nxt_http_static.c @@ -696,6 +696,8 @@ nxt_http_static_next(nxt_task_t *task, nxt_http_request_t *r, if (nxt_slow_path(r->log_route)) { nxt_log(task, NXT_LOG_NOTICE, "\"fallback\" taken"); } + + r->action = action->fallback; nxt_http_request_action(task, r, action->fallback); return; } -- cgit From a28bef097cd63da3bbb375ef0283e65be41a88ff Mon Sep 17 00:00:00 2001 From: Zhidao HONG Date: Wed, 9 Aug 2023 14:37:16 +0800 Subject: HTTP: controlling response headers support. --- src/nxt_conf_validation.c | 37 ++++++++++ src/nxt_http.h | 6 ++ src/nxt_http_request.c | 6 ++ src/nxt_http_route.c | 12 ++++ src/nxt_http_set_headers.c | 176 +++++++++++++++++++++++++++++++++++++++++++++ src/nxt_http_variables.c | 2 +- 6 files changed, 238 insertions(+), 1 deletion(-) create mode 100644 src/nxt_http_set_headers.c (limited to 'src') diff --git a/src/nxt_conf_validation.c b/src/nxt_conf_validation.c index 8c75a9fe..09f5598e 100644 --- a/src/nxt_conf_validation.c +++ b/src/nxt_conf_validation.c @@ -167,6 +167,8 @@ static nxt_int_t nxt_conf_vldt_match_addrs(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); static nxt_int_t nxt_conf_vldt_match_addr(nxt_conf_validation_t *vldt, nxt_conf_value_t *value); +static nxt_int_t nxt_conf_vldt_response_header(nxt_conf_validation_t *vldt, + nxt_str_t *name, nxt_conf_value_t *value); static nxt_int_t nxt_conf_vldt_app_name(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); static nxt_int_t nxt_conf_vldt_forwarded(nxt_conf_validation_t *vldt, @@ -688,6 +690,12 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_action_common_members[] = { .name = nxt_string("rewrite"), .type = NXT_CONF_VLDT_STRING, }, + { + .name = nxt_string("response_headers"), + .type = NXT_CONF_VLDT_OBJECT, + .validator = nxt_conf_vldt_object_iterator, + .u.object = nxt_conf_vldt_response_header, + }, NXT_CONF_VLDT_END }; @@ -2447,6 +2455,35 @@ nxt_conf_vldt_object_conf_commands(nxt_conf_validation_t *vldt, #endif +static nxt_int_t +nxt_conf_vldt_response_header(nxt_conf_validation_t *vldt, nxt_str_t *name, + nxt_conf_value_t *value) +{ + nxt_uint_t type; + + static nxt_str_t content_length = nxt_string("Content-Length"); + + if (name->length == 0) { + return nxt_conf_vldt_error(vldt, "The response header name " + "must not be empty."); + } + + if (nxt_strstr_eq(name, &content_length)) { + return nxt_conf_vldt_error(vldt, "The \"Content-Length\" response " + "header value is not supported"); + } + + type = nxt_conf_type(value); + + if (type == NXT_CONF_STRING || type == NXT_CONF_NULL) { + return NXT_OK; + } + + return nxt_conf_vldt_error(vldt, "The \"%V\" response header value " + "must either be a string or a null", name); +} + + static nxt_int_t nxt_conf_vldt_app_name(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data) diff --git a/src/nxt_http.h b/src/nxt_http.h index fc47faa8..e812bd0d 100644 --- a/src/nxt_http.h +++ b/src/nxt_http.h @@ -228,6 +228,7 @@ typedef struct nxt_http_route_addr_rule_s nxt_http_route_addr_rule_t; typedef struct { nxt_conf_value_t *rewrite; + nxt_conf_value_t *set_headers; nxt_conf_value_t *pass; nxt_conf_value_t *ret; nxt_conf_value_t *location; @@ -256,6 +257,7 @@ struct nxt_http_action_s { } u; nxt_tstr_t *rewrite; + nxt_array_t *set_headers; /* of nxt_http_field_t */ nxt_http_action_t *fallback; }; @@ -385,6 +387,10 @@ nxt_int_t nxt_http_rewrite_init(nxt_router_conf_t *rtcf, nxt_http_action_t *action, nxt_http_action_conf_t *acf); nxt_int_t nxt_http_rewrite(nxt_task_t *task, nxt_http_request_t *r); +nxt_int_t nxt_http_set_headers_init(nxt_router_conf_t *rtcf, + nxt_http_action_t *action, nxt_http_action_conf_t *acf); +nxt_int_t nxt_http_set_headers(nxt_http_request_t *r); + nxt_int_t nxt_http_return_init(nxt_router_conf_t *rtcf, nxt_http_action_t *action, nxt_http_action_conf_t *acf); diff --git a/src/nxt_http_request.c b/src/nxt_http_request.c index 1ede9ffa..e532baff 100644 --- a/src/nxt_http_request.c +++ b/src/nxt_http_request.c @@ -630,9 +630,15 @@ nxt_http_request_header_send(nxt_task_t *task, nxt_http_request_t *r, nxt_work_handler_t body_handler, void *data) { u_char *p, *end, *server_string; + nxt_int_t ret; nxt_http_field_t *server, *date, *content_length; nxt_socket_conf_t *skcf; + ret = nxt_http_set_headers(r); + if (nxt_slow_path(ret != NXT_OK)) { + goto fail; + } + /* * TODO: "Server", "Date", and "Content-Length" processing should be moved * to the last header filter. diff --git a/src/nxt_http_route.c b/src/nxt_http_route.c index 8fd6801d..4a64d5c1 100644 --- a/src/nxt_http_route.c +++ b/src/nxt_http_route.c @@ -583,6 +583,11 @@ static nxt_conf_map_t nxt_http_route_action_conf[] = { NXT_CONF_MAP_PTR, offsetof(nxt_http_action_conf_t, rewrite) }, + { + nxt_string("response_headers"), + NXT_CONF_MAP_PTR, + offsetof(nxt_http_action_conf_t, set_headers) + }, { nxt_string("pass"), NXT_CONF_MAP_PTR, @@ -671,6 +676,13 @@ nxt_http_action_init(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, } } + if (acf.set_headers != NULL) { + ret = nxt_http_set_headers_init(rtcf, action, &acf); + if (nxt_slow_path(ret != NXT_OK)) { + return ret; + } + } + if (acf.ret != NULL) { return nxt_http_return_init(rtcf, action, &acf); } diff --git a/src/nxt_http_set_headers.c b/src/nxt_http_set_headers.c new file mode 100644 index 00000000..25dd7478 --- /dev/null +++ b/src/nxt_http_set_headers.c @@ -0,0 +1,176 @@ + +/* + * Copyright (C) Zhidao HONG + * Copyright (C) NGINX, Inc. + */ + +#include +#include + + +typedef struct { + nxt_str_t name; + nxt_tstr_t *value; +} nxt_http_header_val_t; + + +nxt_int_t +nxt_http_set_headers_init(nxt_router_conf_t *rtcf, nxt_http_action_t *action, + nxt_http_action_conf_t *acf) + { + uint32_t next; + nxt_str_t str, name; + nxt_array_t *headers; + nxt_conf_value_t *value; + nxt_http_header_val_t *hv; + + headers = nxt_array_create(rtcf->mem_pool, 4, + sizeof(nxt_http_header_val_t)); + if (nxt_slow_path(headers == NULL)) { + return NXT_ERROR; + } + + action->set_headers = headers; + + next = 0; + + for ( ;; ) { + value = nxt_conf_next_object_member(acf->set_headers, &name, &next); + if (value == NULL) { + break; + } + + hv = nxt_array_zero_add(headers); + if (nxt_slow_path(hv == NULL)) { + return NXT_ERROR; + } + + hv->name.length = name.length; + + hv->name.start = nxt_mp_nget(rtcf->mem_pool, name.length); + if (nxt_slow_path(hv->name.start == NULL)) { + return NXT_ERROR; + } + + nxt_memcpy(hv->name.start, name.start, name.length); + + if (nxt_conf_type(value) == NXT_CONF_STRING) { + nxt_conf_get_string(value, &str); + + hv->value = nxt_tstr_compile(rtcf->tstr_state, &str, 0); + if (nxt_slow_path(hv->value == NULL)) { + return NXT_ERROR; + } + } + } + + return NXT_OK; +} + + +static nxt_http_field_t * +nxt_http_resp_header_find(nxt_http_request_t *r, u_char *name, size_t length) +{ + nxt_http_field_t *f; + + nxt_list_each(f, r->resp.fields) { + + if (f->skip) { + continue; + } + + if (length == f->name_length + && nxt_memcasecmp(name, f->name, f->name_length) == 0) + { + return f; + } + + } nxt_list_loop; + + return NULL; +} + + +nxt_int_t +nxt_http_set_headers(nxt_http_request_t *r) +{ + nxt_int_t ret; + nxt_uint_t i, n; + nxt_str_t *value; + nxt_http_field_t *f; + nxt_router_conf_t *rtcf; + nxt_http_action_t *action; + nxt_http_header_val_t *hv, *header; + + action = r->action; + + if (action == NULL || action->set_headers == NULL) { + return NXT_OK; + } + + if ((r->status < NXT_HTTP_OK || r->status >= NXT_HTTP_BAD_REQUEST)) { + return NXT_OK; + } + + rtcf = r->conf->socket_conf->router_conf; + + header = action->set_headers->elts; + n = action->set_headers->nelts; + + value = nxt_mp_zalloc(r->mem_pool, sizeof(nxt_str_t) * n); + if (nxt_slow_path(value == NULL)) { + return NXT_ERROR; + } + + for (i = 0; i < n; i++) { + hv = &header[i]; + + if (hv->value == NULL) { + continue; + } + + if (nxt_tstr_is_const(hv->value)) { + nxt_tstr_str(hv->value, &value[i]); + + } else { + ret = nxt_tstr_query_init(&r->tstr_query, rtcf->tstr_state, + &r->tstr_cache, r, r->mem_pool); + if (nxt_slow_path(ret != NXT_OK)) { + return NXT_ERROR; + } + + nxt_tstr_query(&r->task, r->tstr_query, hv->value, &value[i]); + + if (nxt_slow_path(nxt_tstr_query_failed(r->tstr_query))) { + return NXT_ERROR; + } + } + } + + for (i = 0; i < n; i++) { + hv = &header[i]; + + f = nxt_http_resp_header_find(r, hv->name.start, hv->name.length); + + if (value[i].start != NULL) { + + if (f == NULL) { + f = nxt_list_zero_add(r->resp.fields); + if (nxt_slow_path(f == NULL)) { + return NXT_ERROR; + } + + f->name = hv->name.start; + f->name_length = hv->name.length; + } + + f->value = value[i].start; + f->value_length = value[i].length; + + } else if (f != NULL) { + f->skip = 1; + } + } + + return NXT_OK; +} diff --git a/src/nxt_http_variables.c b/src/nxt_http_variables.c index 04e24a4b..46594a6b 100644 --- a/src/nxt_http_variables.c +++ b/src/nxt_http_variables.c @@ -137,7 +137,7 @@ nxt_http_unknown_var_ref(nxt_tstr_state_t *state, nxt_var_ref_t *ref, if (nxt_str_start(name, "response_header_", 16)) { ref->handler = nxt_http_var_response_header; - ref->cacheable = 1; + ref->cacheable = 0; str.start = name->start + 16; str.length = name->length - 16; -- cgit From 46573e6993552e0ecbcbcd6f4e191b4b2e3f5dc9 Mon Sep 17 00:00:00 2001 From: Andrew Clayton Date: Tue, 8 Aug 2023 12:48:41 +0100 Subject: Index initialise the nxt_app_msg_prefix array. This makes it much more clear what's what. This is in preparation for adding WebAssembly language module support. Reviewed-by: Alejandro Colomar Signed-off-by: Andrew Clayton --- src/nxt_router.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/nxt_router.c b/src/nxt_router.c index d089cfb8..5d9c60a1 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -274,12 +274,12 @@ static const nxt_str_t http_prefix = nxt_string("HTTP_"); static const nxt_str_t empty_prefix = nxt_string(""); static const nxt_str_t *nxt_app_msg_prefix[] = { - &empty_prefix, - &empty_prefix, - &http_prefix, - &http_prefix, - &http_prefix, - &empty_prefix, + [NXT_APP_EXTERNAL] = &empty_prefix, + [NXT_APP_PYTHON] = &empty_prefix, + [NXT_APP_PHP] = &http_prefix, + [NXT_APP_PERL] = &http_prefix, + [NXT_APP_RUBY] = &http_prefix, + [NXT_APP_JAVA] = &empty_prefix, }; -- cgit From 52b334acd1a5678ee111d6db73dc02675c21a277 Mon Sep 17 00:00:00 2001 From: Andrew Clayton Date: Wed, 9 Aug 2023 17:09:30 +0100 Subject: Wasm: Register a new WebAssembly language module type. This is the first patch in adding WebAssembly language module support. This just adds a new NXT_APP_WASM type, required by subsequent commits. Reviewed-by: Alejandro Colomar Signed-off-by: Andrew Clayton --- src/nxt_application.h | 1 + src/nxt_router.c | 1 + 2 files changed, 2 insertions(+) (limited to 'src') diff --git a/src/nxt_application.h b/src/nxt_application.h index 2675e6a0..4efe3750 100644 --- a/src/nxt_application.h +++ b/src/nxt_application.h @@ -21,6 +21,7 @@ typedef enum { NXT_APP_PERL, NXT_APP_RUBY, NXT_APP_JAVA, + NXT_APP_WASM, NXT_APP_UNKNOWN, } nxt_app_type_t; diff --git a/src/nxt_router.c b/src/nxt_router.c index 5d9c60a1..4e3cb303 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -280,6 +280,7 @@ static const nxt_str_t *nxt_app_msg_prefix[] = { [NXT_APP_PERL] = &http_prefix, [NXT_APP_RUBY] = &http_prefix, [NXT_APP_JAVA] = &empty_prefix, + [NXT_APP_WASM] = &empty_prefix, }; -- cgit From 0c444397366aa07e23573a03e733a0552187eac4 Mon Sep 17 00:00:00 2001 From: Andrew Clayton Date: Tue, 8 Aug 2023 23:41:52 +0100 Subject: Wasm: Add core configuration data structure. This is required to actually _build_ the Wasm language module. The nxt_wasm_app_conf_t structure consists of the modules name, e.g wasm, then the three required function handlers followed by the five optional function handlers. See the next commit for details of these function handlers. We also need to include the u.wasm union entry that provides access to the above structure. The bulk of the configuration infrastructure will be added in a subsequent commit. Reviewed-by: Alejandro Colomar Signed-off-by: Andrew Clayton --- src/nxt_application.h | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'src') diff --git a/src/nxt_application.h b/src/nxt_application.h index 4efe3750..ff3ec562 100644 --- a/src/nxt_application.h +++ b/src/nxt_application.h @@ -87,6 +87,21 @@ typedef struct { } nxt_java_app_conf_t; +typedef struct { + const char *module; + + const char *request_handler; + const char *malloc_handler; + const char *free_handler; + + const char *module_init_handler; + const char *module_end_handler; + const char *request_init_handler; + const char *request_end_handler; + const char *response_end_handler; +} nxt_wasm_app_conf_t; + + struct nxt_common_app_conf_s { nxt_str_t name; nxt_str_t type; @@ -115,6 +130,7 @@ struct nxt_common_app_conf_s { nxt_perl_app_conf_t perl; nxt_ruby_app_conf_t ruby; nxt_java_app_conf_t java; + nxt_wasm_app_conf_t wasm; } u; nxt_conf_value_t *self; -- cgit From 6a211e2b7468a2524715b7f00a24aa460481bea7 Mon Sep 17 00:00:00 2001 From: Andrew Clayton Date: Tue, 8 Aug 2023 14:29:54 +0100 Subject: Wasm: Add the core of initial WebAssembly language module support. This adds the core of runtime WebAssembly[0] support. Future commits will enable this in the Unit core and expose the configuration. This introduces a new src/wasm directory for storing this source. We are initially using Wasmtime[0] as the WebAssembly runtime, however this has been designed with the ability to use different runtimes in mind. src/wasm/nxt_wasm.[ch] is the main interface to Unit. src/wasm/nxt_rt_wasmtime.c is the Wasmtime runtime support. This is nicely insulated from any knowledge of internal Unit workings. Wasmtime is what loads and runs the Wasm modules. The Wasm modules can export functions Wasmtime can call and Wasmtime can export functions that the module can call. We make use of both. The terminology used is that function exports are what the Wasm module exports and function imports are what the Wasm runtime exports to the module. We currently have four function imports (functions exported by the runtime to be called by the Wasm module). 1) nxt_wasm_get_init_mem_size This allows Wasm modules to get the size of the initially allocated shared memory. This is the size allocated at Unit startup and what the Wasm modules can assume they have access to (in reality this shared memory will likely be larger). The amount of memory allocated at startup is NXT_WASM_MEM_SIZE which as of this commit is 32MiB. We do actually allocate NXT_WASM_MEM_SIZE + NXT_WASM_PAGE_SIZE at startup which is an extra 64KiB (the smallest allocation unit), this is to allow room for the response structure and so module developers can just assume they have the full 32MiB for their actual response. 2) nxt_wasm_send_headers This allows WASM modules to send their headers. 3) nxt_wasm_send_response This allows WASM modules to send their response. 4) nxt_wasm_response_end This allows WASM modules to inform Unit they have finished sending their response. This calls nxt_unit_request_done() Then there are currently up to eight functions that a module can export. Three of which are required. These function can be named anything. I'll use the Unit configuration names to refer to them 1) request_handler The main driving function. This may be called multiple times for a single HTTP request if the request is larger than the shared memory. 2) malloc_handler Used to allocate a chunk of memory at language module startup. This memory is allocated from the WASM modules address space and is what is sued for communicating between the WASM module (the guest) and Unit (the host). 3) free_handler Used to free the memory from above at language module shutdown. Then there are the following optional handlers 1) module_init_handler If set, called at language module startup. 2) module_end_handler If set, called at language module shutdown. 3) request_init_handler If set, called at the start of request. Called only once per HTTP request. 4) request_end_handler If set, called once all of a request has been sent to the WASM module. 5) response_end_handler If set, called at the end of a request, once the WASM module has sent all its headers and data. 32bits We currently support 32bit WASM modules, I.e wasm32-wasi. Newer version of clang, 13+[2], do seem to have support for wasm64 as a target (which uses a LP64 model). However it's not entirely clear if the WASI SDK fully supports[3] this and by extension WASI libc/wasi-sysroot. 64bit support is something than can be explored more thoroughly in the future. As such in structures that are used to communicate between the host and guest we use 32bit ints. Even when a single byte might be enough. This is to avoid issues with structure layout differences between a 64bit host and 32bit guest (I.e WASM module) and the need for various bits of structure padding depending on host architecture. Instead everything is 4-byte aligned. [0]: [1]: [2]: [3]: Reviewed-by: Alejandro Colomar Signed-off-by: Andrew Clayton --- src/wasm/nxt_rt_wasmtime.c | 407 +++++++++++++++++++++++++++++++++++++++++++++ src/wasm/nxt_wasm.c | 258 ++++++++++++++++++++++++++++ src/wasm/nxt_wasm.h | 136 +++++++++++++++ 3 files changed, 801 insertions(+) create mode 100644 src/wasm/nxt_rt_wasmtime.c create mode 100644 src/wasm/nxt_wasm.c create mode 100644 src/wasm/nxt_wasm.h (limited to 'src') diff --git a/src/wasm/nxt_rt_wasmtime.c b/src/wasm/nxt_rt_wasmtime.c new file mode 100644 index 00000000..07a6165a --- /dev/null +++ b/src/wasm/nxt_rt_wasmtime.c @@ -0,0 +1,407 @@ +/* + * Copyright (C) Andrew Clayton + * Copyright (C) F5, Inc. + */ + +#include +#include +#include + +#include +#include +#include + +#include "nxt_wasm.h" + + +typedef struct nxt_wasmtime_ctx_s nxt_wasmtime_ctx_t; + +struct nxt_wasmtime_ctx_s { + wasm_engine_t *engine; + wasmtime_store_t *store; + wasmtime_memory_t memory; + wasmtime_module_t *module; + wasmtime_linker_t *linker; + wasmtime_context_t *ctx; +}; + +static nxt_wasmtime_ctx_t nxt_wasmtime_ctx; + + +static void +nxt_wasmtime_err_msg(wasmtime_error_t *error, wasm_trap_t *trap, + const char *fmt, ...) +{ + va_list args; + wasm_byte_vec_t error_message; + + fprintf(stderr, "WASMTIME ERROR: "); + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + fprintf(stderr, "\n"); + + if (error == NULL && trap == NULL) { + return; + } + + if (error != NULL) { + wasmtime_error_message(error, &error_message); + wasmtime_error_delete(error); + } else { + wasm_trap_message(trap, &error_message); + wasm_trap_delete(trap); + } + fprintf(stderr, "%.*s\n", (int)error_message.size, error_message.data); + + wasm_byte_vec_delete(&error_message); +} + + +static wasm_trap_t * +nxt_wasm_get_init_mem_size(void *env, wasmtime_caller_t *caller, + const wasmtime_val_t *args, size_t nargs, + wasmtime_val_t *results, size_t nresults) +{ + results[0].of.i32 = NXT_WASM_MEM_SIZE; + + return NULL; +} + + +static wasm_trap_t * +nxt_wasm_response_end(void *env, wasmtime_caller_t *caller, + const wasmtime_val_t *args, size_t nargs, + wasmtime_val_t *results, size_t nresults) +{ + nxt_wasm_do_response_end(env); + + return NULL; +} + + +static wasm_trap_t * +nxt_wasm_send_response(void *env, wasmtime_caller_t *caller, + const wasmtime_val_t *args, size_t nargs, + wasmtime_val_t *results, size_t nresults) +{ + nxt_wasm_do_send_response(env, args[0].of.i32); + + return NULL; +} + + +static wasm_trap_t * +nxt_wasm_send_headers(void *env, wasmtime_caller_t *caller, + const wasmtime_val_t *args, size_t nargs, + wasmtime_val_t *results, size_t nresults) +{ + nxt_wasm_do_send_headers(env, args[0].of.i32); + + return NULL; +} + + +static void +nxt_wasmtime_execute_hook(const nxt_wasm_ctx_t *ctx, nxt_wasm_fh_t hook) +{ + const char *name = ctx->fh[hook].func_name; + wasm_trap_t *trap = NULL; + wasmtime_error_t *error; + nxt_wasmtime_ctx_t *rt_ctx = &nxt_wasmtime_ctx; + const nxt_wasm_func_t *func = &ctx->fh[hook].func; + + if (name == NULL) { + return; + } + + error = wasmtime_func_call(rt_ctx->ctx, func, NULL, 0, NULL, 0, &trap); + if (error != NULL || trap != NULL) { + nxt_wasmtime_err_msg(error, trap, "failed to call hook function [%s]", + name); + } +} + + +static void +nxt_wasmtime_execute_request(const nxt_wasm_ctx_t *ctx) +{ + int i = 0; + wasm_trap_t *trap = NULL; + wasmtime_val_t args[1] = { }; + wasmtime_val_t results[1] = { }; + wasmtime_error_t *error; + nxt_wasmtime_ctx_t *rt_ctx = &nxt_wasmtime_ctx; + const nxt_wasm_func_t *func = &ctx->fh[NXT_WASM_FH_REQUEST].func; + + args[i].kind = WASMTIME_I32; + args[i++].of.i32 = ctx->baddr_off; + + error = wasmtime_func_call(rt_ctx->ctx, func, args, i, results, 1, &trap); + if (error != NULL || trap != NULL) { + nxt_wasmtime_err_msg(error, trap, + "failed to call function [->wasm_request_handler]" + ); + } +} + + +static void +nxt_wasmtime_set_function_imports(nxt_wasm_ctx_t *ctx) +{ + nxt_wasmtime_ctx_t *rt_ctx = &nxt_wasmtime_ctx; + + static const struct { + const char *func_name; + + wasmtime_func_callback_t func; + wasm_valkind_t params[1]; + wasm_valkind_t results[1]; + + enum { + NXT_WASM_FT_0_0, + NXT_WASM_FT_1_0, + NXT_WASM_FT_0_1, + } ft; + } import_functions[] = { + { + .func_name = "nxt_wasm_get_init_mem_size", + .func = nxt_wasm_get_init_mem_size, + .results = { WASM_I32 }, + .ft = NXT_WASM_FT_0_1 + }, { + .func_name = "nxt_wasm_response_end", + .func = nxt_wasm_response_end, + .ft = NXT_WASM_FT_0_0 + }, { + .func_name = "nxt_wasm_send_response", + .func = nxt_wasm_send_response, + .params = { WASM_I32 }, + .ft = NXT_WASM_FT_1_0 + }, { + .func_name = "nxt_wasm_send_headers", + .func = nxt_wasm_send_headers, + .params = { WASM_I32 }, + .ft = NXT_WASM_FT_1_0 + }, + + { } + }, *imf; + + for (imf = import_functions; imf->func_name != NULL; imf++) { + wasm_functype_t *func_ty; + + switch (imf->ft) { + case NXT_WASM_FT_0_0: + func_ty = wasm_functype_new_0_0(); + break; + case NXT_WASM_FT_1_0: + func_ty = wasm_functype_new_1_0(wasm_valtype_new(imf->params[0])); + break; + case NXT_WASM_FT_0_1: + func_ty = wasm_functype_new_0_1(wasm_valtype_new(imf->results[0])); + break; + default: + /* Stop GCC complaining about func_ty being used uninitialised */ + func_ty = NULL; + } + + wasmtime_linker_define_func(rt_ctx->linker, "env", 3, + imf->func_name, strlen(imf->func_name), + func_ty, imf->func, ctx, NULL); + wasm_functype_delete(func_ty); + } +} + + +static int +nxt_wasmtime_get_function_exports(nxt_wasm_ctx_t *ctx) +{ + int i; + nxt_wasmtime_ctx_t *rt_ctx = &nxt_wasmtime_ctx; + + for (i = 0; i < NXT_WASM_FH_NR; i++) { + bool ok; + wasmtime_extern_t item; + + if (ctx->fh[i].func_name == NULL) { + continue; + } + + ok = wasmtime_linker_get(rt_ctx->linker, rt_ctx->ctx, "", 0, + ctx->fh[i].func_name, + strlen(ctx->fh[i].func_name), &item); + if (!ok) { + nxt_wasmtime_err_msg(NULL, NULL, + "couldn't get (%s) export from module", + ctx->fh[i].func_name); + return -1; + } + ctx->fh[i].func = item.of.func; + } + + return 0; +} + + +static int +nxt_wasmtime_wasi_init(const nxt_wasm_ctx_t *ctx) +{ + wasi_config_t *wasi_config; + wasmtime_error_t *error; + nxt_wasmtime_ctx_t *rt_ctx = &nxt_wasmtime_ctx; + + wasi_config = wasi_config_new(); + + wasi_config_inherit_env(wasi_config); + wasi_config_inherit_stdin(wasi_config); + wasi_config_inherit_stdout(wasi_config); + wasi_config_inherit_stderr(wasi_config); + + error = wasmtime_context_set_wasi(rt_ctx->ctx, wasi_config); + if (error != NULL) { + nxt_wasmtime_err_msg(error, NULL, "failed to instantiate WASI"); + return -1; + } + + return 0; +} + + +static int +nxt_wasmtime_init_memory(nxt_wasm_ctx_t *ctx) +{ + int i = 0; + bool ok; + wasm_trap_t *trap = NULL; + wasmtime_val_t args[1] = { }; + wasmtime_val_t results[1] = { }; + wasmtime_error_t *error; + wasmtime_extern_t item; + nxt_wasmtime_ctx_t *rt_ctx = &nxt_wasmtime_ctx; + const nxt_wasm_func_t *func = &ctx->fh[NXT_WASM_FH_MALLOC].func; + + args[i].kind = WASMTIME_I32; + args[i++].of.i32 = NXT_WASM_MEM_SIZE + NXT_WASM_PAGE_SIZE; + + error = wasmtime_func_call(rt_ctx->ctx, func, args, i, results, 1, &trap); + if (error != NULL || trap != NULL) { + nxt_wasmtime_err_msg(error, trap, + "failed to call function [->wasm_malloc_handler]" + ); + return -1; + } + + ok = wasmtime_linker_get(rt_ctx->linker, rt_ctx->ctx, "", 0, "memory", + strlen("memory"), &item); + if (!ok) { + nxt_wasmtime_err_msg(NULL, NULL, "couldn't get 'memory' from module\n"); + return -1; + } + rt_ctx->memory = item.of.memory; + + ctx->baddr_off = results[0].of.i32; + ctx->baddr = wasmtime_memory_data(rt_ctx->ctx, &rt_ctx->memory); + + ctx->baddr += ctx->baddr_off; + + return 0; +} + + +static int +nxt_wasmtime_init(nxt_wasm_ctx_t *ctx) +{ + int err; + FILE *fp; + size_t file_size; + wasm_byte_vec_t wasm; + wasmtime_error_t *error; + nxt_wasmtime_ctx_t *rt_ctx = &nxt_wasmtime_ctx; + + rt_ctx->engine = wasm_engine_new(); + rt_ctx->store = wasmtime_store_new(rt_ctx->engine, NULL, NULL); + rt_ctx->ctx = wasmtime_store_context(rt_ctx->store); + + rt_ctx->linker = wasmtime_linker_new(rt_ctx->engine); + error = wasmtime_linker_define_wasi(rt_ctx->linker); + if (error != NULL) { + nxt_wasmtime_err_msg(error, NULL, "failed to link wasi"); + return -1; + } + + fp = fopen(ctx->module_path, "r"); + if (!fp) { + nxt_wasmtime_err_msg(NULL, NULL, + "error opening file (%s)", ctx->module_path); + return -1; + } + fseek(fp, 0L, SEEK_END); + file_size = ftell(fp); + wasm_byte_vec_new_uninitialized(&wasm, file_size); + fseek(fp, 0L, SEEK_SET); + if (fread(wasm.data, file_size, 1, fp) != 1) { + nxt_wasmtime_err_msg(NULL, NULL, "error loading module"); + fclose(fp); + return -1; + } + fclose(fp); + + error = wasmtime_module_new(rt_ctx->engine, (uint8_t *)wasm.data, wasm.size, + &rt_ctx->module); + if (!rt_ctx->module) { + nxt_wasmtime_err_msg(error, NULL, "failed to compile module"); + return -1; + } + wasm_byte_vec_delete(&wasm); + + nxt_wasmtime_set_function_imports(ctx); + + nxt_wasmtime_wasi_init(ctx); + + error = wasmtime_linker_module(rt_ctx->linker, rt_ctx->ctx, "", 0, + rt_ctx->module); + if (error != NULL) { + nxt_wasmtime_err_msg(error, NULL, "failed to instantiate"); + return -1; + } + + err = nxt_wasmtime_get_function_exports(ctx); + if (err) { + return -1; + } + + err = nxt_wasmtime_init_memory(ctx); + if (err) { + return -1; + } + + return 0; +} + + +static void +nxt_wasmtime_destroy(const nxt_wasm_ctx_t *ctx) +{ + int i = 0; + wasmtime_val_t args[1] = { }; + nxt_wasmtime_ctx_t *rt_ctx = &nxt_wasmtime_ctx; + const nxt_wasm_func_t *func = &ctx->fh[NXT_WASM_FH_FREE].func; + + args[i].kind = WASMTIME_I32; + args[i++].of.i32 = ctx->baddr_off; + + wasmtime_func_call(rt_ctx->ctx, func, args, i, NULL, 0, NULL); + + wasmtime_module_delete(rt_ctx->module); + wasmtime_store_delete(rt_ctx->store); + wasm_engine_delete(rt_ctx->engine); +} + + +const nxt_wasm_operations_t nxt_wasm_ops = { + .init = nxt_wasmtime_init, + .destroy = nxt_wasmtime_destroy, + .exec_request = nxt_wasmtime_execute_request, + .exec_hook = nxt_wasmtime_execute_hook, +}; diff --git a/src/wasm/nxt_wasm.c b/src/wasm/nxt_wasm.c new file mode 100644 index 00000000..d96668ad --- /dev/null +++ b/src/wasm/nxt_wasm.c @@ -0,0 +1,258 @@ +/* + * Copyright (C) Andrew Clayton + * Copyright (C) F5, Inc. + */ + +#include +#include +#include +#include + +#include "nxt_wasm.h" + + +#define NXT_WASM_VERSION "0.1" + +#define NXT_WASM_DO_HOOK(hook) nxt_wops->exec_hook(&nxt_wasm_ctx, hook); + + +static uint32_t compat[] = { + NXT_VERNUM, NXT_DEBUG, +}; + +static nxt_wasm_ctx_t nxt_wasm_ctx; + +static const nxt_wasm_operations_t *nxt_wops; + + +void +nxt_wasm_do_response_end(nxt_wasm_ctx_t *ctx) +{ + nxt_unit_request_done(ctx->req, NXT_UNIT_OK); + + NXT_WASM_DO_HOOK(NXT_WASM_FH_RESPONSE_END); +} + + +void +nxt_wasm_do_send_headers(nxt_wasm_ctx_t *ctx, uint32_t offset) +{ + size_t fields_len; + unsigned int i; + nxt_wasm_response_fields_t *rh; + + rh = (nxt_wasm_response_fields_t *)(ctx->baddr + offset); + + fields_len = 0; + for (i = 0; i < rh->nfields; i++) { + fields_len += rh->fields[i].name_len + rh->fields[i].value_len; + } + + nxt_unit_response_init(ctx->req, 200, rh->nfields, fields_len); + + for (i = 0; i < rh->nfields; i++) { + const char *name; + const char *val; + + name = (const char *)rh + rh->fields[i].name_off; + val = (const char *)rh + rh->fields[i].value_off; + + nxt_unit_response_add_field(ctx->req, name, rh->fields[i].name_len, + val, rh->fields[i].value_len); + } + + nxt_unit_response_send(ctx->req); +} + + +void +nxt_wasm_do_send_response(nxt_wasm_ctx_t *ctx, uint32_t offset) +{ + nxt_wasm_response_t *resp; + nxt_unit_request_info_t *req = ctx->req; + + if (!nxt_unit_response_is_init(req)) { + nxt_unit_response_init(req, 200, 0, 0); + } + + resp = (nxt_wasm_response_t *)(nxt_wasm_ctx.baddr + offset); + + nxt_unit_response_write(req, (const char *)resp->data, resp->size); +} + + +static void +nxt_wasm_request_handler(nxt_unit_request_info_t *req) +{ + size_t offset, read_bytes, content_sent, content_len; + ssize_t bytes_read; + nxt_unit_field_t *sf, *sf_end; + nxt_unit_request_t *r; + nxt_wasm_request_t *wr; + nxt_wasm_http_field_t *df; + + NXT_WASM_DO_HOOK(NXT_WASM_FH_REQUEST_INIT); + + wr = (nxt_wasm_request_t *)nxt_wasm_ctx.baddr; + +#define SET_REQ_MEMBER(dmember, smember) \ + do { \ + const char *str = nxt_unit_sptr_get(&r->smember); \ + wr->dmember##_off = offset; \ + wr->dmember##_len = strlen(str); \ + memcpy((uint8_t *)wr + offset, str, wr->dmember##_len + 1); \ + offset += wr->dmember##_len + 1; \ + } while (0) + + r = req->request; + offset = sizeof(nxt_wasm_request_t) + + (r->fields_count * sizeof(nxt_wasm_http_field_t)); + + SET_REQ_MEMBER(path, path); + SET_REQ_MEMBER(method, method); + SET_REQ_MEMBER(version, version); + SET_REQ_MEMBER(query, query); + SET_REQ_MEMBER(remote, remote); + SET_REQ_MEMBER(local_addr, local_addr); + SET_REQ_MEMBER(local_port, local_port); + SET_REQ_MEMBER(server_name, server_name); +#undef SET_REQ_MEMBER + + df = wr->fields; + sf_end = r->fields + r->fields_count; + for (sf = r->fields; sf < sf_end; sf++) { + const char *name = nxt_unit_sptr_get(&sf->name); + const char *value = nxt_unit_sptr_get(&sf->value); + + df->name_off = offset; + df->name_len = strlen(name); + memcpy((uint8_t *)wr + offset, name, df->name_len + 1); + offset += df->name_len + 1; + + df->value_off = offset; + df->value_len = strlen(value); + memcpy((uint8_t *)wr + offset, value, df->value_len + 1); + offset += df->value_len + 1; + + df++; + } + + wr->tls = r->tls; + wr->nfields = r->fields_count; + wr->content_off = offset; + wr->content_len = content_len = r->content_length; + + read_bytes = nxt_min(wr->content_len, NXT_WASM_MEM_SIZE - offset); + + bytes_read = nxt_unit_request_read(req, (uint8_t *)wr + offset, read_bytes); + wr->content_sent = wr->total_content_sent = content_sent = bytes_read; + + wr->request_size = offset + bytes_read; + + nxt_wasm_ctx.req = req; + nxt_wops->exec_request(&nxt_wasm_ctx); + + if (content_len == content_sent) { + goto request_done; + } + + wr->nfields = 0; + wr->content_off = offset = sizeof(nxt_wasm_request_t); + do { + read_bytes = nxt_min(content_len - content_sent, + NXT_WASM_MEM_SIZE - offset); + bytes_read = nxt_unit_request_read(req, (uint8_t *)wr + offset, + read_bytes); + + content_sent += bytes_read; + wr->request_size = wr->content_sent = bytes_read; + wr->total_content_sent = content_sent; + + nxt_wops->exec_request(&nxt_wasm_ctx); + } while (content_sent < content_len); + +request_done: + NXT_WASM_DO_HOOK(NXT_WASM_FH_REQUEST_END); +} + + +static nxt_int_t +nxt_wasm_start(nxt_task_t *task, nxt_process_data_t *data) +{ + nxt_int_t ret; + nxt_unit_ctx_t *unit_ctx; + nxt_unit_init_t wasm_init; + nxt_common_app_conf_t *conf; + + conf = data->app; + + ret = nxt_unit_default_init(task, &wasm_init, conf); + if (nxt_slow_path(ret != NXT_OK)) { + nxt_alert(task, "nxt_unit_default_init() failed"); + return ret; + } + + wasm_init.callbacks.request_handler = nxt_wasm_request_handler; + + unit_ctx = nxt_unit_init(&wasm_init); + if (nxt_slow_path(unit_ctx == NULL)) { + return NXT_ERROR; + } + + NXT_WASM_DO_HOOK(NXT_WASM_FH_MODULE_INIT); + nxt_unit_run(unit_ctx); + nxt_unit_done(unit_ctx); + NXT_WASM_DO_HOOK(NXT_WASM_FH_MODULE_END); + + nxt_wops->destroy(&nxt_wasm_ctx); + + exit(EXIT_SUCCESS); +} + + +static nxt_int_t +nxt_wasm_setup(nxt_task_t *task, nxt_process_t *process, + nxt_common_app_conf_t *conf) +{ + int err; + nxt_wasm_app_conf_t *c; + nxt_wasm_func_handler_t *fh; + + c = &conf->u.wasm; + + nxt_wops = &nxt_wasm_ops; + + nxt_wasm_ctx.module_path = c->module; + + fh = nxt_wasm_ctx.fh; + + fh[NXT_WASM_FH_REQUEST].func_name = c->request_handler; + fh[NXT_WASM_FH_MALLOC].func_name = c->malloc_handler; + fh[NXT_WASM_FH_FREE].func_name = c->free_handler; + + /* Optional function handlers (hooks) */ + fh[NXT_WASM_FH_MODULE_INIT].func_name = c->module_init_handler; + fh[NXT_WASM_FH_MODULE_END].func_name = c->module_end_handler; + fh[NXT_WASM_FH_REQUEST_INIT].func_name = c->request_init_handler; + fh[NXT_WASM_FH_REQUEST_END].func_name = c->request_end_handler; + fh[NXT_WASM_FH_RESPONSE_END].func_name = c->response_end_handler; + + err = nxt_wops->init(&nxt_wasm_ctx); + if (err) { + exit(EXIT_FAILURE); + } + + return NXT_OK; +} + + +NXT_EXPORT nxt_app_module_t nxt_app_module = { + .compat_length = sizeof(compat), + .compat = compat, + .type = nxt_string("wasm"), + .version = NXT_WASM_VERSION, + .mounts = NULL, + .nmounts = 0, + .setup = nxt_wasm_setup, + .start = nxt_wasm_start, +}; diff --git a/src/wasm/nxt_wasm.h b/src/wasm/nxt_wasm.h new file mode 100644 index 00000000..9e18f931 --- /dev/null +++ b/src/wasm/nxt_wasm.h @@ -0,0 +1,136 @@ +/* + * Copyright (C) Andrew Clayton + * Copyright (C) F5, Inc. + */ + +#ifndef _NXT_WASM_H_INCLUDED_ +#define _NXT_WASM_H_INCLUDED_ + +#include +#include + +#include + +#include +#if defined(NXT_HAVE_WASM_WASMTIME) +#include +#endif + + +#define NXT_WASM_PAGE_SIZE (64 * 1024) +#define NXT_WASM_MEM_SIZE (32UL * 1024 * 1024) + +#if defined(NXT_HAVE_WASM_WASMTIME) +typedef wasmtime_func_t nxt_wasm_func_t; +#endif + + +typedef struct nxt_wasm_http_field_s nxt_wasm_http_field_t; +typedef struct nxt_wasm_request_s nxt_wasm_request_t; +typedef struct nxt_wasm_response_s nxt_wasm_response_t; +typedef struct nxt_wasm_response_fields_s nxt_wasm_response_fields_t; +typedef enum nxt_wasm_fh_e nxt_wasm_fh_t; +typedef struct nxt_wasm_func_handler_s nxt_wasm_func_handler_t; +typedef struct nxt_wasm_ctx_s nxt_wasm_ctx_t; +typedef struct nxt_wasm_operations_s nxt_wasm_operations_t; + +struct nxt_wasm_http_field_s { + uint32_t name_off; + uint32_t name_len; + uint32_t value_off; + uint32_t value_len; +}; + +struct nxt_wasm_request_s { + uint32_t method_off; + uint32_t method_len; + uint32_t version_off; + uint32_t version_len; + uint32_t path_off; + uint32_t path_len; + uint32_t query_off; + uint32_t query_len; + uint32_t remote_off; + uint32_t remote_len; + uint32_t local_addr_off; + uint32_t local_addr_len; + uint32_t local_port_off; + uint32_t local_port_len; + uint32_t server_name_off; + uint32_t server_name_len; + + uint32_t content_off; + uint32_t content_len; + uint32_t content_sent; + uint32_t total_content_sent; + + uint32_t request_size; + + uint32_t nfields; + + uint32_t tls; + + nxt_wasm_http_field_t fields[]; +}; + +struct nxt_wasm_response_s { + uint32_t size; + + uint8_t data[]; +}; + +struct nxt_wasm_response_fields_s { + uint32_t nfields; + + nxt_wasm_http_field_t fields[]; +}; + +enum nxt_wasm_fh_e { + NXT_WASM_FH_REQUEST = 0, + NXT_WASM_FH_MALLOC, + NXT_WASM_FH_FREE, + + /* Optional handlers */ + NXT_WASM_FH_MODULE_INIT, + NXT_WASM_FH_MODULE_END, + NXT_WASM_FH_REQUEST_INIT, + NXT_WASM_FH_REQUEST_END, + NXT_WASM_FH_RESPONSE_END, + + NXT_WASM_FH_NR +}; + +struct nxt_wasm_func_handler_s { + const char *func_name; + nxt_wasm_func_t func; +}; + +struct nxt_wasm_ctx_s { + const char *module_path; + + nxt_wasm_func_handler_t fh[NXT_WASM_FH_NR]; + + nxt_unit_request_info_t *req; + + uint8_t *baddr; + size_t baddr_off; + + size_t response_off; +}; + +struct nxt_wasm_operations_s { + int (*init)(nxt_wasm_ctx_t *ctx); + void (*destroy)(const nxt_wasm_ctx_t *ctx); + void (*exec_request)(const nxt_wasm_ctx_t *ctx); + void (*exec_hook)(const nxt_wasm_ctx_t *ctx, nxt_wasm_fh_t hook); +}; + +extern const nxt_wasm_operations_t nxt_wasm_ops; + + +/* Exported to the WASM module */ +extern void nxt_wasm_do_response_end(nxt_wasm_ctx_t *ctx); +extern void nxt_wasm_do_send_response(nxt_wasm_ctx_t *ctx, uint32_t offset); +extern void nxt_wasm_do_send_headers(nxt_wasm_ctx_t *ctx, uint32_t offset); + +#endif /* _NXT_WASM_H_INCLUDED_ */ -- cgit From e99854afdf555678df5af149e53659c5b30a2f6d Mon Sep 17 00:00:00 2001 From: Andrew Clayton Date: Wed, 9 Aug 2023 16:59:50 +0100 Subject: Wasm: Wire up Wasm language module support to the config system. This exposes various WebAssembly language module specific options. The application type is "wasm". There is a "module" option that is required, this specifies the full path to the WebAssembly module to be run. This module should be in binary format, i.e a .wasm file. There are also currently eight function handlers that can be specified. Three of them are _required_ 1) request_handler The main driving function. This may be called multiple times for a single HTTP request if the request is larger than the shared memory. 2) malloc_handler Used to allocate a chunk of memory at language module startup. This memory is allocated from the WASM modules address space and is what is sued for communicating between the WASM module (the guest) and Unit (the host). 3) free_handler Used to free the memory from above at language module shutdown. Then there are the following five _optional_ handlers 1) module_init_handler If set, called at language module startup. 2) module_end_handler If set, called at language module shutdown. 3) request_init_handler If set, called at the start of request. Called only once per HTTP request. 4) request_end_handler If set, called once all of a request has been sent to the WASM module. 5) response_end_handler If set, called at the end of a request, once the WASM module has sent all its headers and data. Example config "applications": { "luw-echo-request": { "type": "wasm", "module": "/path/to/unit-wasm/examples/c/luw-echo-request.wasm", "request_handler": "luw_request_handler", "malloc_handler": "luw_malloc_handler", "free_handler": "luw_free_handler", "module_init_handler": "luw_module_init_handler", "module_end_handler": "luw_module_end_handler", } } Reviewed-by: Alejandro Colomar Signed-off-by: Andrew Clayton --- src/nxt_application.c | 3 +++ src/nxt_conf_validation.c | 39 ++++++++++++++++++++++++++++++++++++ src/nxt_main_process.c | 50 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+) (limited to 'src') diff --git a/src/nxt_application.c b/src/nxt_application.c index ffa8eb53..872e387a 100644 --- a/src/nxt_application.c +++ b/src/nxt_application.c @@ -1099,6 +1099,9 @@ nxt_app_parse_type(u_char *p, size_t length) } else if (nxt_str_eq(&str, "java", 4)) { return NXT_APP_JAVA; + + } else if (nxt_str_eq(&str, "wasm", 4)) { + return NXT_APP_WASM; } return NXT_APP_UNKNOWN; diff --git a/src/nxt_conf_validation.c b/src/nxt_conf_validation.c index 09f5598e..0603c98d 100644 --- a/src/nxt_conf_validation.c +++ b/src/nxt_conf_validation.c @@ -1049,6 +1049,44 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_java_members[] = { }; +static nxt_conf_vldt_object_t nxt_conf_vldt_wasm_members[] = { + { + .name = nxt_string("module"), + .type = NXT_CONF_VLDT_STRING, + .flags = NXT_CONF_VLDT_REQUIRED, + }, { + .name = nxt_string("request_handler"), + .type = NXT_CONF_VLDT_STRING, + .flags = NXT_CONF_VLDT_REQUIRED, + },{ + .name = nxt_string("malloc_handler"), + .type = NXT_CONF_VLDT_STRING, + .flags = NXT_CONF_VLDT_REQUIRED, + }, { + .name = nxt_string("free_handler"), + .type = NXT_CONF_VLDT_STRING, + .flags = NXT_CONF_VLDT_REQUIRED, + }, { + .name = nxt_string("module_init_handler"), + .type = NXT_CONF_VLDT_STRING, + }, { + .name = nxt_string("module_end_handler"), + .type = NXT_CONF_VLDT_STRING, + }, { + .name = nxt_string("request_init_handler"), + .type = NXT_CONF_VLDT_STRING, + }, { + .name = nxt_string("request_end_handler"), + .type = NXT_CONF_VLDT_STRING, + }, { + .name = nxt_string("response_end_handler"), + .type = NXT_CONF_VLDT_STRING, + }, + + NXT_CONF_VLDT_NEXT(nxt_conf_vldt_common_members) +}; + + static nxt_conf_vldt_object_t nxt_conf_vldt_common_members[] = { { .name = nxt_string("type"), @@ -2562,6 +2600,7 @@ nxt_conf_vldt_app(nxt_conf_validation_t *vldt, nxt_str_t *name, { nxt_conf_vldt_object, nxt_conf_vldt_perl_members }, { nxt_conf_vldt_object, nxt_conf_vldt_ruby_members }, { nxt_conf_vldt_object, nxt_conf_vldt_java_members }, + { nxt_conf_vldt_object, nxt_conf_vldt_wasm_members }, }; ret = nxt_conf_vldt_type(vldt, name, value, NXT_CONF_VLDT_OBJECT); diff --git a/src/nxt_main_process.c b/src/nxt_main_process.c index 7cba08d4..b7a93341 100644 --- a/src/nxt_main_process.c +++ b/src/nxt_main_process.c @@ -323,6 +323,55 @@ static nxt_conf_map_t nxt_java_app_conf[] = { }; +static nxt_conf_map_t nxt_wasm_app_conf[] = { + { + nxt_string("module"), + NXT_CONF_MAP_CSTRZ, + offsetof(nxt_common_app_conf_t, u.wasm.module), + }, + { + nxt_string("request_handler"), + NXT_CONF_MAP_CSTRZ, + offsetof(nxt_common_app_conf_t, u.wasm.request_handler), + }, + { + nxt_string("malloc_handler"), + NXT_CONF_MAP_CSTRZ, + offsetof(nxt_common_app_conf_t, u.wasm.malloc_handler), + }, + { + nxt_string("free_handler"), + NXT_CONF_MAP_CSTRZ, + offsetof(nxt_common_app_conf_t, u.wasm.free_handler), + }, + { + nxt_string("module_init_handler"), + NXT_CONF_MAP_CSTRZ, + offsetof(nxt_common_app_conf_t, u.wasm.module_init_handler), + }, + { + nxt_string("module_end_handler"), + NXT_CONF_MAP_CSTRZ, + offsetof(nxt_common_app_conf_t, u.wasm.module_end_handler), + }, + { + nxt_string("request_init_handler"), + NXT_CONF_MAP_CSTRZ, + offsetof(nxt_common_app_conf_t, u.wasm.request_init_handler), + }, + { + nxt_string("request_end_handler"), + NXT_CONF_MAP_CSTRZ, + offsetof(nxt_common_app_conf_t, u.wasm.request_end_handler), + }, + { + nxt_string("response_end_handler"), + NXT_CONF_MAP_CSTRZ, + offsetof(nxt_common_app_conf_t, u.wasm.response_end_handler), + }, +}; + + static nxt_conf_app_map_t nxt_app_maps[] = { { nxt_nitems(nxt_external_app_conf), nxt_external_app_conf }, { nxt_nitems(nxt_python_app_conf), nxt_python_app_conf }, @@ -330,6 +379,7 @@ static nxt_conf_app_map_t nxt_app_maps[] = { { nxt_nitems(nxt_perl_app_conf), nxt_perl_app_conf }, { nxt_nitems(nxt_ruby_app_conf), nxt_ruby_app_conf }, { nxt_nitems(nxt_java_app_conf), nxt_java_app_conf }, + { nxt_nitems(nxt_wasm_app_conf), nxt_wasm_app_conf }, }; -- cgit From 47ff51009fa05d83bb67cd5db16829ab4c0081d7 Mon Sep 17 00:00:00 2001 From: Andrew Clayton Date: Wed, 9 Aug 2023 18:22:46 +0100 Subject: Wasm: Add support for directory access. Due to the sandboxed nature of WebAssembly, by default WASM modules don't have any access to the underlying filesystem. There is however a capabilities based mechanism[0] for allowing such access. This adds a config option to the 'wasm' application type; 'access.filesystem' which takes an array of directory paths that are then made available to the WASM module. This access works recursively, i.e everything under a specific path is allowed access to. Example config might look like "access" { "filesystem": [ "/tmp", "/var/tmp" ] } The actual mechanism used allows directories to be mapped differently in the guest. But at the moment we don't support that and just map say /tmp to /tmp. This can be revisited if it's something users clamour for. Network sockets are another resource that may be controlled in this manner, for example there is a wasi_config_preopen_socket() function, however this requires the runtime to open the network socket then effectively pass this through to the guest. This is something that can be revisited in the future if users desire it. [0]: Reviewed-by: Alejandro Colomar Signed-off-by: Andrew Clayton --- src/nxt_application.h | 2 ++ src/nxt_conf_validation.c | 16 ++++++++++++++++ src/nxt_main_process.c | 5 +++++ src/wasm/nxt_rt_wasmtime.c | 5 +++++ src/wasm/nxt_wasm.c | 40 +++++++++++++++++++++++++++++++++++++++- src/wasm/nxt_wasm.h | 2 ++ 6 files changed, 69 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nxt_application.h b/src/nxt_application.h index ff3ec562..64866db6 100644 --- a/src/nxt_application.h +++ b/src/nxt_application.h @@ -99,6 +99,8 @@ typedef struct { const char *request_init_handler; const char *request_end_handler; const char *response_end_handler; + + nxt_conf_value_t *access; } nxt_wasm_app_conf_t; diff --git a/src/nxt_conf_validation.c b/src/nxt_conf_validation.c index 0603c98d..f00b28b8 100644 --- a/src/nxt_conf_validation.c +++ b/src/nxt_conf_validation.c @@ -252,6 +252,7 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_python_target_members[]; static nxt_conf_vldt_object_t nxt_conf_vldt_php_common_members[]; static nxt_conf_vldt_object_t nxt_conf_vldt_php_options_members[]; static nxt_conf_vldt_object_t nxt_conf_vldt_php_target_members[]; +static nxt_conf_vldt_object_t nxt_conf_vldt_wasm_access_members[]; static nxt_conf_vldt_object_t nxt_conf_vldt_common_members[]; static nxt_conf_vldt_object_t nxt_conf_vldt_app_limits_members[]; static nxt_conf_vldt_object_t nxt_conf_vldt_app_processes_members[]; @@ -1081,12 +1082,27 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_wasm_members[] = { }, { .name = nxt_string("response_end_handler"), .type = NXT_CONF_VLDT_STRING, + }, { + .name = nxt_string("access"), + .type = NXT_CONF_VLDT_OBJECT, + .validator = nxt_conf_vldt_object, + .u.members = nxt_conf_vldt_wasm_access_members, }, NXT_CONF_VLDT_NEXT(nxt_conf_vldt_common_members) }; +static nxt_conf_vldt_object_t nxt_conf_vldt_wasm_access_members[] = { + { + .name = nxt_string("filesystem"), + .type = NXT_CONF_VLDT_ARRAY, + }, + + NXT_CONF_VLDT_END +}; + + static nxt_conf_vldt_object_t nxt_conf_vldt_common_members[] = { { .name = nxt_string("type"), diff --git a/src/nxt_main_process.c b/src/nxt_main_process.c index b7a93341..6622f67e 100644 --- a/src/nxt_main_process.c +++ b/src/nxt_main_process.c @@ -369,6 +369,11 @@ static nxt_conf_map_t nxt_wasm_app_conf[] = { NXT_CONF_MAP_CSTRZ, offsetof(nxt_common_app_conf_t, u.wasm.response_end_handler), }, + { + nxt_string("access"), + NXT_CONF_MAP_PTR, + offsetof(nxt_common_app_conf_t, u.wasm.access), + }, }; diff --git a/src/wasm/nxt_rt_wasmtime.c b/src/wasm/nxt_rt_wasmtime.c index 07a6165a..99786b89 100644 --- a/src/wasm/nxt_rt_wasmtime.c +++ b/src/wasm/nxt_rt_wasmtime.c @@ -247,6 +247,7 @@ nxt_wasmtime_get_function_exports(nxt_wasm_ctx_t *ctx) static int nxt_wasmtime_wasi_init(const nxt_wasm_ctx_t *ctx) { + char **dir; wasi_config_t *wasi_config; wasmtime_error_t *error; nxt_wasmtime_ctx_t *rt_ctx = &nxt_wasmtime_ctx; @@ -258,6 +259,10 @@ nxt_wasmtime_wasi_init(const nxt_wasm_ctx_t *ctx) wasi_config_inherit_stdout(wasi_config); wasi_config_inherit_stderr(wasi_config); + for (dir = ctx->dirs; dir != NULL && *dir != NULL; dir++) { + wasi_config_preopen_dir(wasi_config, *dir, *dir); + } + error = wasmtime_context_set_wasi(rt_ctx->ctx, wasi_config); if (error != NULL) { nxt_wasmtime_err_msg(error, NULL, "failed to instantiate WASI"); diff --git a/src/wasm/nxt_wasm.c b/src/wasm/nxt_wasm.c index d96668ad..45a40b4b 100644 --- a/src/wasm/nxt_wasm.c +++ b/src/wasm/nxt_wasm.c @@ -204,6 +204,15 @@ nxt_wasm_start(nxt_task_t *task, nxt_process_data_t *data) nxt_unit_done(unit_ctx); NXT_WASM_DO_HOOK(NXT_WASM_FH_MODULE_END); + if (nxt_wasm_ctx.dirs != NULL) { + char **p; + + for (p = nxt_wasm_ctx.dirs; *p != NULL; p++) { + nxt_free(*p); + } + nxt_free(nxt_wasm_ctx.dirs); + } + nxt_wops->destroy(&nxt_wasm_ctx); exit(EXIT_SUCCESS); @@ -214,9 +223,11 @@ static nxt_int_t nxt_wasm_setup(nxt_task_t *task, nxt_process_t *process, nxt_common_app_conf_t *conf) { - int err; + int n, i, err; + nxt_conf_value_t *dirs = NULL; nxt_wasm_app_conf_t *c; nxt_wasm_func_handler_t *fh; + static nxt_str_t filesystem_str = nxt_string("filesystem"); c = &conf->u.wasm; @@ -237,6 +248,33 @@ nxt_wasm_setup(nxt_task_t *task, nxt_process_t *process, fh[NXT_WASM_FH_REQUEST_END].func_name = c->request_end_handler; fh[NXT_WASM_FH_RESPONSE_END].func_name = c->response_end_handler; + /* Get any directories to pass through to the WASM module */ + if (c->access != NULL) { + dirs = nxt_conf_get_object_member(c->access, &filesystem_str, NULL); + } + + n = (dirs != NULL) ? nxt_conf_object_members_count(dirs) : 0; + if (n == 0) { + goto out_init; + } + + nxt_wasm_ctx.dirs = nxt_zalloc((n + 1) * sizeof(char *)); + if (nxt_slow_path(nxt_wasm_ctx.dirs == NULL)) { + return NXT_ERROR; + } + + for (i = 0; i < n; i++) { + nxt_str_t str; + nxt_conf_value_t *value; + + value = nxt_conf_get_array_element(dirs, i); + nxt_conf_get_string(value, &str); + + nxt_wasm_ctx.dirs[i] = nxt_zalloc(str.length + 1); + memcpy(nxt_wasm_ctx.dirs[i], str.start, str.length); + } + +out_init: err = nxt_wops->init(&nxt_wasm_ctx); if (err) { exit(EXIT_FAILURE); diff --git a/src/wasm/nxt_wasm.h b/src/wasm/nxt_wasm.h index 9e18f931..cb9dbdfe 100644 --- a/src/wasm/nxt_wasm.h +++ b/src/wasm/nxt_wasm.h @@ -110,6 +110,8 @@ struct nxt_wasm_ctx_s { nxt_wasm_func_handler_t fh[NXT_WASM_FH_NR]; + char **dirs; + nxt_unit_request_info_t *req; uint8_t *baddr; -- cgit From ebcc92069d6b4f59a3ab39d66649f5c3c0e6a768 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov Date: Tue, 1 Aug 2023 10:16:17 -0700 Subject: Added unit pkg-config file. --- src/unit.pc.in | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/unit.pc.in (limited to 'src') diff --git a/src/unit.pc.in b/src/unit.pc.in new file mode 100644 index 00000000..4de0556f --- /dev/null +++ b/src/unit.pc.in @@ -0,0 +1,11 @@ +prefix=@PREFIX@ +libdir=@LIBDIR@ +confargs=@CONFARGS@ +modulesdir=@MODULESDIR@ + +Name: unit +Description: library to embed Unit +Version: @VERSION@ +URL: https://unit.nginx.org +Cflags: @CFLAGS@ +Libs: -L${libdir} -lunit @EXTRA_LIBS@ -- cgit