From b2a06204838096f03a41aac9a3a365a04f6b719a Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Mon, 10 Jun 2019 15:53:26 +0300 Subject: Version bump. --- version | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version b/version index 540e9134..271e354a 100644 --- a/version +++ b/version @@ -1,5 +1,5 @@ # Copyright (C) NGINX, Inc. -NXT_VERSION=1.9.0 -NXT_VERNUM=10900 +NXT_VERSION=1.10.0 +NXT_VERNUM=11000 -- cgit From 1f8c395fc0bc9d1a18391a2ae3b0f282b91ff19c Mon Sep 17 00:00:00 2001 From: Igor Sysoev Date: Mon, 10 Jun 2019 18:47:35 +0300 Subject: Cookie-based routing should be case-sensitive. --- src/nxt_http_route.c | 8 +++----- test/test_routing.py | 33 ++++++++++++++++++++++----------- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/nxt_http_route.c b/src/nxt_http_route.c index d6749acb..ade44666 100644 --- a/src/nxt_http_route.c +++ b/src/nxt_http_route.c @@ -475,7 +475,7 @@ nxt_http_route_match_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, if (mtcf.cookies != NULL) { table = nxt_http_route_table_create(task, mp, mtcf.cookies, - NXT_HTTP_ROUTE_COOKIE, 0); + NXT_HTTP_ROUTE_COOKIE, 1); if (table == NULL) { return NULL; } @@ -613,7 +613,7 @@ nxt_http_route_rule_name_create(nxt_task_t *task, nxt_mp_t *mp, c = name->start[i]; *p++ = c; - c = nxt_lowcase(c); + c = case_sensitive ? c : nxt_lowcase(c); hash = nxt_http_field_hash_char(hash, c); } @@ -1452,7 +1452,6 @@ nxt_http_route_cookie(nxt_array_t *array, u_char *name, size_t name_length, for (p = name; p < name + name_length; p++) { c = *p; - c = nxt_lowcase(c); hash = nxt_http_field_hash_char(hash, c); } @@ -1483,8 +1482,7 @@ nxt_http_route_test_cookie(nxt_http_request_t *r, if (rule->u.name.hash == nv->hash && rule->u.name.length == nv->name_length - && nxt_strncasecmp(rule->u.name.start, nv->name, nv->name_length) - == 0) + && nxt_memcmp(rule->u.name.start, nv->name, nv->name_length) == 0) { ret = nxt_http_route_test_rule(r, rule, nv->value, nv->value_length); diff --git a/test/test_routing.py b/test/test_routing.py index ac2e0de8..ef917ea2 100644 --- a/test/test_routing.py +++ b/test/test_routing.py @@ -2423,7 +2423,7 @@ class TestRouting(TestApplicationProto): self.get( headers={ 'Host': 'localhost', - 'Cookie': 'foo=bar', + 'Cookie': 'foO=bar', 'Connection': 'close', }, )['status'], @@ -2434,7 +2434,7 @@ class TestRouting(TestApplicationProto): self.get( headers={ 'Host': 'localhost', - 'Cookie': ['foo=bar', 'blah=blah'], + 'Cookie': ['foO=bar', 'blah=blah'], 'Connection': 'close', }, )['status'], @@ -2445,7 +2445,7 @@ class TestRouting(TestApplicationProto): self.get( headers={ 'Host': 'localhost', - 'Cookie': 'foo=bar; blah=blah', + 'Cookie': 'foO=bar; blah=blah', 'Connection': 'close', }, )['status'], @@ -2461,25 +2461,25 @@ class TestRouting(TestApplicationProto): 'Connection': 'close', }, )['status'], - 200, - 'match cookies case insensitive', + 404, + 'match cookies case sensitive', ) self.assertEqual( self.get( headers={ 'Host': 'localhost', - 'Cookie': 'foo=Bar', + 'Cookie': 'foO=Bar', 'Connection': 'close', }, )['status'], - 200, - 'match cookies case insensitive 2', + 404, + 'match cookies case sensitive 2', ) self.assertEqual( self.get( headers={ 'Host': 'localhost', - 'Cookie': 'foo=bar1', + 'Cookie': 'foO=bar1', 'Connection': 'close', }, )['status'], @@ -2490,13 +2490,24 @@ class TestRouting(TestApplicationProto): self.get( headers={ 'Host': 'localhost', - 'Cookie': 'foo=bar;', + 'Cookie': '1foO=bar;', 'Connection': 'close', }, )['status'], - 200, + 404, 'match cookies exact 2', ) + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'Cookie': 'foO=bar;1', + 'Connection': 'close', + }, + )['status'], + 200, + 'match cookies exact 3', + ) def test_routes_match_cookies_empty(self): self.assertIn( -- cgit From 24400b3a5da2725dcad8d445911b5b4001a966ac Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Mon, 17 Jun 2019 18:16:06 +0300 Subject: Node.js: packaging new nxt_napi.h. File nxt_napi.h (introduced in 53533ba0097c) added into packaged files list. This closes #261 issue on GitHub. --- src/nodejs/unit-http/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/nodejs/unit-http/package.json b/src/nodejs/unit-http/package.json index 13c91018..6a6c00b4 100644 --- a/src/nodejs/unit-http/package.json +++ b/src/nodejs/unit-http/package.json @@ -10,6 +10,7 @@ "unit.cpp", "http.js", "http_server.js", + "nxt_napi.h", "package.json", "socket.js", "binding.gyp", -- cgit From a2c1907fda8d589db72a9ccd7543f307c1eacbaf Mon Sep 17 00:00:00 2001 From: Igor Sysoev Date: Tue, 18 Jun 2019 18:01:03 +0300 Subject: Decreased level of some shutdown() and send() errors. --- src/nxt_socket.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nxt_socket.c b/src/nxt_socket.c index eba53354..95a298d8 100644 --- a/src/nxt_socket.c +++ b/src/nxt_socket.c @@ -281,7 +281,7 @@ nxt_socket_shutdown(nxt_task_t *task, nxt_socket_t s, nxt_uint_t how) switch (err) { case NXT_ENOTCONN: - level = NXT_LOG_INFO; + level = NXT_LOG_DEBUG; break; case NXT_ECONNRESET: @@ -313,7 +313,7 @@ nxt_socket_error_level(nxt_err_t err) case NXT_ENETUNREACH: case NXT_EHOSTDOWN: case NXT_EHOSTUNREACH: - return NXT_LOG_ERR; + return NXT_LOG_INFO; default: return NXT_LOG_ALERT; -- cgit From 3ceec5f4d2fa59adc1c5764ef491a8f848bbf452 Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Fri, 28 Jun 2019 12:19:40 +0300 Subject: Java: adding Content-Type response header for static files. --- src/java/nginx/unit/Context.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/java/nginx/unit/Context.java b/src/java/nginx/unit/Context.java index f6d5e339..3c64e99f 100644 --- a/src/java/nginx/unit/Context.java +++ b/src/java/nginx/unit/Context.java @@ -313,6 +313,7 @@ public class Context implements ServletContext, InitParams } else { response.setContentLengthLong(f.length()); + response.setContentType(getMimeType(f.getName())); InputStream is = new FileInputStream(f); byte[] buffer = new byte[response.getBufferSize()]; -- cgit From 9ea4be7e4ec5d0d24cddb5868aa4920f0298ee67 Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Fri, 28 Jun 2019 12:19:48 +0300 Subject: Fixing allocation alignment for port fragments. All allocated blocks for lvlhash required to be aligned because lower address bits used for various extra information. Using unaligned blocks may cause invalid memory aceess. This was issue found on buildbot running large configuration tests. --- src/nxt_port_socket.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nxt_port_socket.c b/src/nxt_port_socket.c index c9b5105b..fe113a68 100644 --- a/src/nxt_port_socket.c +++ b/src/nxt_port_socket.c @@ -668,7 +668,7 @@ nxt_port_lvlhsh_frag_test(nxt_lvlhsh_query_t *lhq, void *data) static void * nxt_port_lvlhsh_frag_alloc(void *ctx, size_t size) { - return nxt_mp_alloc(ctx, size); + return nxt_mp_align(ctx, size, size); } -- cgit From ce17fef73d1c7e9dacacef305158921b97cdbc96 Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Fri, 28 Jun 2019 12:19:54 +0300 Subject: Fixed application crash handling in router. Before this fix, request in router may hang until timeout expired if application crashed during request processing. --- src/nxt_router.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/nxt_router.c b/src/nxt_router.c index 149a0ff3..018cd4f4 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -928,10 +928,6 @@ nxt_router_remove_pid_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg) nxt_port_remove_pid_handler(task, msg); - if (msg->port_msg.stream == 0) { - return; - } - nxt_queue_each(engine, &nxt_router->engines, nxt_event_engine_t, link0) { nxt_port_post(task, engine->port, nxt_router_app_process_remove_pid, @@ -939,6 +935,10 @@ nxt_router_remove_pid_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg) } nxt_queue_loop; + if (msg->port_msg.stream == 0) { + return; + } + msg->port_msg.type = _NXT_PORT_MSG_RPC_ERROR; nxt_port_rpc_handler(task, msg); -- cgit From b1ee07370796a53b77746dbd11b1b71c73ae940e Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Fri, 28 Jun 2019 12:20:00 +0300 Subject: PHP: removing excessive debug message. --- src/nxt_php_sapi.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/nxt_php_sapi.c b/src/nxt_php_sapi.c index a6ec6c60..df7cf243 100644 --- a/src/nxt_php_sapi.c +++ b/src/nxt_php_sapi.c @@ -783,8 +783,6 @@ nxt_php_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC) h; h = zend_llist_get_next_ex(&sapi_headers->headers, &zpos)) { - nxt_unit_req_debug(req, "header: %.*s", (int) h->header_len, h->header); - colon = memchr(h->header, ':', h->header_len); if (nxt_slow_path(colon == NULL)) { nxt_unit_req_warn(req, "colon not found in header '%.*s'", -- cgit From 29225c4fc62e2cf8b06ad8507fe64c079c69974a Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Tue, 2 Jul 2019 15:36:13 +0300 Subject: Tests: removed misleading comments in test_routing.t. --- test/test_routing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_routing.py b/test/test_routing.py index ef917ea2..7e14fd65 100644 --- a/test/test_routing.py +++ b/test/test_routing.py @@ -1993,12 +1993,12 @@ class TestRouting(TestApplicationProto): self.get(url='/?Foo=bar')['status'], 404, 'match arguments case sensitive', - ) # FAIL + ) self.assertEqual( self.get(url='/?foo=Bar')['status'], 404, 'match arguments case sensitive 2', - ) # FAIL + ) self.assertEqual( self.get(url='/?foo=bar1')['status'], 404, -- cgit From bcb9048c4663a34d9b29a6b5b8e3da14a4321a96 Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Tue, 2 Jul 2019 16:44:08 +0300 Subject: Tests: adjusted inactive interval in Java app for slow hosts. --- test/java/session_inactive/app.java | 8 +++++++- test/test_java_application.py | 16 ++++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/test/java/session_inactive/app.java b/test/java/session_inactive/app.java index f338fc89..618e4d67 100644 --- a/test/java/session_inactive/app.java +++ b/test/java/session_inactive/app.java @@ -17,7 +17,13 @@ public class app extends HttpServlet HttpSession s = request.getSession(); if (s.isNew()) { - s.setMaxInactiveInterval(2); + String interval = request.getHeader("X-Interval"); + + if (interval == null) { + s.setMaxInactiveInterval(0); + } else { + s.setMaxInactiveInterval(Integer.parseInt(interval)); + } } response.addHeader("X-Session-Id", s.getId()); diff --git a/test/test_java_application.py b/test/test_java_application.py index 5d0350fa..54ae3366 100644 --- a/test/test_java_application.py +++ b/test/test_java_application.py @@ -99,12 +99,16 @@ class TestJavaApplication(TestApplicationJava): def test_java_application_session_active(self): self.load('session_inactive') - resp = self.get() + resp = self.get(headers={ + 'X-Interval': '4', + 'Host': 'localhost', + 'Connection': 'close', + }) session_id = resp['headers']['X-Session-Id'] self.assertEqual(resp['status'], 200, 'session init') self.assertEqual( - resp['headers']['X-Session-Interval'], '2', 'session interval' + resp['headers']['X-Session-Interval'], '4', 'session interval' ) self.assertLess( abs( @@ -147,7 +151,7 @@ class TestJavaApplication(TestApplicationJava): resp['headers']['X-Session-Id'], session_id, 'session active 2' ) - time.sleep(1) + time.sleep(2) resp = self.get( headers={ @@ -164,7 +168,11 @@ class TestJavaApplication(TestApplicationJava): def test_java_application_session_inactive(self): self.load('session_inactive') - resp = self.get() + resp = self.get(headers={ + 'X-Interval': '1', + 'Host': 'localhost', + 'Connection': 'close', + }) session_id = resp['headers']['X-Session-Id'] time.sleep(3) -- cgit From e1de5bcfabf468b88b42aa27c60a643e97aacc11 Mon Sep 17 00:00:00 2001 From: Andrei Belov Date: Thu, 4 Jul 2019 14:14:29 +0300 Subject: Packages: log and pid paths made consistent. --- pkg/deb/debian/unit.default | 2 +- pkg/deb/debian/unit.init | 2 +- pkg/deb/debian/unit.service | 2 +- pkg/rpm/Makefile | 4 ++-- pkg/rpm/rpmbuild/SOURCES/unit.service | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pkg/deb/debian/unit.default b/pkg/deb/debian/unit.default index b0f0f72d..8aff8bfe 100644 --- a/pkg/deb/debian/unit.default +++ b/pkg/deb/debian/unit.default @@ -1 +1 @@ -DAEMON_ARGS="--log /var/log/unit.log --pid /run/unit.pid" +DAEMON_ARGS="--log /var/log/unit.log --pid /var/run/unit.pid" diff --git a/pkg/deb/debian/unit.init b/pkg/deb/debian/unit.init index c991b912..2f573f99 100644 --- a/pkg/deb/debian/unit.init +++ b/pkg/deb/debian/unit.init @@ -31,7 +31,7 @@ umask 022 case "$1" in start) log_daemon_msg "Starting $DESC" "$NAME" - if start-stop-daemon --start --quiet --pidfile /run/$NAME.pid \ + if start-stop-daemon --start --quiet --pidfile /var/run/$NAME.pid \ --exec $DAEMON -- $DAEMON_ARGS; then log_end_msg 0 else diff --git a/pkg/deb/debian/unit.service b/pkg/deb/debian/unit.service index 445851a5..d07a06d3 100644 --- a/pkg/deb/debian/unit.service +++ b/pkg/deb/debian/unit.service @@ -5,7 +5,7 @@ After=network-online.target [Service] Type=forking -PIDFile=/run/unit.pid +PIDFile=/var/run/unit.pid EnvironmentFile=-/etc/default/unit ExecStart=/usr/sbin/unitd $DAEMON_ARGS ExecReload= diff --git a/pkg/rpm/Makefile b/pkg/rpm/Makefile index 9e343aa2..4e970e45 100644 --- a/pkg/rpm/Makefile +++ b/pkg/rpm/Makefile @@ -140,8 +140,8 @@ CONFIGURE_ARGS=\ --prefix=/usr \ --state=%{_sharedstatedir}/unit \ --control="unix:/var/run/unit/control.sock" \ - --pid=/var/run/unit.pid \ - --log=/var/log/unit.log \ + --pid=/var/run/unit/unit.pid \ + --log=/var/log/unit/unit.log \ --tests \ --openssl diff --git a/pkg/rpm/rpmbuild/SOURCES/unit.service b/pkg/rpm/rpmbuild/SOURCES/unit.service index f888685f..6df00fbb 100644 --- a/pkg/rpm/rpmbuild/SOURCES/unit.service +++ b/pkg/rpm/rpmbuild/SOURCES/unit.service @@ -7,7 +7,7 @@ # the following: # [Service] -# Environment="UNITD_OPTIONS=--log /var/log/unit/unit.log --pid /run/unit/unit.pid" +# Environment="UNITD_OPTIONS=--log /var/log/unit/unit.log --pid /var/run/unit/unit.pid" [Unit] Description=NGINX Unit @@ -16,7 +16,7 @@ After=network-online.target [Service] Type=simple -Environment="UNITD_OPTIONS=--log /var/log/unit/unit.log --pid /run/unit/unit.pid" +Environment="UNITD_OPTIONS=--log /var/log/unit/unit.log --pid /var/run/unit/unit.pid" ExecStart=/usr/sbin/unitd $UNITD_OPTIONS --no-daemon ExecReload= RuntimeDirectory=unit -- cgit From 924165c90ba51dd4ffea2c45541d46af56352d73 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Fri, 5 Jul 2019 18:42:30 +0300 Subject: PHP: improved response status code handling. There's no reason to parse "http_status_line"; the PHP interpreter already does this. If the line contains a valid status code, it's assigned to "http_response_code". This also fixes invalid status line handling, where the nxt_int_parse() function returned -1; it was cast to unsigned, yielding response code 65535. --- src/nxt_php_sapi.c | 14 ++------------ test/php/header/index.php | 4 ++++ test/test_php_application.py | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 12 deletions(-) create mode 100644 test/php/header/index.php diff --git a/src/nxt_php_sapi.c b/src/nxt_php_sapi.c index df7cf243..dfad9eb8 100644 --- a/src/nxt_php_sapi.c +++ b/src/nxt_php_sapi.c @@ -730,7 +730,7 @@ static int nxt_php_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC) { int rc, fields_count; - char *colon, *status_line, *value; + char *colon, *value; uint16_t status; uint32_t resp_size; nxt_php_run_ctx_t *ctx; @@ -762,17 +762,7 @@ nxt_php_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC) resp_size += h->header_len; } - if (SG(sapi_headers).http_status_line) { - status_line = SG(sapi_headers).http_status_line; - - status = nxt_int_parse((u_char *) status_line + 9, 3); - - } else if (SG(sapi_headers).http_response_code) { - status = SG(sapi_headers).http_response_code; - - } else { - status = 200; - } + status = SG(sapi_headers).http_response_code; rc = nxt_unit_response_init(req, status, fields_count, resp_size); if (nxt_slow_path(rc != NXT_UNIT_OK)) { diff --git a/test/php/header/index.php b/test/php/header/index.php new file mode 100644 index 00000000..1aa5ca04 --- /dev/null +++ b/test/php/header/index.php @@ -0,0 +1,4 @@ + diff --git a/test/test_php_application.py b/test/test_php_application.py index 8032e96e..bf0d3bd1 100644 --- a/test/test_php_application.py +++ b/test/test_php_application.py @@ -102,6 +102,46 @@ class TestPHPApplication(TestApplicationPHP): self.assertEqual(resp['status'], 200, 'status') self.assertNotEqual(resp['body'], '', 'body not empty') + def test_php_application_header_status(self): + self.load('header') + + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'Connection': 'close', + 'X-Header': 'HTTP/1.1 404 Not Found', + } + )['status'], + 404, + 'status', + ) + + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'Connection': 'close', + 'X-Header': 'http/1.1 404 Not Found', + } + )['status'], + 404, + 'status case insensitive', + ) + + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'Connection': 'close', + 'X-Header': 'HTTP/ 404 Not Found', + } + )['status'], + 404, + 'status version empty', + ) + + def test_php_application_404(self): self.load('404') -- cgit From cc578b2e63745e8a0cf290669d0922bf176c45ff Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Fri, 5 Jul 2019 21:10:01 +0300 Subject: PHP: added PATH_INFO support. --- src/nxt_php_sapi.c | 170 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 96 insertions(+), 74 deletions(-) diff --git a/src/nxt_php_sapi.c b/src/nxt_php_sapi.c index dfad9eb8..1551b05e 100644 --- a/src/nxt_php_sapi.c +++ b/src/nxt_php_sapi.c @@ -143,14 +143,16 @@ static sapi_module_struct nxt_php_sapi_module = struct nxt_php_run_ctx_s { char *cookie; - nxt_str_t script; + nxt_str_t path_info; + nxt_str_t script_name; + nxt_str_t script_filename; nxt_unit_request_info_t *req; }; -static nxt_str_t nxt_php_path; static nxt_str_t nxt_php_root; -static nxt_str_t nxt_php_script; +static nxt_str_t nxt_php_script_name; +static nxt_str_t nxt_php_script_filename; static nxt_str_t nxt_php_index = nxt_string("index.php"); @@ -179,8 +181,8 @@ static nxt_int_t nxt_php_init(nxt_task_t *task, nxt_common_app_conf_t *conf) { u_char *p; - nxt_str_t rpath, ini_path; - nxt_str_t *root, *path, *script, *index; + nxt_str_t ini_path; + nxt_str_t *root, *script_filename, *script_name, *index; nxt_port_t *my_port, *main_port; nxt_runtime_t *rt; nxt_unit_ctx_t *unit_ctx; @@ -202,8 +204,8 @@ nxt_php_init(nxt_task_t *task, nxt_common_app_conf_t *conf) } root = &nxt_php_root; - path = &nxt_php_path; - script = &nxt_php_script; + script_filename = &nxt_php_script_filename; + script_name = &nxt_php_script_name; index = &nxt_php_index; root->start = nxt_realpath(c->root); @@ -219,47 +221,44 @@ nxt_php_init(nxt_task_t *task, nxt_common_app_conf_t *conf) if (c->script.length > 0) { nxt_php_str_trim_lead(&c->script, '/'); - path->length = root->length + 1 + c->script.length; - path->start = nxt_malloc(path->length + 1); - if (nxt_slow_path(path->start == NULL)) { + p = nxt_malloc(root->length + 1 + c->script.length + 1); + if (nxt_slow_path(p == NULL)) { return NXT_ERROR; } - p = nxt_cpymem(path->start, root->start, root->length); + p = nxt_cpymem(p, root->start, root->length); *p++ = '/'; p = nxt_cpymem(p, c->script.start, c->script.length); *p = '\0'; - rpath.start = nxt_realpath(path->start); - if (nxt_slow_path(rpath.start == NULL)) { - nxt_alert(task, "script realpath(%V) failed %E", path, nxt_errno); + script_filename->start = nxt_realpath(p); + if (nxt_slow_path(script_filename->start == NULL)) { + nxt_alert(task, "script realpath(%s) failed %E", p, nxt_errno); return NXT_ERROR; } - rpath.length = nxt_strlen(rpath.start); + nxt_free(p); + + script_filename->length = nxt_strlen(script_filename->start); - if (!nxt_str_start(&rpath, root->start, root->length)) { + if (!nxt_str_start(script_filename, root->start, root->length)) { nxt_alert(task, "script is not under php root"); return NXT_ERROR; } - nxt_free(path->start); - - *path = rpath; - - script->length = c->script.length + 1; - script->start = nxt_malloc(script->length); - if (nxt_slow_path(script->start == NULL)) { + script_name->length = c->script.length + 1; + script_name->start = nxt_malloc(script_name->length); + if (nxt_slow_path(script_name->start == NULL)) { return NXT_ERROR; } - script->start[0] = '/'; - nxt_memcpy(script->start + 1, c->script.start, c->script.length); + script_name->start[0] = '/'; + nxt_memcpy(script_name->start + 1, c->script.start, c->script.length); nxt_log_error(NXT_LOG_INFO, task->log, "(ABS_MODE) php script \"%V\" root: \"%V\"", - script, root); + script_name, root); } else { nxt_log_error(NXT_LOG_INFO, task->log, @@ -596,7 +595,15 @@ nxt_php_request_handler(nxt_unit_request_info_t *req) path.length = r->path_length; path.start = nxt_unit_sptr_get(&r->path); - if (nxt_php_path.start == NULL) { + if (nxt_php_script_filename.start == NULL) { + ctx->path_info.start = (u_char *) strstr((char *) path.start, ".php/"); + if (ctx->path_info.start != NULL) { + ctx->path_info.start += 4; + path.length = ctx->path_info.start - path.start; + + ctx->path_info.length = r->path_length - path.length; + } + if (path.start[path.length - 1] == '/') { script_name = nxt_php_index; @@ -605,15 +612,20 @@ nxt_php_request_handler(nxt_unit_request_info_t *req) script_name.start = NULL; } - ctx->script.length = nxt_php_root.length + path.length - + script_name.length; - p = ctx->script.start = nxt_malloc(ctx->script.length + 1); + ctx->script_filename.length = nxt_php_root.length + path.length + + script_name.length; + p = nxt_malloc(ctx->script_filename.length + 1); if (nxt_slow_path(p == NULL)) { nxt_unit_request_done(req, NXT_UNIT_ERROR); return; } + ctx->script_filename.start = p; + + ctx->script_name.length = path.length + script_name.length; + ctx->script_name.start = p + nxt_php_root.length; + p = nxt_cpymem(p, nxt_php_root.start, nxt_php_root.length); p = nxt_cpymem(p, path.start, path.length); @@ -624,7 +636,8 @@ nxt_php_request_handler(nxt_unit_request_info_t *req) *p = '\0'; } else { - ctx->script = nxt_php_path; + ctx->script_filename = nxt_php_script_filename; + ctx->script_name = nxt_php_script_name; } SG(server_context) = ctx; @@ -654,20 +667,22 @@ nxt_php_request_handler(nxt_unit_request_info_t *req) SG(request_info).path_translated = NULL; file_handle.type = ZEND_HANDLE_FILENAME; - file_handle.filename = (char *) ctx->script.start; + file_handle.filename = (char *) ctx->script_filename.start; file_handle.free_filename = 0; file_handle.opened_path = NULL; - nxt_unit_req_debug(req, "handle.filename = '%s'", ctx->script.start); + nxt_unit_req_debug(req, "handle.filename = '%s'", + ctx->script_filename.start); - if (nxt_php_path.start != NULL) { + if (nxt_php_script_filename.start != NULL) { nxt_unit_req_debug(req, "run script %.*s in absolute mode", - (int) nxt_php_path.length, - (char *) nxt_php_path.start); + (int) nxt_php_script_filename.length, + (char *) nxt_php_script_filename.start); } else { - nxt_unit_req_debug(req, "run script %.*s", (int) ctx->script.length, - (char *) ctx->script.start); + nxt_unit_req_debug(req, "run script %.*s", + (int) ctx->script_filename.length, + (char *) ctx->script_filename.start); } #if (NXT_PHP7) @@ -690,8 +705,8 @@ fail: nxt_unit_request_done(req, rc); - if (ctx->script.start != nxt_php_path.start) { - nxt_free(ctx->script.start); + if (ctx->script_filename.start != nxt_php_script_filename.start) { + nxt_free(ctx->script_filename.start); } } @@ -855,49 +870,56 @@ nxt_php_register_variables(zval *track_vars_array TSRMLS_DC) nxt_php_set_sptr(req, "SERVER_PROTOCOL", &r->version, r->version_length, track_vars_array TSRMLS_CC); -/* - * 'SCRIPT_NAME' - * Contains the current script's path. This is useful for pages which need to - * point to themselves. The __FILE__ constant contains the full path and - * filename of the current (i.e. included) file. - */ - -/* - * 'SCRIPT_FILENAME' - * The absolute pathname of the currently executing script. - */ - -/* - * 'DOCUMENT_ROOT' - * The document root directory under which the current script is executing, - * as defined in the server's configuration file. - */ + /* + * 'PHP_SELF' + * The filename of the currently executing script, relative to the document + * root. For instance, $_SERVER['PHP_SELF'] in a script at the address + * http://example.com/foo/bar.php would be /foo/bar.php. The __FILE__ + * constant contains the full path and filename of the current (i.e. + * included) file. If PHP is running as a command-line processor this + * variable contains the script name since PHP 4.3.0. Previously it was not + * available. + */ - if (nxt_php_script.start != NULL) { - // ABS_MODE -/* - * 'PHP_SELF' - * The filename of the currently executing script, relative to the document - * root. For instance, $_SERVER['PHP_SELF'] in a script at the address - * http://example.com/foo/bar.php would be /foo/bar.php. The __FILE__ constant - * contains the full path and filename of the current (i.e. included) file. - * If PHP is running as a command-line processor this variable contains the - * script name since PHP 4.3.0. Previously it was not available. - */ - nxt_php_set_str(req, "PHP_SELF", &nxt_php_script, - track_vars_array TSRMLS_CC); - nxt_php_set_str(req, "SCRIPT_NAME", &nxt_php_script, + if (nxt_php_script_name.start != NULL) { + /* ABS_MODE */ + nxt_php_set_str(req, "PHP_SELF", &nxt_php_script_name, track_vars_array TSRMLS_CC); } else { nxt_php_set_sptr(req, "PHP_SELF", &r->path, r->path_length, track_vars_array TSRMLS_CC); - nxt_php_set_sptr(req, "SCRIPT_NAME", &r->path, r->path_length, - track_vars_array TSRMLS_CC); } - nxt_php_set_str(req, "SCRIPT_FILENAME", &ctx->script, + if (ctx->path_info.length != 0) { + nxt_php_set_str(req, "PATH_INFO", &ctx->path_info, + track_vars_array TSRMLS_CC); + } + + /* + * 'SCRIPT_NAME' + * Contains the current script's path. This is useful for pages which need + * to point to themselves. The __FILE__ constant contains the full path and + * filename of the current (i.e. included) file. + */ + + nxt_php_set_str(req, "SCRIPT_NAME", &ctx->script_name, track_vars_array TSRMLS_CC); + + /* + * 'SCRIPT_FILENAME' + * The absolute pathname of the currently executing script. + */ + + nxt_php_set_str(req, "SCRIPT_FILENAME", &ctx->script_filename, + track_vars_array TSRMLS_CC); + + /* + * 'DOCUMENT_ROOT' + * The document root directory under which the current script is executing, + * as defined in the server's configuration file. + */ + nxt_php_set_str(req, "DOCUMENT_ROOT", &nxt_php_root, track_vars_array TSRMLS_CC); -- cgit From 4ea7d714056f63486a4c3a7d0953e513e898b4ba Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Mon, 8 Jul 2019 19:23:33 +0300 Subject: Tests: added PATH_INFO variable test. --- test/php/variables/index.php | 1 + test/test_php_application.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/test/php/variables/index.php b/test/php/variables/index.php index 8f2e3bfc..279efc79 100644 --- a/test/php/variables/index.php +++ b/test/php/variables/index.php @@ -4,6 +4,7 @@ $body = file_get_contents('php://input'); header('Content-Length: ' . strlen($body)); header('Request-Method: ' . $_SERVER['REQUEST_METHOD']); header('Request-Uri: ' . $_SERVER['REQUEST_URI']); +header('Path-Info: ' . $_SERVER['PATH_INFO']); header('Http-Host: ' . $_SERVER['HTTP_HOST']); header('Server-Protocol: ' . $_SERVER['SERVER_PROTOCOL']); header('Server-Software: ' . $_SERVER['SERVER_SOFTWARE']); diff --git a/test/test_php_application.py b/test/test_php_application.py index bf0d3bd1..ad520596 100644 --- a/test/test_php_application.py +++ b/test/test_php_application.py @@ -24,6 +24,7 @@ class TestPHPApplication(TestApplicationPHP): 'Connection': 'close', }, body=body, + url='/index.php/blah?var=val' ) self.assertEqual(resp['status'], 200, 'status') @@ -54,7 +55,8 @@ class TestPHPApplication(TestApplicationPHP): 'Connection': 'close', 'Content-Length': str(len(body)), 'Request-Method': 'POST', - 'Request-Uri': '/', + 'Path-Info': '/blah', + 'Request-Uri': '/index.php/blah?var=val', 'Http-Host': 'localhost', 'Server-Protocol': 'HTTP/1.1', 'Custom-Header': 'blah', -- cgit From d7516e5f4e7709853c5f010f149acf8822c81788 Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Mon, 8 Jul 2019 20:46:06 +0300 Subject: Tests: made openssl certificates more strong to avoid errors. --- test/test_tls.py | 4 ++-- test/unit/applications/tls.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test_tls.py b/test/test_tls.py index f055aa24..14efb3a7 100644 --- a/test/test_tls.py +++ b/test/test_tls.py @@ -139,7 +139,7 @@ class TestTLS(TestApplicationTLS): self.assertEqual( self.conf_get('/certificates/default/key'), - 'RSA (1024 bits)', + 'RSA (2048 bits)', 'certificate key rsa', ) @@ -250,7 +250,7 @@ default_ca = myca [ myca ] new_certs_dir = %(dir)s database = %(database)s -default_md = sha1 +default_md = sha256 policy = myca_policy serial = %(certserial)s default_days = 1 diff --git a/test/unit/applications/tls.py b/test/unit/applications/tls.py index 83cc1a03..c8287ac5 100644 --- a/test/unit/applications/tls.py +++ b/test/unit/applications/tls.py @@ -68,7 +68,7 @@ class TestApplicationTLS(TestApplicationProto): with open(self.testdir + '/openssl.conf', 'w') as f: f.write( """[ req ] -default_bits = 1024 +default_bits = 2048 encrypt_key = no distinguished_name = req_distinguished_name [ req_distinguished_name ]""" -- cgit From c0aad59d13dc611641f204066d9453bbb4baf25d Mon Sep 17 00:00:00 2001 From: Andrei Belov Date: Tue, 9 Jul 2019 14:17:03 +0300 Subject: Packages: Debian 10 "buster" support added. --- pkg/deb/Makefile | 12 ++++++++++++ pkg/deb/Makefile.jsc-common | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/pkg/deb/Makefile b/pkg/deb/Makefile index 0ad98d80..952408cd 100644 --- a/pkg/deb/Makefile +++ b/pkg/deb/Makefile @@ -78,6 +78,18 @@ include Makefile.python include Makefile.perl endif +# Debian 10 +ifeq ($(CODENAME),buster) +include Makefile.php +include Makefile.python27 +include Makefile.python37 +include Makefile.go111 +include Makefile.perl +include Makefile.ruby +include Makefile.jsc-common +include Makefile.jsc11 +endif + # Debian 9 ifeq ($(CODENAME),stretch) include Makefile.php diff --git a/pkg/deb/Makefile.jsc-common b/pkg/deb/Makefile.jsc-common index 080e248f..30f777fd 100644 --- a/pkg/deb/Makefile.jsc-common +++ b/pkg/deb/Makefile.jsc-common @@ -6,7 +6,7 @@ MODULE_SUMMARY_jsc_common= Java shared packages for NGINX Unit MODULE_VERSION_jsc_common= $(VERSION) MODULE_RELEASE_jsc_common= 1 -ifeq ($(CODENAME),disco) +ifneq (,$(findstring $(CODENAME),disco buster)) JAVA_MINVERSION= 11 else JAVA_MINVERSION= 8 -- cgit From ee1f0365d20cc0e58dd5538c5f6e082587f0b093 Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Sun, 14 Jul 2019 23:47:07 +0300 Subject: Tests: removed unused import. --- test/test_tls.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/test_tls.py b/test/test_tls.py index 14efb3a7..3aa0218e 100644 --- a/test/test_tls.py +++ b/test/test_tls.py @@ -1,6 +1,5 @@ import re import ssl -import time import subprocess import unittest from unit.applications.tls import TestApplicationTLS -- cgit From ff5413f109301ff0049153a22c2b135fa94a26fb Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Sun, 14 Jul 2019 23:47:49 +0300 Subject: Tests: removed unnecessary initialization. That could leads to the incorrect behaviour in test_tls_application_respawn. --- test/test_tls.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/test_tls.py b/test/test_tls.py index 3aa0218e..df3499da 100644 --- a/test/test_tls.py +++ b/test/test_tls.py @@ -514,8 +514,6 @@ basicConstraints = critical,CA:TRUE""" self.skip_alerts.append(r'process \d+ exited on signal 9') self.load('mirror') - self.assertEqual(self.get()['status'], 200, 'init') - self.certificate() self.conf('1', 'applications/mirror/processes') -- cgit From 72e987e1105822fa10899378d26b2706f04f5515 Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Sun, 14 Jul 2019 23:49:31 +0300 Subject: Tests: check application configuration loading in TLS tests. --- test/unit/applications/tls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/applications/tls.py b/test/unit/applications/tls.py index c8287ac5..0f1053bf 100644 --- a/test/unit/applications/tls.py +++ b/test/unit/applications/tls.py @@ -76,7 +76,7 @@ distinguished_name = req_distinguished_name script_path = self.current_dir + '/python/' + script - self.conf( + self._load_conf( { "listeners": {"*:7080": {"pass": "applications/" + name}}, "applications": { -- cgit From b097476824b356bfd025e7afffdc4650bae2b375 Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Sun, 14 Jul 2019 23:49:37 +0300 Subject: Tests: TestApplicationTLS.openssl_conf() introduced. --- test/test_tls.py | 2 ++ test/unit/applications/tls.py | 17 ++++++++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/test/test_tls.py b/test/test_tls.py index df3499da..076a2c38 100644 --- a/test/test_tls.py +++ b/test/test_tls.py @@ -145,6 +145,8 @@ class TestTLS(TestApplicationTLS): def test_tls_certificate_key_ec(self): self.load('empty') + self.openssl_conf() + subprocess.call( [ 'openssl', diff --git a/test/unit/applications/tls.py b/test/unit/applications/tls.py index 0f1053bf..6e8deefb 100644 --- a/test/unit/applications/tls.py +++ b/test/unit/applications/tls.py @@ -1,3 +1,4 @@ +import os import ssl import subprocess from unit.applications.proto import TestApplicationProto @@ -12,6 +13,8 @@ class TestApplicationTLS(TestApplicationProto): self.context.verify_mode = ssl.CERT_NONE def certificate(self, name='default', load=True): + self.openssl_conf() + subprocess.call( [ 'openssl', @@ -59,13 +62,13 @@ class TestApplicationTLS(TestApplicationProto): return ssl.get_server_certificate(addr, ssl_version=ssl_version) - def load(self, script, name=None): - if name is None: - name = script + def openssl_conf(self): + conf_path = self.testdir + '/openssl.conf' - # create default openssl configuration + if os.path.exists(conf_path): + return - with open(self.testdir + '/openssl.conf', 'w') as f: + with open(conf_path, 'w') as f: f.write( """[ req ] default_bits = 2048 @@ -74,6 +77,10 @@ distinguished_name = req_distinguished_name [ req_distinguished_name ]""" ) + def load(self, script, name=None): + if name is None: + name = script + script_path = self.current_dir + '/python/' + script self._load_conf( -- cgit From ce1fbd927379abf7336171967d788f10692eef68 Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Sun, 14 Jul 2019 23:49:45 +0300 Subject: Tests: simplified one route case configuration. No functional changes. --- test/test_routing.py | 1081 +++++++++++++++++++------------------------------- 1 file changed, 413 insertions(+), 668 deletions(-) diff --git a/test/test_routing.py b/test/test_routing.py index 7e14fd65..40d9f786 100644 --- a/test/test_routing.py +++ b/test/test_routing.py @@ -38,6 +38,9 @@ class TestRouting(TestApplicationProto): } ) + def route(self, route): + return self.conf([route], 'routes') + def test_routes_match_method_positive(self): self.assertEqual(self.get()['status'], 200, 'method positive GET') self.assertEqual(self.post()['status'], 404, 'method positive POST') @@ -45,14 +48,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_method_positive_many(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"method": ["GET", "POST"]}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"method": ["GET", "POST"]}, + "action": {"pass": "applications/empty"}, + } ), 'method positive many configure', ) @@ -68,14 +68,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_method_negative(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"method": "!GET"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"method": "!GET"}, + "action": {"pass": "applications/empty"}, + } ), 'method negative configure', ) @@ -86,14 +83,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_method_negative_many(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"method": ["!GET", "!POST"]}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"method": ["!GET", "!POST"]}, + "action": {"pass": "applications/empty"}, + } ), 'method negative many configure', ) @@ -109,14 +103,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_method_wildcard_left(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"method": "*ET"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"method": "*ET"}, + "action": {"pass": "applications/empty"}, + } ), 'method wildcard left configure', ) @@ -129,14 +120,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_method_wildcard_right(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"method": "GE*"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"method": "GE*"}, + "action": {"pass": "applications/empty"}, + } ), 'method wildcard right configure', ) @@ -151,14 +139,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_method_wildcard_left_right(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"method": "*GET*"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"method": "*GET*"}, + "action": {"pass": "applications/empty"}, + } ), 'method wildcard left right configure', ) @@ -173,14 +158,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_method_wildcard(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"method": "*"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"method": "*"}, + "action": {"pass": "applications/empty"}, + } ), 'method wildcard configure', ) @@ -190,70 +172,55 @@ class TestRouting(TestApplicationProto): def test_routes_match_invalid(self): self.assertIn( 'error', - self.conf( - [ - { - "match": {"method": "**"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"method": "**"}, + "action": {"pass": "applications/empty"}, + } ), 'wildcard invalid', ) self.assertIn( 'error', - self.conf( - [ - { - "match": {"method": "blah**"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"method": "blah**"}, + "action": {"pass": "applications/empty"}, + } ), 'wildcard invalid 2', ) self.assertIn( 'error', - self.conf( - [ - { - "match": {"host": "*blah*blah"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"host": "*blah*blah"}, + "action": {"pass": "applications/empty"}, + } ), 'wildcard invalid 3', ) self.assertIn( 'error', - self.conf( - [ - { - "match": {"host": "blah*blah*blah"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"host": "blah*blah*blah"}, + "action": {"pass": "applications/empty"}, + } ), 'wildcard invalid 4', ) self.assertIn( 'error', - self.conf( - [ - { - "match": {"host": "blah*blah*"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"host": "blah*blah*"}, + "action": {"pass": "applications/empty"}, + } ), 'wildcard invalid 5', ) @@ -261,14 +228,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_wildcard_middle(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"host": "ex*le"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"host": "ex*le"}, + "action": {"pass": "applications/empty"}, + } ), 'host wildcard middle configure', ) @@ -308,14 +272,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_method_case_insensitive(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"method": "get"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"method": "get"}, + "action": {"pass": "applications/empty"}, + } ), 'method case insensitive configure', ) @@ -325,14 +286,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_wildcard_left_case_insensitive(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"method": "*et"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"method": "*et"}, + "action": {"pass": "applications/empty"}, + } ), 'match wildcard case insensitive configure', ) @@ -344,14 +302,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_wildcard_middle_case_insensitive(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"method": "g*t"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"method": "g*t"}, + "action": {"pass": "applications/empty"}, + } ), 'match wildcard case insensitive configure', ) @@ -363,14 +318,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_wildcard_right_case_insensitive(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"method": "get*"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"method": "get*"}, + "action": {"pass": "applications/empty"}, + } ), 'match wildcard case insensitive configure', ) @@ -382,14 +334,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_wildcard_substring_case_insensitive(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"method": "*et*"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"method": "*et*"}, + "action": {"pass": "applications/empty"}, + } ), 'match wildcard substring case insensitive configure', ) @@ -403,14 +352,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_wildcard_left_case_sensitive(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"uri": "*blah"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"uri": "*blah"}, + "action": {"pass": "applications/empty"}, + } ), 'match wildcard left case sensitive configure', ) @@ -430,14 +376,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_wildcard_middle_case_sensitive(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"uri": "/b*h"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"uri": "/b*h"}, + "action": {"pass": "applications/empty"}, + } ), 'match wildcard middle case sensitive configure', ) @@ -457,14 +400,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_wildcard_right_case_sensitive(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"uri": "/bla*"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"uri": "/bla*"}, + "action": {"pass": "applications/empty"}, + } ), 'match wildcard right case sensitive configure', ) @@ -484,14 +424,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_wildcard_substring_case_sensitive(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"uri": "*bla*"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"uri": "*bla*"}, + "action": {"pass": "applications/empty"}, + } ), 'match wildcard substring case sensitive configure', ) @@ -677,14 +614,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_host_positive(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"host": "localhost"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"host": "localhost"}, + "action": {"pass": "applications/empty"}, + } ), 'match host positive configure', ) @@ -729,14 +663,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_host_absent(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"host": "localhost"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"host": "localhost"}, + "action": {"pass": "applications/empty"}, + } ), 'match host absent configure', ) @@ -750,14 +681,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_host_ipv4(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"host": "127.0.0.1"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"host": "127.0.0.1"}, + "action": {"pass": "applications/empty"}, + } ), 'match host ipv4 configure', ) @@ -773,14 +701,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_host_ipv6(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"host": "[::1]"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"host": "[::1]"}, + "action": {"pass": "applications/empty"}, + } ), 'match host ipv6 configure', ) @@ -804,14 +729,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_host_positive_many(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"host": ["localhost", "example.com"]}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"host": ["localhost", "example.com"]}, + "action": {"pass": "applications/empty"}, + } ), 'match host positive many configure', ) @@ -830,17 +752,12 @@ class TestRouting(TestApplicationProto): def test_routes_match_host_positive_and_negative(self): self.assertIn( - 'success', - self.conf( - [ - { - "match": { - "host": ["*example.com", "!www.example.com"] - }, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + 'success', + self.route( + { + "match": {"host": ["*example.com", "!www.example.com"]}, + "action": {"pass": "applications/empty"}, + } ), 'match host positive and negative configure', ) @@ -878,14 +795,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_host_positive_and_negative_wildcard(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"host": ["*example*", "!www.example*"]}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"host": ["*example*", "!www.example*"]}, + "action": {"pass": "applications/empty"}, + } ), 'match host positive and negative wildcard configure', ) @@ -909,14 +823,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_host_case_insensitive(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"host": "Example.com"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"host": "Example.com"}, + "action": {"pass": "applications/empty"}, + } ), 'host case insensitive configure', ) @@ -940,14 +851,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_host_port(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"host": "example.com"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"host": "example.com"}, + "action": {"pass": "applications/empty"}, + } ), 'match host port configure', ) @@ -963,14 +871,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_host_empty(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"host": ""}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"host": ""}, + "action": {"pass": "applications/empty"}, + } ), 'match host empty configure', ) @@ -990,14 +895,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_uri_positive(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"uri": "/"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"uri": "/"}, + "action": {"pass": "applications/empty"}, + } ), 'match uri positive configure', ) @@ -1025,14 +927,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_uri_case_sensitive(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"uri": "/BLAH"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"uri": "/BLAH"}, + "action": {"pass": "applications/empty"}, + } ), 'match uri case sensitive configure', ) @@ -1056,14 +955,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_uri_normalize(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"uri": "/blah"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"uri": "/blah"}, + "action": {"pass": "applications/empty"}, + } ), 'match uri normalize configure', ) @@ -1075,14 +971,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_empty_array(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"uri": []}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"uri": []}, + "action": {"pass": "applications/empty"}, + } ), 'match empty array configure', ) @@ -1180,14 +1073,11 @@ class TestRouting(TestApplicationProto): def test_routes_edit(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"method": "GET"}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"method": "GET"}, + "action": {"pass": "applications/empty"}, + } ), 'routes edit configure', ) @@ -1324,14 +1214,11 @@ class TestRouting(TestApplicationProto): self.assertIn( 'success', - self.conf( - [ - { - "match": {"method": ["GET", "POST"]}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"method": ["GET", "POST"]}, + "action": {"pass": "applications/empty"}, + } ), 'match edit configure', ) @@ -1457,18 +1344,15 @@ class TestRouting(TestApplicationProto): def test_routes_match_rules(self): self.assertIn( 'success', - self.conf( - [ - { - "match": { - "method": "GET", - "host": "localhost", - "uri": "/", - }, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": { + "method": "GET", + "host": "localhost", + "uri": "/", + }, + "action": {"pass": "applications/empty"}, + } ), 'routes match rules configure', ) @@ -1478,10 +1362,7 @@ class TestRouting(TestApplicationProto): def test_routes_loop(self): self.assertIn( 'success', - self.conf( - [{"match": {"uri": "/"}, "action": {"pass": "routes"}}], - 'routes', - ), + self.route({"match": {"uri": "/"}, "action": {"pass": "routes"}}), 'routes loop configure', ) @@ -1490,14 +1371,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_headers(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"headers": {"host": "localhost"}}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"headers": {"host": "localhost"}}, + "action": {"pass": "applications/empty"}, + } ), 'match headers configure', ) @@ -1547,16 +1425,13 @@ class TestRouting(TestApplicationProto): def test_routes_match_headers_multiple(self): self.assertIn( 'success', - self.conf( - [ - { - "match": { - "headers": {"host": "localhost", "x-blah": "test"} - }, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": { + "headers": {"host": "localhost", "x-blah": "test"} + }, + "action": {"pass": "applications/empty"}, + } ), 'match headers multiple configure', ) @@ -1590,14 +1465,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_headers_multiple_values(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"headers": {"x-blah": "test"}}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"headers": {"x-blah": "test"}}, + "action": {"pass": "applications/empty"}, + } ), 'match headers multiple values configure', ) @@ -1639,14 +1511,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_headers_multiple_rules(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"headers": {"x-blah": ["test", "blah"]}}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"headers": {"x-blah": ["test", "blah"]}}, + "action": {"pass": "applications/empty"}, + } ), 'match headers multiple rules configure', ) @@ -1706,14 +1575,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_headers_case_insensitive(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"headers": {"X-BLAH": "TEST"}}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"headers": {"X-BLAH": "TEST"}}, + "action": {"pass": "applications/empty"}, + } ), 'match headers case insensitive configure', ) @@ -1733,28 +1599,22 @@ class TestRouting(TestApplicationProto): def test_routes_match_headers_invalid(self): self.assertIn( 'error', - self.conf( - [ - { - "match": {"headers": ["blah"]}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"headers": ["blah"]}, + "action": {"pass": "applications/empty"}, + } ), 'match headers invalid', ) self.assertIn( 'error', - self.conf( - [ - { - "match": {"headers": {"foo": ["bar", {}]}}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"headers": {"foo": ["bar", {}]}}, + "action": {"pass": "applications/empty"}, + } ), 'match headers invalid 2', ) @@ -1762,14 +1622,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_headers_empty_rule(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"headers": {"host": ""}}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"headers": {"host": ""}}, + "action": {"pass": "applications/empty"}, + } ), 'match headers empty rule configure', ) @@ -1785,14 +1642,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_headers_rule_field_empty(self): self.assertIn( 'error', - self.conf( - [ - { - "match": {"headers": {"": "blah"}}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"headers": {"": "blah"}}, + "action": {"pass": "applications/empty"}, + } ), 'match headers rule field empty configure', ) @@ -1800,14 +1654,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_headers_empty(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"headers": {}}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"headers": {}}, + "action": {"pass": "applications/empty"}, + } ), 'match headers empty configure', ) @@ -1816,14 +1667,11 @@ class TestRouting(TestApplicationProto): self.assertIn( 'success', - self.conf( - [ - { - "match": {"headers": []}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"headers": []}, + "action": {"pass": "applications/empty"}, + } ), 'match headers array empty configure 2', ) @@ -1835,14 +1683,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_headers_rule_array_empty(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"headers": {"blah": []}}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"headers": {"blah": []}}, + "action": {"pass": "applications/empty"}, + } ), 'match headers rule array empty configure', ) @@ -1857,27 +1702,24 @@ class TestRouting(TestApplicationProto): "blah": "foo", "Connection": "close", } - )['status'], 200, 'match headers rule array empty 2' - ) - - def test_routes_match_headers_array(self): - self.assertIn( - 'success', - self.conf( - [ - { - "match": { - "headers": [ - {"x-header1": "foo*"}, - {"x-header2": "bar"}, - {"x-header3": ["foo", "bar"]}, - {"x-header1": "bar", "x-header4": "foo"}, - ] - }, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + )['status'], 200, 'match headers rule array empty 2' + ) + + def test_routes_match_headers_array(self): + self.assertIn( + 'success', + self.route( + { + "match": { + "headers": [ + {"x-header1": "foo*"}, + {"x-header2": "bar"}, + {"x-header3": ["foo", "bar"]}, + {"x-header1": "bar", "x-header4": "foo"}, + ] + }, + "action": {"pass": "applications/empty"}, + } ), 'match headers array configure', ) @@ -1972,14 +1814,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_arguments(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"arguments": {"foo": "bar"}}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"arguments": {"foo": "bar"}}, + "action": {"pass": "applications/empty"}, + } ), 'match arguments configure', ) @@ -2013,14 +1852,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_arguments_empty(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"arguments": {}}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"arguments": {}}, + "action": {"pass": "applications/empty"}, + } ), 'match arguments empty configure', ) @@ -2029,14 +1865,11 @@ class TestRouting(TestApplicationProto): self.assertIn( 'success', - self.conf( - [ - { - "match": {"arguments": []}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"arguments": []}, + "action": {"pass": "applications/empty"}, + } ), 'match arguments empty configure 2', ) @@ -2046,46 +1879,33 @@ class TestRouting(TestApplicationProto): def test_routes_match_arguments_invalid(self): self.assertIn( 'error', - self.conf( - [ - { - "match": {"arguments": ["var"]}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"arguments": ["var"]}, + "action": {"pass": "applications/empty"}, + } ), 'match arguments invalid', ) self.assertIn( 'error', - self.conf( - [ - { - "match": {"arguments": [{"var1": {}}]}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"arguments": [{"var1": {}}]}, + "action": {"pass": "applications/empty"}, + } ), 'match arguments invalid 2', ) self.assertIn( 'error', - self.conf( - [ - { - "match": { - "arguments": { - "": "bar" - } - }, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"arguments": {"": "bar"}}, + "action": {"pass": "applications/empty"}, + } ), 'match arguments invalid 3', ) @@ -2094,18 +1914,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_arguments_space(self): self.assertIn( 'success', - self.conf( - [ - { - "match": { - "arguments": { - "foo": "bar " - } - }, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"arguments": {"foo": "bar "}}, + "action": {"pass": "applications/empty"}, + } ), 'match arguments space configure', ) @@ -2130,18 +1943,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_arguments_plus(self): self.assertIn( 'success', - self.conf( - [ - { - "match": { - "arguments": [ - {"foo": "bar+"} - ] - }, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"arguments": [{"foo": "bar+"}]}, + "action": {"pass": "applications/empty"}, + } ), 'match arguments plus configure', ) @@ -2161,18 +1967,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_arguments_hex(self): self.assertIn( 'success', - self.conf( - [ - { - "match": { - "arguments": [ - {"foo": "bar"} - ] - }, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"arguments": [{"foo": "bar"}]}, + "action": {"pass": "applications/empty"}, + } ), 'match arguments hex configure', ) @@ -2186,18 +1985,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_arguments_chars(self): self.assertIn( 'success', - self.conf( - [ - { - "match": { - "arguments": { - "foo": "-._()[],;" - } - }, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"arguments": {"foo": "-._()[],;"}}, + "action": {"pass": "applications/empty"}, + } ), 'match arguments chars configure', ) @@ -2211,18 +2003,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_arguments_complex(self): self.assertIn( 'success', - self.conf( - [ - { - "match": { - "arguments": { - "foo": "" - } - }, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"arguments": {"foo": ""}}, + "action": {"pass": "applications/empty"}, + } ), 'match arguments complex configure', ) @@ -2266,16 +2051,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_arguments_multiple(self): self.assertIn( 'success', - self.conf( - [ - { - "match": { - "arguments": {"foo": "bar", "blah": "test"} - }, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"arguments": {"foo": "bar", "blah": "test"}}, + "action": {"pass": "applications/empty"}, + } ), 'match arguments multiple configure', ) @@ -2297,14 +2077,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_arguments_multiple_rules(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"arguments": {"foo": ["bar", "blah"]}}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"arguments": {"foo": ["bar", "blah"]}}, + "action": {"pass": "applications/empty"}, + } ), 'match arguments multiple rules configure', ) @@ -2340,21 +2117,18 @@ class TestRouting(TestApplicationProto): def test_routes_match_arguments_array(self): self.assertIn( 'success', - self.conf( - [ - { - "match": { - "arguments": [ - {"var1": "val1*"}, - {"var2": "val2"}, - {"var3": ["foo", "bar"]}, - {"var1": "bar", "var4": "foo"}, - ] - }, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": { + "arguments": [ + {"var1": "val1*"}, + {"var2": "val2"}, + {"var3": ["foo", "bar"]}, + {"var1": "bar", "var4": "foo"}, + ] + }, + "action": {"pass": "applications/empty"}, + } ), 'match arguments array configure', ) @@ -2406,14 +2180,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_cookies(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"cookies": {"foO": "bar"}}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"cookies": {"foO": "bar"}}, + "action": {"pass": "applications/empty"}, + } ), 'match cookie configure', ) @@ -2512,14 +2283,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_cookies_empty(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"cookies": {}}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"cookies": {}}, + "action": {"pass": "applications/empty"}, + } ), 'match cookies empty configure', ) @@ -2528,14 +2296,11 @@ class TestRouting(TestApplicationProto): self.assertIn( 'success', - self.conf( - [ - { - "match": {"cookies": []}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"cookies": []}, + "action": {"pass": "applications/empty"}, + } ), 'match cookies empty configure 2', ) @@ -2545,28 +2310,22 @@ class TestRouting(TestApplicationProto): def test_routes_match_cookies_invalid(self): self.assertIn( 'error', - self.conf( - [ - { - "match": {"cookies": ["var"]}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"cookies": ["var"]}, + "action": {"pass": "applications/empty"}, + } ), 'match cookies invalid', ) self.assertIn( 'error', - self.conf( - [ - { - "match": {"cookies": [{"foo": {}}]}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"cookies": [{"foo": {}}]}, + "action": {"pass": "applications/empty"}, + } ), 'match cookies invalid 2', ) @@ -2574,16 +2333,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_cookies_multiple(self): self.assertIn( 'success', - self.conf( - [ - { - "match": { - "cookies": {"foo": "bar", "blah": "blah"} - }, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"cookies": {"foo": "bar", "blah": "blah"}}, + "action": {"pass": "applications/empty"}, + } ), 'match cookies multiple configure', ) @@ -2641,14 +2395,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_cookies_multiple_values(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"cookies": {"blah": "blah"}}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"cookies": {"blah": "blah"}}, + "action": {"pass": "applications/empty"}, + } ), 'match cookies multiple values configure', ) @@ -2690,14 +2441,11 @@ class TestRouting(TestApplicationProto): def test_routes_match_cookies_multiple_rules(self): self.assertIn( 'success', - self.conf( - [ - { - "match": {"cookies": {"blah": ["test", "blah"]}}, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": {"cookies": {"blah": ["test", "blah"]}}, + "action": {"pass": "applications/empty"}, + } ), 'match cookies multiple rules configure', ) @@ -2769,21 +2517,18 @@ class TestRouting(TestApplicationProto): def test_routes_match_cookies_array(self): self.assertIn( 'success', - self.conf( - [ - { - "match": { - "cookies": [ - {"var1": "val1*"}, - {"var2": "val2"}, - {"var3": ["foo", "bar"]}, - {"var1": "bar", "var4": "foo"}, - ] - }, - "action": {"pass": "applications/empty"}, - } - ], - 'routes', + self.route( + { + "match": { + "cookies": [ + {"var1": "val1*"}, + {"var2": "val2"}, + {"var3": ["foo", "bar"]}, + {"var1": "bar", "var4": "foo"}, + ] + }, + "action": {"pass": "applications/empty"}, + } ), 'match cookies array configure', ) -- cgit From ef89815f03ff27ae5ccf4d93ce10f08539a315bd Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Tue, 16 Jul 2019 17:58:48 +0300 Subject: PHP: fixed script filename setting, broken after 2a71417d297f. --- src/nxt_php_sapi.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/nxt_php_sapi.c b/src/nxt_php_sapi.c index 1551b05e..f5f115f5 100644 --- a/src/nxt_php_sapi.c +++ b/src/nxt_php_sapi.c @@ -180,7 +180,7 @@ static void ***tsrm_ls; static nxt_int_t nxt_php_init(nxt_task_t *task, nxt_common_app_conf_t *conf) { - u_char *p; + u_char *p, *tmp; nxt_str_t ini_path; nxt_str_t *root, *script_filename, *script_name, *index; nxt_port_t *my_port, *main_port; @@ -221,24 +221,26 @@ nxt_php_init(nxt_task_t *task, nxt_common_app_conf_t *conf) if (c->script.length > 0) { nxt_php_str_trim_lead(&c->script, '/'); - p = nxt_malloc(root->length + 1 + c->script.length + 1); - if (nxt_slow_path(p == NULL)) { + tmp = nxt_malloc(root->length + 1 + c->script.length + 1); + if (nxt_slow_path(tmp == NULL)) { return NXT_ERROR; } + p = tmp; + p = nxt_cpymem(p, root->start, root->length); *p++ = '/'; p = nxt_cpymem(p, c->script.start, c->script.length); *p = '\0'; - script_filename->start = nxt_realpath(p); + script_filename->start = nxt_realpath(tmp); if (nxt_slow_path(script_filename->start == NULL)) { - nxt_alert(task, "script realpath(%s) failed %E", p, nxt_errno); + nxt_alert(task, "script realpath(%s) failed %E", tmp, nxt_errno); return NXT_ERROR; } - nxt_free(p); + nxt_free(tmp); script_filename->length = nxt_strlen(script_filename->start); -- cgit From aab235c4dcda9aa9a629512a413b46f6ccffbd0f Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Wed, 17 Jul 2019 10:52:48 +0300 Subject: Java: fixing realpath error message. --- src/nxt_java.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nxt_java.c b/src/nxt_java.c index bf4931ab..3421d825 100644 --- a/src/nxt_java.c +++ b/src/nxt_java.c @@ -68,7 +68,7 @@ nxt_java_pre_init(nxt_task_t *task, nxt_common_app_conf_t *conf) nxt_java_modules = realpath(unit_jars, NULL); if (nxt_java_modules == NULL) { - nxt_alert(task, "realpath(%s) failed: %E", NXT_JARS, nxt_errno); + nxt_alert(task, "realpath(%s) failed: %E", unit_jars, nxt_errno); return NXT_ERROR; } -- cgit From dcf46a63eb43297180b5284fab0acca405a6a1c4 Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Wed, 17 Jul 2019 16:57:43 +0300 Subject: Exiting application process in case of pre_init stage error. --- src/nxt_application.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/nxt_application.c b/src/nxt_application.c index f63b90fb..468bc627 100644 --- a/src/nxt_application.c +++ b/src/nxt_application.c @@ -333,10 +333,7 @@ nxt_app_start(nxt_task_t *task, void *data) ret = nxt_app->pre_init(task, data); if (nxt_slow_path(ret != NXT_OK)) { - nxt_debug(task, "application pre_init failed"); - - } else { - nxt_debug(task, "application pre_init done"); + return ret; } } -- cgit From 4153fad89dfeb694bf7f0c94d4c02da50af1852b Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Wed, 17 Jul 2019 16:50:24 +0300 Subject: Tests: added PHP tests with "script" and "index" options. --- test/php/script/phpinfo.php | 3 +++ test/test_php_application.py | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 test/php/script/phpinfo.php diff --git a/test/php/script/phpinfo.php b/test/php/script/phpinfo.php new file mode 100644 index 00000000..cf608608 --- /dev/null +++ b/test/php/script/phpinfo.php @@ -0,0 +1,3 @@ + diff --git a/test/test_php_application.py b/test/test_php_application.py index ad520596..ee2048b5 100644 --- a/test/test_php_application.py +++ b/test/test_php_application.py @@ -462,6 +462,45 @@ class TestPHPApplication(TestApplicationPHP): self.get()['body'], r'012345', 'disable_classes before' ) + def test_php_application_script(self): + self.assertIn( + 'success', self.conf( + { + "listeners": {"*:7080": {"pass": "applications/script"}}, + "applications": { + "script": { + "type": "php", + "processes": {"spare": 0}, + "root": self.current_dir + "/php/script", + "script": "phpinfo.php", + } + }, + } + ), 'configure script' + ) + + resp = self.get() + + self.assertEqual(resp['status'], 200, 'status') + self.assertNotEqual(resp['body'], '', 'body not empty') + + def test_php_application_index_default(self): + self.assertIn( + 'success', self.conf( + { + "listeners": {"*:7080": {"pass": "applications/phpinfo"}}, + "applications": { + "phpinfo": { + "type": "php", + "processes": {"spare": 0}, + "root": self.current_dir + "/php/phpinfo", + } + }, + } + ), 'configure index default' + ) + + self.assertEqual(self.get()['status'], 200, 'status') if __name__ == '__main__': TestPHPApplication.main() -- cgit From a920215a5cec785e8e3f4bb675b18b5c8916f4dd Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Wed, 17 Jul 2019 17:48:35 +0300 Subject: Tests: more Java tests. Added test with war file and error configuration test. --- test/java/empty_war/empty.war | Bin 0 -> 484 bytes test/test_java_application.py | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 test/java/empty_war/empty.war diff --git a/test/java/empty_war/empty.war b/test/java/empty_war/empty.war new file mode 100644 index 00000000..4985e804 Binary files /dev/null and b/test/java/empty_war/empty.war differ diff --git a/test/test_java_application.py b/test/test_java_application.py index 54ae3366..e62720d1 100644 --- a/test/test_java_application.py +++ b/test/test_java_application.py @@ -5,6 +5,46 @@ from unit.applications.lang.java import TestApplicationJava class TestJavaApplication(TestApplicationJava): prerequisites = ['java'] + def test_java_conf_error(self): + self.skip_alerts.extend( + [ + r'realpath.*failed', + r'failed to apply new conf', + ] + ) + self.assertIn( + 'error', + self.conf( + { + "listeners": {"*:7080": {"pass": "applications/app"}}, + "applications": { + "app": { + "type": "java", + "processes": 1, + "working_directory": self.current_dir + + "/java/empty", + "webapp": self.testdir + "/java", + } + }, + } + ), + 'conf error', + ) + + def test_java_war(self): + self.load('empty_war') + + self.assertIn( + 'success', + self.conf( + '"' + self.testdir + '/java/empty.war"', + '/config/applications/empty_war/webapp', + ), + 'configure war', + ) + + self.assertEqual(self.get()['status'], 200, 'war') + def test_java_application_cookies(self): self.load('cookies') -- cgit From dbce694d5a9b43009fef894120edd44ad1e59c25 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Wed, 17 Jul 2019 21:17:30 +0300 Subject: Perl: removed "--include=" configure option. It's surplus option because the perl executable returns the proper path. Also the Perl module configure script was cleaned up a bit. Note that NXT_PERL_LDOPTS already contains the library path. --- auto/modules/perl | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/auto/modules/perl b/auto/modules/perl index bf6fe6f0..904a5055 100644 --- a/auto/modules/perl +++ b/auto/modules/perl @@ -14,14 +14,12 @@ for nxt_option; do case "$nxt_option" in --perl=*) NXT_PERL="$value" ;; - --include=*) NXT_PERL_INCPATH="$value" ;; --module=*) NXT_PERL_MODULE="$value" ;; --help) cat << END --perl=FILE set perl executable, default: perl - --include=DIRECTORY set directory path to perl headers --module=NAME set unit perl module name END @@ -59,10 +57,8 @@ nxt_found=no if /bin/sh -c "$NXT_PERL -MConfig -e 'print \"Perl version: \", \$Config{version}, \"\\n\"'" >> $NXT_AUTOCONF_ERR 2>&1; then - NXT_PERL_INCPATH=${NXT_PERL_INCPATH=`$NXT_PERL -MConfig -e 'print $Config{archlib}, "/CORE"'`} - NXT_PERL_INCLUDE="-I ${NXT_PERL_INCPATH}" - - NXT_PERL_LDOPTS=`$NXT_PERL -MExtUtils::Embed -e ldopts` + NXT_PERL_INCLUDE=`$NXT_PERL -MExtUtils::Embed -e perl_inc | sed -e 's/^ //;s/ $//'` + NXT_PERL_LDOPTS=`$NXT_PERL -MExtUtils::Embed -e ldopts | sed -e 's/^ //;s/ $//'` if [ "$NXT_SYSTEM" = "Darwin" ]; then # OS X system perl wants to link universal binaries @@ -70,13 +66,11 @@ if /bin/sh -c "$NXT_PERL -MConfig -e 'print \"Perl version: \", | sed -e 's/-arch i386//' -e 's/-arch x86_64//'` fi - NXT_PERL_LIBS="-L ${NXT_PERL_INCPATH} ${NXT_PERL_LDOPTS}" - nxt_feature="Perl" nxt_feature_name="" nxt_feature_run=no nxt_feature_incs="${NXT_PERL_INCLUDE}" - nxt_feature_libs="${NXT_PERL_LIBS}" + nxt_feature_libs="${NXT_PERL_LDOPTS}" nxt_feature_test=" #define _GNU_SOURCE #include @@ -123,7 +117,7 @@ nxt_feature="Perl version" nxt_feature_name="" nxt_feature_run=value nxt_feature_incs="${NXT_PERL_INCLUDE}" -nxt_feature_libs="${NXT_PERL_LIBS}" +nxt_feature_libs="${NXT_PERL_LDOPTS}" nxt_feature_test=" #define _GNU_SOURCE #include @@ -194,7 +188,7 @@ ${NXT_PERL_MODULE}: $NXT_BUILD_DIR/${NXT_PERL_MODULE}.unit.so $NXT_BUILD_DIR/${NXT_PERL_MODULE}.unit.so: $nxt_objs \$(NXT_MODULE_LINK) -o $NXT_BUILD_DIR/${NXT_PERL_MODULE}.unit.so \\ - $nxt_objs $NXT_PERL_LIBS $NXT_LD_OPT + $nxt_objs $NXT_PERL_LDOPTS $NXT_LD_OPT install: ${NXT_PERL_MODULE}-install -- cgit From 242dc363ced8d244ff296e1c3c8f802b4857fd35 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Wed, 17 Jul 2019 21:17:30 +0300 Subject: Perl: propagated compile options from perl build. Some Perl compile options affects ABI and not using them while compiling our module resulted in non-working build. Notably on 32-bit Debian 10, Perl is built with -D_FILE_OFFSET_BITS=64 and our module after being compiled without this option caused segmentation faults in unexpected places. --- auto/modules/perl | 3 ++- src/perl/nxt_perl_psgi_layer.h | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/auto/modules/perl b/auto/modules/perl index 904a5055..a4fd5437 100644 --- a/auto/modules/perl +++ b/auto/modules/perl @@ -57,6 +57,7 @@ nxt_found=no if /bin/sh -c "$NXT_PERL -MConfig -e 'print \"Perl version: \", \$Config{version}, \"\\n\"'" >> $NXT_AUTOCONF_ERR 2>&1; then + NXT_PERL_CFLAGS=`$NXT_PERL -MExtUtils::Embed -e ccflags | sed -e 's/^ //;s/ $//'` NXT_PERL_INCLUDE=`$NXT_PERL -MExtUtils::Embed -e perl_inc | sed -e 's/^ //;s/ $//'` NXT_PERL_LDOPTS=`$NXT_PERL -MExtUtils::Embed -e ldopts | sed -e 's/^ //;s/ $//'` @@ -165,7 +166,7 @@ for nxt_src in $NXT_PERL_MODULE_SRCS; do $NXT_BUILD_DIR/$nxt_obj: $nxt_src mkdir -p $NXT_BUILD_DIR/src/perl - \$(CC) -c \$(CFLAGS) \$(NXT_INCS) $NXT_PERL_INCLUDE \\ + \$(CC) -c \$(CFLAGS) $NXT_PERL_CFLAGS \$(NXT_INCS) $NXT_PERL_INCLUDE \\ $nxt_dep_flags \\ -o $NXT_BUILD_DIR/$nxt_obj $nxt_src $nxt_dep_post diff --git a/src/perl/nxt_perl_psgi_layer.h b/src/perl/nxt_perl_psgi_layer.h index 561d5153..3fa349c0 100644 --- a/src/perl/nxt_perl_psgi_layer.h +++ b/src/perl/nxt_perl_psgi_layer.h @@ -8,7 +8,6 @@ #define _NXT_PERL_PSGI_LAYER_H_INCLUDED_ -#define _GNU_SOURCE #include #include #include -- cgit From b1165d2edc99daf8eef0e092e4ed6dcee9bce252 Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Tue, 23 Jul 2019 14:58:27 +0300 Subject: Tests: fixed test_java_conf_error to pass with "--modules=" option. --- test/test_java_application.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test_java_application.py b/test/test_java_application.py index e62720d1..f604368c 100644 --- a/test/test_java_application.py +++ b/test/test_java_application.py @@ -24,6 +24,7 @@ class TestJavaApplication(TestApplicationJava): "working_directory": self.current_dir + "/java/empty", "webapp": self.testdir + "/java", + "unit_jars": self.testdir + "/no_such_dir", } }, } -- cgit From 7785c96c1aea16dee0ec17403fda01b4f5ba41b3 Mon Sep 17 00:00:00 2001 From: Axel Duch Date: Wed, 24 Jul 2019 13:47:35 +0300 Subject: Added routing based on request scheme. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Scheme matches exact string “http” or “https”. --- src/nxt_conf_validation.c | 29 +++++++++++++++ src/nxt_h1proto.c | 21 ++--------- src/nxt_http.h | 2 - src/nxt_http_request.c | 1 - src/nxt_http_route.c | 36 ++++++++++++++++++ test/test_routing.py | 94 +++++++++++++++++++++++++++++++++++++++++++++++ test/test_routing_tls.py | 58 +++++++++++++++++++++++++++++ 7 files changed, 221 insertions(+), 20 deletions(-) create mode 100644 test/test_routing_tls.py diff --git a/src/nxt_conf_validation.c b/src/nxt_conf_validation.c index bee82dd4..45c0eb41 100644 --- a/src/nxt_conf_validation.c +++ b/src/nxt_conf_validation.c @@ -72,6 +72,8 @@ static nxt_int_t nxt_conf_vldt_match_patterns_set(nxt_conf_validation_t *vldt, nxt_conf_value_t *value); static nxt_int_t nxt_conf_vldt_match_patterns_set_member( nxt_conf_validation_t *vldt, nxt_str_t *name, nxt_conf_value_t *value); +static nxt_int_t nxt_conf_vldt_match_scheme_pattern(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value, void *data); 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_app(nxt_conf_validation_t *vldt, @@ -214,6 +216,11 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_match_members[] = { &nxt_conf_vldt_match_patterns, NULL }, + { nxt_string("scheme"), + NXT_CONF_VLDT_STRING, + &nxt_conf_vldt_match_scheme_pattern, + NULL }, + { nxt_string("host"), NXT_CONF_VLDT_STRING | NXT_CONF_VLDT_ARRAY, &nxt_conf_vldt_match_patterns, @@ -819,6 +826,28 @@ nxt_conf_vldt_match_pattern(nxt_conf_validation_t *vldt, } +static nxt_int_t +nxt_conf_vldt_match_scheme_pattern(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value, void *data) +{ + nxt_str_t scheme; + + static const nxt_str_t http = nxt_string("http"); + static const nxt_str_t https = nxt_string("https"); + + nxt_conf_get_string(value, &scheme); + + if (nxt_strcasestr_eq(&scheme, &http) + || nxt_strcasestr_eq(&scheme, &https)) + { + return NXT_OK; + } + + return nxt_conf_vldt_error(vldt, "The \"scheme\" can either be " + "\"http\" or \"https\"."); +} + + static nxt_int_t nxt_conf_vldt_match_patterns_sets(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data) diff --git a/src/nxt_h1proto.c b/src/nxt_h1proto.c index 3a822042..6bc6c7ee 100644 --- a/src/nxt_h1proto.c +++ b/src/nxt_h1proto.c @@ -35,7 +35,6 @@ static void nxt_h1p_request_body_read(nxt_task_t *task, nxt_http_request_t *r); static void nxt_h1p_conn_request_body_read(nxt_task_t *task, void *obj, void *data); static void nxt_h1p_request_local_addr(nxt_task_t *task, nxt_http_request_t *r); -static void nxt_h1p_request_tls(nxt_task_t *task, nxt_http_request_t *r); static void nxt_h1p_request_header_send(nxt_task_t *task, nxt_http_request_t *r); static void nxt_h1p_request_send(nxt_task_t *task, nxt_http_request_t *r, @@ -104,13 +103,6 @@ const nxt_http_proto_local_addr_t nxt_http_proto_local_addr[3] = { }; -const nxt_http_proto_tls_t nxt_http_proto_tls[3] = { - nxt_h1p_request_tls, - NULL, - NULL, -}; - - const nxt_http_proto_header_send_t nxt_http_proto_header_send[3] = { nxt_h1p_request_header_send, NULL, @@ -448,6 +440,10 @@ nxt_h1p_conn_request_init(nxt_task_t *task, void *obj, void *data) r->remote = c->remote; +#if (NXT_TLS) + r->tls = c->u.tls; +#endif + ret = nxt_http_parse_request_init(&h1p->parser, r->mem_pool); if (nxt_fast_path(ret == NXT_OK)) { @@ -821,15 +817,6 @@ nxt_h1p_request_local_addr(nxt_task_t *task, nxt_http_request_t *r) } -static void -nxt_h1p_request_tls(nxt_task_t *task, nxt_http_request_t *r) -{ -#if (NXT_TLS) - r->tls = r->proto.h1->conn->u.tls; -#endif -} - - #define NXT_HTTP_LAST_SUCCESS \ (NXT_HTTP_OK + nxt_nitems(nxt_http_success) - 1) diff --git a/src/nxt_http.h b/src/nxt_http.h index 835cf66d..7398c9c1 100644 --- a/src/nxt_http.h +++ b/src/nxt_http.h @@ -172,7 +172,6 @@ typedef void (*nxt_http_proto_body_read_t)(nxt_task_t *task, nxt_http_request_t *r); typedef void (*nxt_http_proto_local_addr_t)(nxt_task_t *task, nxt_http_request_t *r); -typedef void (*nxt_http_proto_tls_t)(nxt_task_t *task, nxt_http_request_t *r); typedef void (*nxt_http_proto_header_send_t)(nxt_task_t *task, nxt_http_request_t *r); typedef void (*nxt_http_proto_send_t)(nxt_task_t *task, nxt_http_request_t *r, @@ -228,7 +227,6 @@ extern nxt_lvlhsh_t nxt_response_fields_hash; extern const nxt_http_proto_body_read_t nxt_http_proto_body_read[]; extern const nxt_http_proto_local_addr_t nxt_http_proto_local_addr[]; -extern const nxt_http_proto_tls_t nxt_http_proto_tls[]; extern const nxt_http_proto_header_send_t nxt_http_proto_header_send[]; extern const nxt_http_proto_send_t nxt_http_proto_send[]; extern const nxt_http_proto_body_bytes_sent_t nxt_http_proto_body_bytes_sent[]; diff --git a/src/nxt_http_request.c b/src/nxt_http_request.c index 1265c186..ce088acb 100644 --- a/src/nxt_http_request.c +++ b/src/nxt_http_request.c @@ -357,7 +357,6 @@ nxt_http_request_proto_info(nxt_task_t *task, nxt_http_request_t *r) { if (r->proto.any != NULL) { nxt_http_proto_local_addr[r->protocol](task, r); - nxt_http_proto_tls[r->protocol](task, r); } } diff --git a/src/nxt_http_route.c b/src/nxt_http_route.c index ade44666..0b665573 100644 --- a/src/nxt_http_route.c +++ b/src/nxt_http_route.c @@ -15,6 +15,7 @@ typedef enum { NXT_HTTP_ROUTE_HEADER, NXT_HTTP_ROUTE_ARGUMENT, NXT_HTTP_ROUTE_COOKIE, + NXT_HTTP_ROUTE_SCHEME, } nxt_http_route_object_t; @@ -41,6 +42,7 @@ typedef struct { nxt_conf_value_t *headers; nxt_conf_value_t *arguments; nxt_conf_value_t *cookies; + nxt_conf_value_t *scheme; } nxt_http_route_match_conf_t; @@ -197,6 +199,8 @@ static nxt_http_name_value_t *nxt_http_route_argument(nxt_array_t *array, u_char *end); static nxt_int_t nxt_http_route_test_argument(nxt_http_request_t *r, nxt_http_route_rule_t *rule, nxt_array_t *array); +static nxt_int_t nxt_http_route_scheme(nxt_http_request_t *r, + nxt_http_route_rule_t *rule); static nxt_int_t nxt_http_route_cookies(nxt_http_request_t *r, nxt_http_route_rule_t *rule); static nxt_array_t *nxt_http_route_cookies_parse(nxt_http_request_t *r); @@ -276,6 +280,11 @@ nxt_http_routes_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, static nxt_conf_map_t nxt_http_route_match_conf[] = { + { + nxt_string("scheme"), + NXT_CONF_MAP_PTR, + offsetof(nxt_http_route_match_conf_t, scheme) + }, { nxt_string("host"), NXT_CONF_MAP_PTR, @@ -412,6 +421,18 @@ nxt_http_route_match_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, test = &match->test[0]; + if (mtcf.scheme != NULL) { + rule = nxt_http_route_rule_create(task, mp, mtcf.scheme, 1, + NXT_HTTP_ROUTE_PATTERN_NOCASE); + if (rule == NULL) { + return NULL; + } + + rule->object = NXT_HTTP_ROUTE_SCHEME; + test->rule = rule; + test++; + } + if (mtcf.host != NULL) { rule = nxt_http_route_rule_create(task, mp, mtcf.host, 1, NXT_HTTP_ROUTE_PATTERN_LOWCASE); @@ -1125,6 +1146,9 @@ nxt_http_route_rule(nxt_http_request_t *r, nxt_http_route_rule_t *rule) case NXT_HTTP_ROUTE_COOKIE: return nxt_http_route_cookies(r, rule); + case NXT_HTTP_ROUTE_SCHEME: + return nxt_http_route_scheme(r, rule); + default: break; } @@ -1330,6 +1354,18 @@ nxt_http_route_test_argument(nxt_http_request_t *r, } +static nxt_int_t +nxt_http_route_scheme(nxt_http_request_t *r, nxt_http_route_rule_t *rule) +{ + nxt_bool_t tls, https; + + https = (rule->pattern[0].length1 == nxt_length("https")); + tls = (r->tls != NULL); + + return (tls == https); +} + + static nxt_int_t nxt_http_route_cookies(nxt_http_request_t *r, nxt_http_route_rule_t *rule) { diff --git a/test/test_routing.py b/test/test_routing.py index 40d9f786..6073877d 100644 --- a/test/test_routing.py +++ b/test/test_routing.py @@ -2641,5 +2641,99 @@ class TestRouting(TestApplicationProto): 'match cookies array 10', ) + def test_routes_match_scheme(self): + self.assertIn( + 'success', + self.route( + { + "match": {"scheme": "http"}, + "action": {"pass": "applications/empty"}, + } + ), + 'match scheme http configure', + ) + self.assertIn( + 'success', + self.route( + { + "match": {"scheme": "https"}, + "action": {"pass": "applications/empty"}, + } + ), + 'match scheme https configure', + ) + self.assertIn( + 'success', + self.route( + { + "match": {"scheme": "HtTp"}, + "action": {"pass": "applications/empty"}, + } + ), + 'match scheme http case insensitive configure', + ) + self.assertIn( + 'success', + self.route( + { + "match": {"scheme": "HtTpS"}, + "action": {"pass": "applications/empty"}, + } + ), + 'match scheme https case insensitive configure', + ) + + def test_routes_match_scheme_invalid(self): + self.assertIn( + 'error', + self.route( + { + "match": {"scheme": ["http"]}, + "action": {"pass": "applications/empty"}, + } + ), + 'scheme invalid type no arrays allowed', + ) + self.assertIn( + 'error', + self.route( + { + "match": {"scheme": "ftp"}, + "action": {"pass": "applications/empty"}, + } + ), + 'scheme invalid protocol 1', + ) + self.assertIn( + 'error', + self.route( + { + "match": {"scheme": "ws"}, + "action": {"pass": "applications/empty"}, + } + ), + 'scheme invalid protocol 2', + ) + self.assertIn( + 'error', + self.route( + { + "match": {"scheme": "*"}, + "action": {"pass": "applications/empty"}, + } + ), + 'scheme invalid no wildcard allowed', + ) + self.assertIn( + 'error', + self.route( + { + "match": {"scheme": ""}, + "action": {"pass": "applications/empty"}, + } + ), + 'scheme invalid empty', + ) + if __name__ == '__main__': TestRouting.main() diff --git a/test/test_routing_tls.py b/test/test_routing_tls.py new file mode 100644 index 00000000..433a303e --- /dev/null +++ b/test/test_routing_tls.py @@ -0,0 +1,58 @@ +from unit.applications.tls import TestApplicationTLS + + +class TestRoutingTLS(TestApplicationTLS): + prerequisites = ['python', 'openssl'] + + def test_routes_match_scheme(self): + self.certificate() + + self.assertIn( + 'success', + self.conf( + { + "listeners": { + "*:7080": {"pass": "routes"}, + "*:7081": { + "pass": "routes", + "tls": {"certificate": 'default'}, + }, + }, + "routes": [ + { + "match": {"scheme": "http"}, + "action": {"pass": "applications/empty"}, + }, + { + "match": {"scheme": "https"}, + "action": {"pass": "applications/204_no_content"}, + }, + ], + "applications": { + "empty": { + "type": "python", + "processes": {"spare": 0}, + "path": self.current_dir + "/python/empty", + "module": "wsgi", + }, + "204_no_content": { + "type": "python", + "processes": {"spare": 0}, + "path": self.current_dir + + "/python/204_no_content", + "module": "wsgi", + }, + }, + } + ), + 'scheme configure', + ) + + self.assertEqual(self.get()['status'], 200, 'scheme http') + self.assertEqual( + self.get_ssl(port=7081)['status'], 204, 'scheme https' + ) + + +if __name__ == '__main__': + TestRoutingTLS.main() -- cgit From fdf570f1d9561bfc349f24c87e1ac35afd2254e2 Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Thu, 1 Aug 2019 18:15:00 +0300 Subject: Tests: added delay before SIGQUIT in access_log partial tests. This change is necessary to avoid race between client connection close and Unit close. Also "read_timeout" value decreased to speed up tests. --- test/test_access_log.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/test/test_access_log.py b/test/test_access_log.py index 49497ad2..fbcc131f 100644 --- a/test/test_access_log.py +++ b/test/test_access_log.py @@ -180,7 +180,9 @@ Connection: close self.assertEqual(self.post()['status'], 200, 'init') - resp = self.http(b"""GE""", raw=True, read_timeout=5) + resp = self.http(b"""GE""", raw=True, read_timeout=1) + + time.sleep(1) self.stop() @@ -206,7 +208,9 @@ Connection: close self.assertEqual(self.post()['status'], 200, 'init') - resp = self.http(b"""GET / HTTP/1.1""", raw=True, read_timeout=5) + resp = self.http(b"""GET / HTTP/1.1""", raw=True, read_timeout=1) + + time.sleep(1) self.stop() @@ -219,7 +223,9 @@ Connection: close self.assertEqual(self.post()['status'], 200, 'init') - resp = self.http(b"""GET / HTTP/1.1\n""", raw=True, read_timeout=5) + resp = self.http(b"""GET / HTTP/1.1\n""", raw=True, read_timeout=1) + + time.sleep(1) self.stop() -- cgit From 17bb22a4e46b390f42af665d1e92d2e1a09c9b56 Mon Sep 17 00:00:00 2001 From: Igor Sysoev Date: Tue, 6 Aug 2019 15:29:39 +0300 Subject: Refactored HTTP protocol callback table. --- src/nxt_h1proto.c | 60 ++++++++++++-------------------------------------- src/nxt_http.h | 41 ++++++++++++++++------------------ src/nxt_http_request.c | 28 +++++++++++------------ src/nxt_router.c | 2 +- 4 files changed, 48 insertions(+), 83 deletions(-) diff --git a/src/nxt_h1proto.c b/src/nxt_h1proto.c index 6bc6c7ee..40cd370e 100644 --- a/src/nxt_h1proto.c +++ b/src/nxt_h1proto.c @@ -89,52 +89,19 @@ static const nxt_conn_state_t nxt_h1p_keepalive_state; static const nxt_conn_state_t nxt_h1p_close_state; -const nxt_http_proto_body_read_t nxt_http_proto_body_read[3] = { - nxt_h1p_request_body_read, - NULL, - NULL, -}; - - -const nxt_http_proto_local_addr_t nxt_http_proto_local_addr[3] = { - nxt_h1p_request_local_addr, - NULL, - NULL, -}; - - -const nxt_http_proto_header_send_t nxt_http_proto_header_send[3] = { - nxt_h1p_request_header_send, - NULL, - NULL, -}; - - -const nxt_http_proto_send_t nxt_http_proto_send[3] = { - nxt_h1p_request_send, - NULL, - NULL, -}; - - -const nxt_http_proto_body_bytes_sent_t nxt_http_proto_body_bytes_sent[3] = { - nxt_h1p_request_body_bytes_sent, - NULL, - NULL, -}; - - -const nxt_http_proto_discard_t nxt_http_proto_discard[3] = { - nxt_h1p_request_discard, - NULL, - NULL, -}; - - -const nxt_http_proto_close_t nxt_http_proto_close[3] = { - nxt_h1p_request_close, - NULL, - NULL, +const nxt_http_proto_table_t nxt_http_proto[3] = { + /* NXT_HTTP_PROTO_H1 */ + { + .body_read = nxt_h1p_request_body_read, + .local_addr = nxt_h1p_request_local_addr, + .header_send = nxt_h1p_request_header_send, + .send = nxt_h1p_request_send, + .body_bytes_sent = nxt_h1p_request_body_bytes_sent, + .discard = nxt_h1p_request_discard, + .close = nxt_h1p_request_close, + }, + /* NXT_HTTP_PROTO_H2 */ + /* NXT_HTTP_PROTO_DEVNULL */ }; @@ -438,6 +405,7 @@ nxt_h1p_conn_request_init(nxt_task_t *task, void *obj, void *data) h1p->request = r; r->proto.h1 = h1p; + /* r->protocol = NXT_HTTP_PROTO_H1 is done by zeroing. */ r->remote = c->remote; #if (NXT_TLS) diff --git a/src/nxt_http.h b/src/nxt_http.h index 7398c9c1..d9916a31 100644 --- a/src/nxt_http.h +++ b/src/nxt_http.h @@ -46,6 +46,13 @@ typedef enum { } nxt_http_te_t; +typedef enum { + NXT_HTTP_PROTO_H1 = 0, + NXT_HTTP_PROTO_H2, + NXT_HTTP_PROTO_DEVNULL, +} nxt_http_protocol_t; + + typedef struct { nxt_work_handler_t ready_handler; nxt_work_handler_t error_handler; @@ -145,7 +152,7 @@ struct nxt_http_request_s { nxt_http_status_t status:16; uint8_t pass_count; /* 8 bits */ - uint8_t protocol; /* 2 bits */ + nxt_http_protocol_t protocol:8; /* 2 bits */ uint8_t logged; /* 1 bit */ uint8_t header_sent; /* 1 bit */ uint8_t error; /* 1 bit */ @@ -168,20 +175,16 @@ struct nxt_http_pass_s { }; -typedef void (*nxt_http_proto_body_read_t)(nxt_task_t *task, - nxt_http_request_t *r); -typedef void (*nxt_http_proto_local_addr_t)(nxt_task_t *task, - nxt_http_request_t *r); -typedef void (*nxt_http_proto_header_send_t)(nxt_task_t *task, - nxt_http_request_t *r); -typedef void (*nxt_http_proto_send_t)(nxt_task_t *task, nxt_http_request_t *r, - nxt_buf_t *out); -typedef nxt_off_t (*nxt_http_proto_body_bytes_sent_t)(nxt_task_t *task, - nxt_http_proto_t proto); -typedef void (*nxt_http_proto_discard_t)(nxt_task_t *task, - nxt_http_request_t *r, nxt_buf_t *last); -typedef void (*nxt_http_proto_close_t)(nxt_task_t *task, - nxt_http_proto_t proto, nxt_socket_conf_joint_t *joint); +typedef struct { + void (*body_read)(nxt_task_t *task, nxt_http_request_t *r); + void (*local_addr)(nxt_task_t *task, nxt_http_request_t *r); + void (*header_send)(nxt_task_t *task, nxt_http_request_t *r); + void (*send)(nxt_task_t *task, nxt_http_request_t *r, nxt_buf_t *out); + nxt_off_t (*body_bytes_sent)(nxt_task_t *task, nxt_http_proto_t proto); + void (*discard)(nxt_task_t *task, nxt_http_request_t *r, nxt_buf_t *last); + void (*close)(nxt_task_t *task, nxt_http_proto_t proto, + nxt_socket_conf_joint_t *joint); +} nxt_http_proto_table_t; nxt_int_t nxt_http_init(nxt_task_t *task, nxt_runtime_t *rt); @@ -225,13 +228,7 @@ extern nxt_time_string_t nxt_http_date_cache; extern nxt_lvlhsh_t nxt_response_fields_hash; -extern const nxt_http_proto_body_read_t nxt_http_proto_body_read[]; -extern const nxt_http_proto_local_addr_t nxt_http_proto_local_addr[]; -extern const nxt_http_proto_header_send_t nxt_http_proto_header_send[]; -extern const nxt_http_proto_send_t nxt_http_proto_send[]; -extern const nxt_http_proto_body_bytes_sent_t nxt_http_proto_body_bytes_sent[]; -extern const nxt_http_proto_discard_t nxt_http_proto_discard[]; -extern const nxt_http_proto_close_t nxt_http_proto_close[]; +extern const nxt_http_proto_table_t nxt_http_proto[]; #endif /* _NXT_HTTP_H_INCLUDED_ */ diff --git a/src/nxt_http_request.c b/src/nxt_http_request.c index ce088acb..1ab22223 100644 --- a/src/nxt_http_request.c +++ b/src/nxt_http_request.c @@ -355,8 +355,8 @@ nxt_http_request_application(nxt_task_t *task, nxt_http_request_t *r, static void nxt_http_request_proto_info(nxt_task_t *task, nxt_http_request_t *r) { - if (r->proto.any != NULL) { - nxt_http_proto_local_addr[r->protocol](task, r); + if (nxt_fast_path(r->proto.any != NULL)) { + nxt_http_proto[r->protocol].local_addr(task, r); } } @@ -364,8 +364,8 @@ nxt_http_request_proto_info(nxt_task_t *task, nxt_http_request_t *r) void nxt_http_request_read_body(nxt_task_t *task, nxt_http_request_t *r) { - if (r->proto.any != NULL) { - nxt_http_proto_body_read[r->protocol](task, r); + if (nxt_fast_path(r->proto.any != NULL)) { + nxt_http_proto[r->protocol].body_read(task, r); } } @@ -431,8 +431,8 @@ nxt_http_request_header_send(nxt_task_t *task, nxt_http_request_t *r) r->resp.content_length = content_length; } - if (r->proto.any != NULL) { - nxt_http_proto_header_send[r->protocol](task, r); + if (nxt_fast_path(r->proto.any != NULL)) { + nxt_http_proto[r->protocol].header_send(task, r); } return; @@ -446,8 +446,8 @@ fail: void nxt_http_request_send(nxt_task_t *task, nxt_http_request_t *r, nxt_buf_t *out) { - if (r->proto.any != NULL) { - nxt_http_proto_send[r->protocol](task, r, out); + if (nxt_fast_path(r->proto.any != NULL)) { + nxt_http_proto[r->protocol].send(task, r, out); } } @@ -524,8 +524,8 @@ nxt_http_request_error_handler(nxt_task_t *task, void *obj, void *data) r->error = 1; - if (proto.any != NULL) { - nxt_http_proto_discard[r->protocol](task, r, nxt_http_buf_last(r)); + if (nxt_fast_path(proto.any != NULL)) { + nxt_http_proto[r->protocol].discard(task, r, nxt_http_buf_last(r)); } } @@ -535,7 +535,7 @@ nxt_http_request_close_handler(nxt_task_t *task, void *obj, void *data) { nxt_http_proto_t proto; nxt_http_request_t *r; - nxt_http_proto_close_t handler; + nxt_http_protocol_t protocol; nxt_socket_conf_joint_t *conf; nxt_router_access_log_t *access_log; @@ -556,13 +556,13 @@ nxt_http_request_close_handler(nxt_task_t *task, void *obj, void *data) } } - handler = nxt_http_proto_close[r->protocol]; + protocol = r->protocol; r->proto.any = NULL; nxt_mp_release(r->mem_pool); - if (proto.any != NULL) { - handler(task, proto, conf); + if (nxt_fast_path(proto.any != NULL)) { + nxt_http_proto[protocol].close(task, proto, conf); } } diff --git a/src/nxt_router.c b/src/nxt_router.c index 018cd4f4..f43b9a9e 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -3058,7 +3058,7 @@ nxt_router_access_log_writer(nxt_task_t *task, nxt_http_request_t *r, *p++ = ' '; - bytes = nxt_http_proto_body_bytes_sent[r->protocol](task, r->proto); + bytes = nxt_http_proto[r->protocol].body_bytes_sent(task, r->proto); p = nxt_sprintf(p, p + NXT_OFF_T_LEN, "%O", bytes); -- cgit From c7210eaa5a15083715cac574cce055b94860e70e Mon Sep 17 00:00:00 2001 From: Igor Sysoev Date: Tue, 6 Aug 2019 15:29:40 +0300 Subject: nxt_h1proto_t definition was moved to h1proto implementation. --- src/nxt_h1proto.c | 20 ++++++++++++++++++++ src/nxt_http.h | 20 +------------------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/nxt_h1proto.c b/src/nxt_h1proto.c index 40cd370e..b0bd39e1 100644 --- a/src/nxt_h1proto.c +++ b/src/nxt_h1proto.c @@ -8,6 +8,26 @@ #include +struct nxt_h1proto_s { + nxt_http_request_parse_t parser; + + uint8_t nbuffers; + uint8_t keepalive; /* 1 bit */ + uint8_t chunked; /* 1 bit */ + nxt_http_te_t transfer_encoding:8; /* 2 bits */ + + uint32_t header_size; + + nxt_http_request_t *request; + nxt_buf_t *buffers; + /* + * All fields before the conn field will + * be zeroed in a keep-alive connection. + */ + nxt_conn_t *conn; +}; + + /* * nxt_http_conn_ and nxt_h1p_conn_ prefixes are used for connection handlers. * nxt_h1p_idle_ prefix is used for idle connection handlers. diff --git a/src/nxt_http.h b/src/nxt_http.h index d9916a31..c1a230ec 100644 --- a/src/nxt_http.h +++ b/src/nxt_http.h @@ -59,25 +59,7 @@ typedef struct { } nxt_http_request_state_t; -typedef struct { - nxt_http_request_parse_t parser; - - uint8_t nbuffers; - uint8_t keepalive; /* 1 bit */ - uint8_t chunked; /* 1 bit */ - nxt_http_te_t transfer_encoding:8; /* 2 bits */ - - uint32_t header_size; - - nxt_http_request_t *request; - nxt_buf_t *buffers; - /* - * All fields before the conn field will - * be zeroed in a keep-alive connection. - */ - nxt_conn_t *conn; -} nxt_h1proto_t; - +typedef struct nxt_h1proto_s nxt_h1proto_t; typedef union { void *any; -- cgit From 7fd9444728e1e509630f5ba0f50e7f9da150369c Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Tue, 6 Aug 2019 16:24:11 +0300 Subject: Node.js: returning "this" from writeHead() to allow chaining. In Node.js version 11.10.0 and later, the writeHead() function returns "this". --- src/nodejs/unit-http/http_server.js | 2 ++ test/node/404/app.js | 3 +-- test/node/basic/app.js | 4 ++-- test/node/double_end/app.js | 3 +-- test/node/mirror/app.js | 4 ++-- test/node/promise_handler/app.js | 3 +-- test/node/status_message/app.js | 3 +-- test/node/variables/app.js | 3 +-- test/node/write_before_write_head/app.js | 3 +-- test/node/write_buffer/app.js | 4 ++-- test/node/write_return/app.js | 4 ++-- 11 files changed, 16 insertions(+), 20 deletions(-) diff --git a/src/nodejs/unit-http/http_server.js b/src/nodejs/unit-http/http_server.js index ae8e204a..0fe5dd34 100755 --- a/src/nodejs/unit-http/http_server.js +++ b/src/nodejs/unit-http/http_server.js @@ -195,6 +195,8 @@ function writeHead(statusCode, reason, obj) { } } } + + return this; }; /* diff --git a/test/node/404/app.js b/test/node/404/app.js index 9600d486..587c432d 100755 --- a/test/node/404/app.js +++ b/test/node/404/app.js @@ -3,6 +3,5 @@ var fs = require('fs'); require('unit-http').createServer(function (req, res) { - res.writeHead(404, {}); - res.end(fs.readFileSync('404.html')); + res.writeHead(404, {}).end(fs.readFileSync('404.html')); }).listen(7080); diff --git a/test/node/basic/app.js b/test/node/basic/app.js index bc8d570a..7820c474 100755 --- a/test/node/basic/app.js +++ b/test/node/basic/app.js @@ -1,6 +1,6 @@ #!/usr/bin/env node require('unit-http').createServer(function (req, res) { - res.writeHead(200, {'Content-Length': 12, 'Content-Type': 'text/plain'}); - res.end('Hello World\n'); + res.writeHead(200, {'Content-Length': 12, 'Content-Type': 'text/plain'}) + .end('Hello World\n'); }).listen(7080); diff --git a/test/node/double_end/app.js b/test/node/double_end/app.js index d8280917..63912097 100755 --- a/test/node/double_end/app.js +++ b/test/node/double_end/app.js @@ -1,6 +1,5 @@ #!/usr/bin/env node require('unit-http').createServer(function (req, res) { - res.end(); - res.end(); + res.end().end(); }).listen(7080); diff --git a/test/node/mirror/app.js b/test/node/mirror/app.js index abcb87cb..1488917e 100755 --- a/test/node/mirror/app.js +++ b/test/node/mirror/app.js @@ -6,7 +6,7 @@ require('unit-http').createServer(function (req, res) { body += chunk.toString(); }); req.on('end', () => { - res.writeHead(200, {'Content-Length': Buffer.byteLength(body)}); - res.end(body); + res.writeHead(200, {'Content-Length': Buffer.byteLength(body)}) + .end(body); }); }).listen(7080); diff --git a/test/node/promise_handler/app.js b/test/node/promise_handler/app.js index 60b0c3bb..51c3666b 100755 --- a/test/node/promise_handler/app.js +++ b/test/node/promise_handler/app.js @@ -6,8 +6,7 @@ require('unit-http').createServer(function (req, res) { res.end(); if (req.headers['x-write-call']) { - res.writeHead(200, {'Content-Type': 'text/plain'}); - res.write('blah'); + res.writeHead(200, {'Content-Type': 'text/plain'}).write('blah'); } Promise.resolve().then(() => { diff --git a/test/node/status_message/app.js b/test/node/status_message/app.js index 4f3b064a..e8a798dd 100755 --- a/test/node/status_message/app.js +++ b/test/node/status_message/app.js @@ -1,6 +1,5 @@ #!/usr/bin/env node require('unit-http').createServer(function (req, res) { - res.writeHead(200, 'blah', {'Content-Type': 'text/plain'}); - res.end(); + res.writeHead(200, 'blah', {'Content-Type': 'text/plain'}).end(); }).listen(7080); diff --git a/test/node/variables/app.js b/test/node/variables/app.js index 4ed94d09..d8cdc20c 100755 --- a/test/node/variables/app.js +++ b/test/node/variables/app.js @@ -14,7 +14,6 @@ require('unit-http').createServer(function (req, res) { res.setHeader('Content-Type', req.headers['content-type']); res.setHeader('Custom-Header', req.headers['custom-header']); res.setHeader('Http-Host', req.headers['host']); - res.writeHead(200, {}); - res.end(body); + res.writeHead(200, {}).end(body); }); }).listen(7080); diff --git a/test/node/write_before_write_head/app.js b/test/node/write_before_write_head/app.js index 6e3fb9a9..724b0efb 100755 --- a/test/node/write_before_write_head/app.js +++ b/test/node/write_before_write_head/app.js @@ -2,6 +2,5 @@ require('unit-http').createServer(function (req, res) { res.write('blah'); - res.writeHead(200, {'Content-Type': 'text/plain'}); - res.end(); + res.writeHead(200, {'Content-Type': 'text/plain'}).end(); }).listen(7080); diff --git a/test/node/write_buffer/app.js b/test/node/write_buffer/app.js index f41de2a1..a7623523 100755 --- a/test/node/write_buffer/app.js +++ b/test/node/write_buffer/app.js @@ -1,6 +1,6 @@ #!/usr/bin/env node require('unit-http').createServer(function (req, res) { - res.writeHead(200, {'Content-Type': 'text/plain'}); - res.end(new Buffer([0x62, 0x75, 0x66, 0x66, 0x65, 0x72])); + res.writeHead(200, {'Content-Type': 'text/plain'}) + .end(new Buffer([0x62, 0x75, 0x66, 0x66, 0x65, 0x72])); }).listen(7080); diff --git a/test/node/write_return/app.js b/test/node/write_return/app.js index 3ae967c6..82dfbc6e 100755 --- a/test/node/write_return/app.js +++ b/test/node/write_return/app.js @@ -1,6 +1,6 @@ #!/usr/bin/env node require('unit-http').createServer(function (req, res) { - res.writeHead(200, {'Content-Type': 'text/plain'}); - res.end(res.write('body').toString()); + res.writeHead(200, {'Content-Type': 'text/plain'}) + .end(res.write('body').toString()); }).listen(7080); -- cgit From 274260bd2824f11d5007587e0f3bf897149d7107 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Tue, 6 Aug 2019 17:13:13 +0300 Subject: Router: allowed empty configurations. Now it's possible to delete "listeners" and "applications" objects. This closes #187 issue on GitHub. --- src/nxt_router.c | 380 +++++++++++++++++++++++++++---------------------------- 1 file changed, 189 insertions(+), 191 deletions(-) diff --git a/src/nxt_router.c b/src/nxt_router.c index f43b9a9e..df2557fc 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -1432,177 +1432,177 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, tmcf->router_conf->threads = nxt_ncpu; } - applications = nxt_conf_get_path(conf, &applications_path); - if (applications == NULL) { - nxt_alert(task, "no \"applications\" block"); - return NXT_ERROR; - } - router = tmcf->router_conf->router; - next = 0; - - for ( ;; ) { - application = nxt_conf_next_object_member(applications, &name, &next); - if (application == NULL) { - break; - } - - nxt_debug(task, "application \"%V\"", &name); + applications = nxt_conf_get_path(conf, &applications_path); - size = nxt_conf_json_length(application, NULL); + if (applications != NULL) { + next = 0; - app = nxt_malloc(sizeof(nxt_app_t) + name.length + size); - if (app == NULL) { - goto fail; - } - - nxt_memzero(app, sizeof(nxt_app_t)); + for ( ;; ) { + application = nxt_conf_next_object_member(applications, &name, &next); + if (application == NULL) { + break; + } - app->name.start = nxt_pointer_to(app, sizeof(nxt_app_t)); - app->conf.start = nxt_pointer_to(app, sizeof(nxt_app_t) + name.length); + nxt_debug(task, "application \"%V\"", &name); - p = nxt_conf_json_print(app->conf.start, application, NULL); - app->conf.length = p - app->conf.start; + size = nxt_conf_json_length(application, NULL); - nxt_assert(app->conf.length <= size); + app = nxt_malloc(sizeof(nxt_app_t) + name.length + size); + if (app == NULL) { + goto fail; + } - nxt_debug(task, "application conf \"%V\"", &app->conf); + nxt_memzero(app, sizeof(nxt_app_t)); - prev = nxt_router_app_find(&router->apps, &name); + app->name.start = nxt_pointer_to(app, sizeof(nxt_app_t)); + app->conf.start = nxt_pointer_to(app, sizeof(nxt_app_t) + + name.length); - if (prev != NULL && nxt_strstr_eq(&app->conf, &prev->conf)) { - nxt_free(app); + p = nxt_conf_json_print(app->conf.start, application, NULL); + app->conf.length = p - app->conf.start; - nxt_queue_remove(&prev->link); - nxt_queue_insert_tail(&tmcf->previous, &prev->link); - continue; - } + nxt_assert(app->conf.length <= size); - apcf.processes = 1; - apcf.max_processes = 1; - apcf.spare_processes = 0; - apcf.timeout = 0; - apcf.res_timeout = 1000; - apcf.idle_timeout = 15000; - apcf.requests = 0; - apcf.limits_value = NULL; - apcf.processes_value = NULL; - - app_joint = nxt_malloc(sizeof(nxt_app_joint_t)); - if (nxt_slow_path(app_joint == NULL)) { - goto app_fail; - } + nxt_debug(task, "application conf \"%V\"", &app->conf); - nxt_memzero(app_joint, sizeof(nxt_app_joint_t)); + prev = nxt_router_app_find(&router->apps, &name); - ret = nxt_conf_map_object(mp, application, nxt_router_app_conf, - nxt_nitems(nxt_router_app_conf), &apcf); - if (ret != NXT_OK) { - nxt_alert(task, "application map error"); - goto app_fail; - } + if (prev != NULL && nxt_strstr_eq(&app->conf, &prev->conf)) { + nxt_free(app); - if (apcf.limits_value != NULL) { + nxt_queue_remove(&prev->link); + nxt_queue_insert_tail(&tmcf->previous, &prev->link); + continue; + } - if (nxt_conf_type(apcf.limits_value) != NXT_CONF_OBJECT) { - nxt_alert(task, "application limits is not object"); + apcf.processes = 1; + apcf.max_processes = 1; + apcf.spare_processes = 0; + apcf.timeout = 0; + apcf.res_timeout = 1000; + apcf.idle_timeout = 15000; + apcf.requests = 0; + apcf.limits_value = NULL; + apcf.processes_value = NULL; + + app_joint = nxt_malloc(sizeof(nxt_app_joint_t)); + if (nxt_slow_path(app_joint == NULL)) { goto app_fail; } - ret = nxt_conf_map_object(mp, apcf.limits_value, - nxt_router_app_limits_conf, - nxt_nitems(nxt_router_app_limits_conf), - &apcf); + nxt_memzero(app_joint, sizeof(nxt_app_joint_t)); + + ret = nxt_conf_map_object(mp, application, nxt_router_app_conf, + nxt_nitems(nxt_router_app_conf), &apcf); if (ret != NXT_OK) { - nxt_alert(task, "application limits map error"); + nxt_alert(task, "application map error"); goto app_fail; } - } - if (apcf.processes_value != NULL - && nxt_conf_type(apcf.processes_value) == NXT_CONF_OBJECT) - { - ret = nxt_conf_map_object(mp, apcf.processes_value, - nxt_router_app_processes_conf, - nxt_nitems(nxt_router_app_processes_conf), - &apcf); - if (ret != NXT_OK) { - nxt_alert(task, "application processes map error"); - goto app_fail; + if (apcf.limits_value != NULL) { + + if (nxt_conf_type(apcf.limits_value) != NXT_CONF_OBJECT) { + nxt_alert(task, "application limits is not object"); + goto app_fail; + } + + ret = nxt_conf_map_object(mp, apcf.limits_value, + nxt_router_app_limits_conf, + nxt_nitems(nxt_router_app_limits_conf), + &apcf); + if (ret != NXT_OK) { + nxt_alert(task, "application limits map error"); + goto app_fail; + } } - } else { - apcf.max_processes = apcf.processes; - apcf.spare_processes = apcf.processes; - } + if (apcf.processes_value != NULL + && nxt_conf_type(apcf.processes_value) == NXT_CONF_OBJECT) + { + ret = nxt_conf_map_object(mp, apcf.processes_value, + nxt_router_app_processes_conf, + nxt_nitems(nxt_router_app_processes_conf), + &apcf); + if (ret != NXT_OK) { + nxt_alert(task, "application processes map error"); + goto app_fail; + } - nxt_debug(task, "application type: %V", &apcf.type); - nxt_debug(task, "application processes: %D", apcf.processes); - nxt_debug(task, "application request timeout: %M", apcf.timeout); - nxt_debug(task, "application reschedule timeout: %M", apcf.res_timeout); - nxt_debug(task, "application requests: %D", apcf.requests); + } else { + apcf.max_processes = apcf.processes; + apcf.spare_processes = apcf.processes; + } - lang = nxt_app_lang_module(task->thread->runtime, &apcf.type); + nxt_debug(task, "application type: %V", &apcf.type); + nxt_debug(task, "application processes: %D", apcf.processes); + nxt_debug(task, "application request timeout: %M", apcf.timeout); + nxt_debug(task, "application reschedule timeout: %M", + apcf.res_timeout); + nxt_debug(task, "application requests: %D", apcf.requests); - if (lang == NULL) { - nxt_alert(task, "unknown application type: \"%V\"", &apcf.type); - goto app_fail; - } + lang = nxt_app_lang_module(task->thread->runtime, &apcf.type); - nxt_debug(task, "application language module: \"%s\"", lang->file); + if (lang == NULL) { + nxt_alert(task, "unknown application type: \"%V\"", &apcf.type); + goto app_fail; + } - ret = nxt_thread_mutex_create(&app->mutex); - if (ret != NXT_OK) { - goto app_fail; - } + nxt_debug(task, "application language module: \"%s\"", lang->file); - nxt_queue_init(&app->ports); - nxt_queue_init(&app->spare_ports); - nxt_queue_init(&app->idle_ports); - nxt_queue_init(&app->requests); - nxt_queue_init(&app->pending); + ret = nxt_thread_mutex_create(&app->mutex); + if (ret != NXT_OK) { + goto app_fail; + } - app->name.length = name.length; - nxt_memcpy(app->name.start, name.start, name.length); + nxt_queue_init(&app->ports); + nxt_queue_init(&app->spare_ports); + nxt_queue_init(&app->idle_ports); + nxt_queue_init(&app->requests); + nxt_queue_init(&app->pending); - app->type = lang->type; - app->max_processes = apcf.max_processes; - app->spare_processes = apcf.spare_processes; - app->max_pending_processes = apcf.spare_processes - ? apcf.spare_processes : 1; - app->timeout = apcf.timeout; - app->res_timeout = apcf.res_timeout * 1000000; - app->idle_timeout = apcf.idle_timeout; - app->max_pending_responses = 2; - app->max_requests = apcf.requests; + app->name.length = name.length; + nxt_memcpy(app->name.start, name.start, name.length); - engine = task->thread->engine; + app->type = lang->type; + app->max_processes = apcf.max_processes; + app->spare_processes = apcf.spare_processes; + app->max_pending_processes = apcf.spare_processes + ? apcf.spare_processes : 1; + app->timeout = apcf.timeout; + app->res_timeout = apcf.res_timeout * 1000000; + app->idle_timeout = apcf.idle_timeout; + app->max_pending_responses = 2; + app->max_requests = apcf.requests; - app->engine = engine; + engine = task->thread->engine; - app->adjust_idle_work.handler = nxt_router_adjust_idle_timer; - app->adjust_idle_work.task = &engine->task; - app->adjust_idle_work.obj = app; + app->engine = engine; - nxt_queue_insert_tail(&tmcf->apps, &app->link); + app->adjust_idle_work.handler = nxt_router_adjust_idle_timer; + app->adjust_idle_work.task = &engine->task; + app->adjust_idle_work.obj = app; - nxt_router_app_use(task, app, 1); + nxt_queue_insert_tail(&tmcf->apps, &app->link); + + nxt_router_app_use(task, app, 1); - app->joint = app_joint; + app->joint = app_joint; - app_joint->use_count = 1; - app_joint->app = app; + app_joint->use_count = 1; + app_joint->app = app; - app_joint->idle_timer.bias = NXT_TIMER_DEFAULT_BIAS; - app_joint->idle_timer.work_queue = &engine->fast_work_queue; - app_joint->idle_timer.handler = nxt_router_app_idle_timeout; - app_joint->idle_timer.task = &engine->task; - app_joint->idle_timer.log = app_joint->idle_timer.task->log; + app_joint->idle_timer.bias = NXT_TIMER_DEFAULT_BIAS; + app_joint->idle_timer.work_queue = &engine->fast_work_queue; + app_joint->idle_timer.handler = nxt_router_app_idle_timeout; + app_joint->idle_timer.task = &engine->task; + app_joint->idle_timer.log = app_joint->idle_timer.task->log; - app_joint->free_app_work.handler = nxt_router_free_app; - app_joint->free_app_work.task = &engine->task; - app_joint->free_app_work.obj = app_joint; + app_joint->free_app_work.handler = nxt_router_free_app; + app_joint->free_app_work.task = &engine->task; + app_joint->free_app_work.obj = app_joint; + } } routes_conf = nxt_conf_get_path(conf, &routes_path); @@ -1623,86 +1623,84 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, #endif listeners = nxt_conf_get_path(conf, &listeners_path); - if (listeners == NULL) { - nxt_alert(task, "no \"listeners\" block"); - return NXT_ERROR; - } - next = 0; + if (listeners != NULL) { + next = 0; - for ( ;; ) { - listener = nxt_conf_next_object_member(listeners, &name, &next); - if (listener == NULL) { - break; - } - - skcf = nxt_router_socket_conf(task, tmcf, &name); - if (skcf == NULL) { - goto fail; - } + for ( ;; ) { + listener = nxt_conf_next_object_member(listeners, &name, &next); + if (listener == NULL) { + break; + } - nxt_memzero(&lscf, sizeof(lscf)); + skcf = nxt_router_socket_conf(task, tmcf, &name); + if (skcf == NULL) { + goto fail; + } - ret = nxt_conf_map_object(mp, listener, nxt_router_listener_conf, - nxt_nitems(nxt_router_listener_conf), &lscf); - if (ret != NXT_OK) { - nxt_alert(task, "listener map error"); - goto fail; - } + nxt_memzero(&lscf, sizeof(lscf)); - nxt_debug(task, "application: %V", &lscf.application); - - // STUB, default values if http block is not defined. - skcf->header_buffer_size = 2048; - skcf->large_header_buffer_size = 8192; - skcf->large_header_buffers = 4; - skcf->body_buffer_size = 16 * 1024; - skcf->max_body_size = 8 * 1024 * 1024; - skcf->idle_timeout = 180 * 1000; - skcf->header_read_timeout = 30 * 1000; - skcf->body_read_timeout = 30 * 1000; - skcf->send_timeout = 30 * 1000; - - if (http != NULL) { - ret = nxt_conf_map_object(mp, http, nxt_router_http_conf, - nxt_nitems(nxt_router_http_conf), skcf); + ret = nxt_conf_map_object(mp, listener, nxt_router_listener_conf, + nxt_nitems(nxt_router_listener_conf), + &lscf); if (ret != NXT_OK) { - nxt_alert(task, "http map error"); + nxt_alert(task, "listener map error"); goto fail; } - } - -#if (NXT_TLS) - value = nxt_conf_get_path(listener, &certificate_path); + nxt_debug(task, "application: %V", &lscf.application); + + // STUB, default values if http block is not defined. + skcf->header_buffer_size = 2048; + skcf->large_header_buffer_size = 8192; + skcf->large_header_buffers = 4; + skcf->body_buffer_size = 16 * 1024; + skcf->max_body_size = 8 * 1024 * 1024; + skcf->idle_timeout = 180 * 1000; + skcf->header_read_timeout = 30 * 1000; + skcf->body_read_timeout = 30 * 1000; + skcf->send_timeout = 30 * 1000; + + if (http != NULL) { + ret = nxt_conf_map_object(mp, http, nxt_router_http_conf, + nxt_nitems(nxt_router_http_conf), + skcf); + if (ret != NXT_OK) { + nxt_alert(task, "http map error"); + goto fail; + } + } - if (value != NULL) { - nxt_conf_get_string(value, &name); +#if (NXT_TLS) + value = nxt_conf_get_path(listener, &certificate_path); - tls = nxt_mp_get(mp, sizeof(nxt_router_tlssock_t)); - if (nxt_slow_path(tls == NULL)) { - goto fail; - } + if (value != NULL) { + nxt_conf_get_string(value, &name); - tls->name = name; - tls->conf = skcf; + tls = nxt_mp_get(mp, sizeof(nxt_router_tlssock_t)); + if (nxt_slow_path(tls == NULL)) { + goto fail; + } - nxt_queue_insert_tail(&tmcf->tls, &tls->link); - } + tls->name = name; + tls->conf = skcf; + nxt_queue_insert_tail(&tmcf->tls, &tls->link); + } #endif - skcf->listen->handler = nxt_http_conn_init; - skcf->router_conf = tmcf->router_conf; - skcf->router_conf->count++; + skcf->listen->handler = nxt_http_conn_init; + skcf->router_conf = tmcf->router_conf; + skcf->router_conf->count++; - if (lscf.pass.length != 0) { - skcf->pass = nxt_http_pass_create(task, tmcf, &lscf.pass); + if (lscf.pass.length != 0) { + skcf->pass = nxt_http_pass_create(task, tmcf, &lscf.pass); - /* COMPATIBILITY: listener application. */ - } else if (lscf.application.length > 0) { - skcf->pass = nxt_http_pass_application(task, tmcf, - &lscf.application); + /* COMPATIBILITY: listener application. */ + } else if (lscf.application.length > 0) { + skcf->pass = nxt_http_pass_application(task, tmcf, + &lscf.application); + } } } -- cgit From c8c259b9728c57e70042d7630045e5b043f46e5b Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Tue, 6 Aug 2019 18:25:13 +0300 Subject: Tests: rerun tests for each module version. Rerun supported for Python, PHP, Perl, Ruby, and Java modules. --- test/unit/applications/lang/java.py | 4 +++- test/unit/applications/lang/perl.py | 4 +++- test/unit/applications/lang/php.py | 4 +++- test/unit/applications/lang/python.py | 4 +++- test/unit/applications/lang/ruby.py | 4 +++- test/unit/main.py | 26 +++++++++++++++++++++++++- 6 files changed, 40 insertions(+), 6 deletions(-) diff --git a/test/unit/applications/lang/java.py b/test/unit/applications/lang/java.py index c4390f15..ec492d06 100644 --- a/test/unit/applications/lang/java.py +++ b/test/unit/applications/lang/java.py @@ -5,6 +5,8 @@ from unit.applications.proto import TestApplicationProto class TestApplicationJava(TestApplicationProto): + application_type = "java" + def load(self, script, name='app'): app_path = self.testdir + '/java' @@ -64,7 +66,7 @@ class TestApplicationJava(TestApplicationProto): "applications": { script: { "unit_jars": self.pardir + '/build', - "type": "java", + "type": self.application_type, "processes": {"spare": 0}, "working_directory": script_path, "webapp": app_path, diff --git a/test/unit/applications/lang/perl.py b/test/unit/applications/lang/perl.py index 8aaf33a4..79df2cfa 100644 --- a/test/unit/applications/lang/perl.py +++ b/test/unit/applications/lang/perl.py @@ -2,6 +2,8 @@ from unit.applications.proto import TestApplicationProto class TestApplicationPerl(TestApplicationProto): + application_type = "perl" + def load(self, script, name='psgi.pl'): script_path = self.current_dir + '/perl/' + script @@ -10,7 +12,7 @@ class TestApplicationPerl(TestApplicationProto): "listeners": {"*:7080": {"pass": "applications/" + script}}, "applications": { script: { - "type": "perl", + "type": self.application_type, "processes": {"spare": 0}, "working_directory": script_path, "script": script_path + '/' + name, diff --git a/test/unit/applications/lang/php.py b/test/unit/applications/lang/php.py index 99d84164..9c54368d 100644 --- a/test/unit/applications/lang/php.py +++ b/test/unit/applications/lang/php.py @@ -2,6 +2,8 @@ from unit.applications.proto import TestApplicationProto class TestApplicationPHP(TestApplicationProto): + application_type = "php" + def load(self, script, name='index.php'): script_path = self.current_dir + '/php/' + script @@ -10,7 +12,7 @@ class TestApplicationPHP(TestApplicationProto): "listeners": {"*:7080": {"pass": "applications/" + script}}, "applications": { script: { - "type": "php", + "type": self.application_type, "processes": {"spare": 0}, "root": script_path, "working_directory": script_path, diff --git a/test/unit/applications/lang/python.py b/test/unit/applications/lang/python.py index d1b5b839..ded76cb6 100644 --- a/test/unit/applications/lang/python.py +++ b/test/unit/applications/lang/python.py @@ -2,6 +2,8 @@ from unit.applications.proto import TestApplicationProto class TestApplicationPython(TestApplicationProto): + application_type = "python" + def load(self, script, name=None): if name is None: name = script @@ -13,7 +15,7 @@ class TestApplicationPython(TestApplicationProto): "listeners": {"*:7080": {"pass": "applications/" + name}}, "applications": { name: { - "type": "python", + "type": self.application_type, "processes": {"spare": 0}, "path": script_path, "working_directory": script_path, diff --git a/test/unit/applications/lang/ruby.py b/test/unit/applications/lang/ruby.py index c2d8633e..d30735ad 100644 --- a/test/unit/applications/lang/ruby.py +++ b/test/unit/applications/lang/ruby.py @@ -2,6 +2,8 @@ from unit.applications.proto import TestApplicationProto class TestApplicationRuby(TestApplicationProto): + application_type = "ruby" + def load(self, script, name='config.ru'): script_path = self.current_dir + '/ruby/' + script @@ -10,7 +12,7 @@ class TestApplicationRuby(TestApplicationProto): "listeners": {"*:7080": {"pass": "applications/" + script}}, "applications": { script: { - "type": "ruby", + "type": self.application_type, "processes": {"spare": 0}, "working_directory": script_path, "script": script_path + '/' + name, diff --git a/test/unit/main.py b/test/unit/main.py index 49806fe7..212da49e 100644 --- a/test/unit/main.py +++ b/test/unit/main.py @@ -12,6 +12,8 @@ import subprocess from multiprocessing import Process +available_modules = {} + class TestUnit(unittest.TestCase): current_dir = os.path.abspath( @@ -34,6 +36,17 @@ class TestUnit(unittest.TestCase): TestUnit._set_args(args) + def run(self, result=None): + if not hasattr(self, 'application_type'): + return super().run(result) + + type = self.application_type + for prerequisite in self.prerequisites: + if prerequisite in available_modules: + for version in available_modules[prerequisite]: + self.application_type = type + ' ' + version + super().run(result) + @classmethod def main(cls): args, rest = TestUnit._parse_args() @@ -108,6 +121,16 @@ class TestUnit(unittest.TestCase): self.stop() exit("Unit is writing log too long") + # discover all available modules + + global available_modules + available_modules = {} + for module in re.findall(r'module: ([a-zA-Z]+) ([\d\.]*) ', log): + if module[0] not in available_modules: + available_modules[module[0]] = [module[1]] + else: + available_modules[module[0]].append(module[1]) + missed_module = '' for module in modules: if module == 'go': @@ -153,7 +176,8 @@ class TestUnit(unittest.TestCase): m = None else: - m = re.search('module: ' + module, log) + if module not in available_modules: + m = None if m is None: missed_module = module -- cgit From e8d1c760d6c8134c809d2bb60c212743fdbb9b16 Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Wed, 7 Aug 2019 14:43:38 +0300 Subject: Tests: Java multipart test. --- test/java/multipart/app.java | 93 +++++++++++++++++++++++++++++++++++++++++++ test/test_java_application.py | 39 ++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 test/java/multipart/app.java diff --git a/test/java/multipart/app.java b/test/java/multipart/app.java new file mode 100644 index 00000000..c4c89ffb --- /dev/null +++ b/test/java/multipart/app.java @@ -0,0 +1,93 @@ + +import java.io.IOException; +import java.io.PrintWriter; + +import java.util.Map; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; + +import javax.servlet.annotation.WebServlet; +import javax.servlet.annotation.MultipartConfig; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.io.FileOutputStream; +import java.io.OutputStream; +import javax.servlet.http.Part; + +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@WebServlet("/") +@MultipartConfig( + fileSizeThreshold = 1024 * 1024 * 1, // 1 MB + maxFileSize = 1024 * 1024 * 10, // 10 MB + maxRequestSize = 1024 * 1024 * 15 // 15 MB +) +public class app extends HttpServlet +{ + @Override + public void doPost(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + response.setContentType("text/html;charset=UTF-8"); + + // Create path components to save the file + final String path = request.getParameter("destination"); + final Part filePart = request.getPart("file"); + final String fileName = getFileName(filePart); + + OutputStream out = null; + InputStream filecontent = null; + final PrintWriter writer = response.getWriter(); + + try { + out = new FileOutputStream(new File(path + File.separator + + fileName)); + filecontent = filePart.getInputStream(); + + int read = 0; + final byte[] bytes = new byte[1024]; + + while ((read = filecontent.read(bytes)) != -1) { + out.write(bytes, 0, read); + } + writer.println(fileName + " created at " + path); + + } catch (FileNotFoundException fne) { + writer.println("You either did not specify a file to upload or are " + + "trying to upload a file to a protected or nonexistent " + + "location."); + writer.println("
ERROR: " + fne.getMessage()); + + } finally { + if (out != null) { + out.close(); + } + if (filecontent != null) { + filecontent.close(); + } + if (writer != null) { + writer.close(); + } + } + + return; + } + + private String getFileName(final Part part) { + final String partHeader = part.getHeader("content-disposition"); + + for (String content : part.getHeader("content-disposition").split(";")) + { + if (content.trim().startsWith("filename")) { + return content.substring( + content.indexOf("=") + 1).trim().replace("\"", ""); + } + } + return null; + } +} diff --git a/test/test_java_application.py b/test/test_java_application.py index f604368c..2e22e545 100644 --- a/test/test_java_application.py +++ b/test/test_java_application.py @@ -1,4 +1,5 @@ import time +import unittest from unit.applications.lang.java import TestApplicationJava @@ -1213,6 +1214,44 @@ class TestJavaApplication(TestApplicationJava): ) self.assertEqual(headers['X-Get-Date'], date, 'get date header') + @unittest.skip('not yet') + def test_java_application_multipart(self): + self.load('multipart') + + body = """Preamble. Should be ignored.\r +\r +--12345\r +Content-Disposition: form-data; name="file"; filename="sample.txt"\r +Content-Type: text/plain\r +\r +Data from sample file\r +--12345\r +Content-Disposition: form-data; name="destination"\r +\r +%s\r +--12345\r +Content-Disposition: form-data; name="upload"\r +\r +Upload\r +--12345--\r +\r +Epilogue. Should be ignored.""" % self.testdir + + resp = self.post( + headers={ + 'Content-Type': 'multipart/form-data; boundary=12345', + 'Host': 'localhost', + 'Connection': 'close', + }, + body=body, + ) + + self.assertEqual(resp['status'], 200, 'multipart status') + self.assertRegex(resp['body'], r'sample\.txt created', 'multipart body') + self.assertIsNotNone( + self.search_in_log(r'^Data from sample file$', name='sample.txt'), + 'file created', + ) if __name__ == '__main__': TestJavaApplication.main() -- cgit From 78fbf9ee60a1cf4306a65c15ecce8dafbe6bf862 Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Thu, 8 Aug 2019 15:47:26 +0300 Subject: Tests: fixed modules version parsing. --- test/unit/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/main.py b/test/unit/main.py index 212da49e..27f8bff9 100644 --- a/test/unit/main.py +++ b/test/unit/main.py @@ -125,7 +125,7 @@ class TestUnit(unittest.TestCase): global available_modules available_modules = {} - for module in re.findall(r'module: ([a-zA-Z]+) ([\d\.]*) ', log): + for module in re.findall(r'module: ([a-zA-Z]+) (.*) ".*"$', log, re.M): if module[0] not in available_modules: available_modules[module[0]] = [module[1]] else: -- cgit From b3b7013edaa142318349e46825f250b7ab6b2bc0 Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Wed, 14 Aug 2019 14:06:22 +0300 Subject: Tests: added tests for deleting listeners and applications objects. --- test/test_php_basic.py | 26 ++++++++++++++++++++++++++ test/test_python_basic.py | 27 +++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/test/test_php_basic.py b/test/test_php_basic.py index 02ff81de..be5064ba 100644 --- a/test/test_php_basic.py +++ b/test/test_php_basic.py @@ -164,6 +164,32 @@ class TestPHPBasic(TestControl): 'error', self.conf_delete('applications/app'), 'delete app again' ) + def test_php_delete_blocks(self): + self.conf(self.conf_basic) + + self.assertIn( + 'success', + self.conf_delete('listeners'), + 'listeners delete', + ) + + self.assertIn( + 'success', + self.conf_delete('applications'), + 'applications delete', + ) + + self.assertIn( + 'success', + self.conf(self.conf_app, 'applications'), + 'listeners restore', + ) + + self.assertIn( + 'success', + self.conf({"*:7080": {"pass": "applications/app"}}, 'listeners'), + 'applications restore', + ) if __name__ == '__main__': TestPHPBasic.main() diff --git a/test/test_python_basic.py b/test/test_python_basic.py index 9987e886..f7a7e354 100644 --- a/test/test_python_basic.py +++ b/test/test_python_basic.py @@ -177,6 +177,33 @@ class TestPythonBasic(TestControl): 'error', self.conf_delete('applications/app'), 'delete app again' ) + def test_python_delete_blocks(self): + self.conf(self.conf_basic) + + self.assertIn( + 'success', + self.conf_delete('listeners'), + 'listeners delete', + ) + + self.assertIn( + 'success', + self.conf_delete('applications'), + 'applications delete', + ) + + self.assertIn( + 'success', + self.conf(self.conf_app, 'applications'), + 'listeners restore', + ) + + self.assertIn( + 'success', + self.conf({"*:7080": {"pass": "applications/app"}}, 'listeners'), + 'applications restore', + ) + if __name__ == '__main__': TestPythonBasic.main() -- cgit From 8904c87c6beb4f2b080bf4269fb211e6f2eea7f2 Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Wed, 14 Aug 2019 14:06:28 +0300 Subject: Tests: goftm used for Go applications. --- test/go/404/app.go | 22 ++++++++++----------- test/go/command_line_arguments/app.go | 24 +++++++++++------------ test/go/cookies/app.go | 16 ++++++++-------- test/go/empty/app.go | 8 ++++---- test/go/get_variables/app.go | 14 +++++++------- test/go/mirror/app.go | 20 +++++++++---------- test/go/post_variables/app.go | 16 ++++++++-------- test/go/variables/app.go | 36 +++++++++++++++++------------------ 8 files changed, 78 insertions(+), 78 deletions(-) diff --git a/test/go/404/app.go b/test/go/404/app.go index abb33066..08fe56c9 100644 --- a/test/go/404/app.go +++ b/test/go/404/app.go @@ -1,22 +1,22 @@ package main import ( - "io" - "io/ioutil" - "net/http" - "nginx/unit" + "io" + "io/ioutil" + "net/http" + "nginx/unit" ) func handler(w http.ResponseWriter, r *http.Request) { - b, e := ioutil.ReadFile("404.html") + b, e := ioutil.ReadFile("404.html") - if e == nil { - w.WriteHeader(http.StatusNotFound) - io.WriteString(w, string(b)) - } + if e == nil { + w.WriteHeader(http.StatusNotFound) + io.WriteString(w, string(b)) + } } func main() { - http.HandleFunc("/", handler) - unit.ListenAndServe(":7080", nil) + http.HandleFunc("/", handler) + unit.ListenAndServe(":7080", nil) } diff --git a/test/go/command_line_arguments/app.go b/test/go/command_line_arguments/app.go index 228e07c0..234e565e 100644 --- a/test/go/command_line_arguments/app.go +++ b/test/go/command_line_arguments/app.go @@ -1,23 +1,23 @@ package main import ( - "io" - "os" - "fmt" - "strings" - "net/http" - "nginx/unit" + "fmt" + "io" + "net/http" + "nginx/unit" + "os" + "strings" ) func handler(w http.ResponseWriter, r *http.Request) { - args := strings.Join(os.Args[1:], ",") + args := strings.Join(os.Args[1:], ",") - w.Header().Add("X-Arg-0", fmt.Sprintf("%v", os.Args[0])) - w.Header().Add("Content-Length", fmt.Sprintf("%v", len(args))) - io.WriteString(w, args) + w.Header().Add("X-Arg-0", fmt.Sprintf("%v", os.Args[0])) + w.Header().Add("Content-Length", fmt.Sprintf("%v", len(args))) + io.WriteString(w, args) } func main() { - http.HandleFunc("/", handler) - unit.ListenAndServe(":7080", nil) + http.HandleFunc("/", handler) + unit.ListenAndServe(":7080", nil) } diff --git a/test/go/cookies/app.go b/test/go/cookies/app.go index 6fb9def0..e6647ea8 100644 --- a/test/go/cookies/app.go +++ b/test/go/cookies/app.go @@ -1,19 +1,19 @@ package main import ( - "net/http" - "nginx/unit" + "net/http" + "nginx/unit" ) func handler(w http.ResponseWriter, r *http.Request) { - cookie1, _ := r.Cookie("var1") - cookie2, _ := r.Cookie("var2") + cookie1, _ := r.Cookie("var1") + cookie2, _ := r.Cookie("var2") - w.Header().Set("X-Cookie-1", cookie1.Value) - w.Header().Set("X-Cookie-2", cookie2.Value) + w.Header().Set("X-Cookie-1", cookie1.Value) + w.Header().Set("X-Cookie-2", cookie2.Value) } func main() { - http.HandleFunc("/", handler) - unit.ListenAndServe(":7080", nil) + http.HandleFunc("/", handler) + unit.ListenAndServe(":7080", nil) } diff --git a/test/go/empty/app.go b/test/go/empty/app.go index 2e07405f..6e0fce1b 100644 --- a/test/go/empty/app.go +++ b/test/go/empty/app.go @@ -1,13 +1,13 @@ package main import ( - "net/http" - "nginx/unit" + "net/http" + "nginx/unit" ) func handler(w http.ResponseWriter, r *http.Request) {} func main() { - http.HandleFunc("/", handler) - unit.ListenAndServe(":7080", nil) + http.HandleFunc("/", handler) + unit.ListenAndServe(":7080", nil) } diff --git a/test/go/get_variables/app.go b/test/go/get_variables/app.go index 563febc8..4dcc0e7b 100644 --- a/test/go/get_variables/app.go +++ b/test/go/get_variables/app.go @@ -1,17 +1,17 @@ package main import ( - "net/http" - "nginx/unit" + "net/http" + "nginx/unit" ) func handler(w http.ResponseWriter, r *http.Request) { - w.Header().Set("X-Var-1", r.URL.Query().Get("var1")) - w.Header().Set("X-Var-2", r.URL.Query().Get("var2")) - w.Header().Set("X-Var-3", r.URL.Query().Get("var3")) + w.Header().Set("X-Var-1", r.URL.Query().Get("var1")) + w.Header().Set("X-Var-2", r.URL.Query().Get("var2")) + w.Header().Set("X-Var-3", r.URL.Query().Get("var3")) } func main() { - http.HandleFunc("/", handler) - unit.ListenAndServe(":7080", nil) + http.HandleFunc("/", handler) + unit.ListenAndServe(":7080", nil) } diff --git a/test/go/mirror/app.go b/test/go/mirror/app.go index 82b1c92d..748aa7ee 100644 --- a/test/go/mirror/app.go +++ b/test/go/mirror/app.go @@ -1,21 +1,21 @@ package main import ( - "io" - "fmt" - "net/http" - "nginx/unit" + "fmt" + "io" + "net/http" + "nginx/unit" ) func handler(w http.ResponseWriter, r *http.Request) { - var buf [32768]byte; - len, _ := r.Body.Read(buf[:]) + var buf [32768]byte + len, _ := r.Body.Read(buf[:]) - w.Header().Add("Content-Length", fmt.Sprintf("%v", len)) - io.WriteString(w, string(buf[:len])) + w.Header().Add("Content-Length", fmt.Sprintf("%v", len)) + io.WriteString(w, string(buf[:len])) } func main() { - http.HandleFunc("/", handler) - unit.ListenAndServe(":7080", nil) + http.HandleFunc("/", handler) + unit.ListenAndServe(":7080", nil) } diff --git a/test/go/post_variables/app.go b/test/go/post_variables/app.go index 433afc62..947976d2 100644 --- a/test/go/post_variables/app.go +++ b/test/go/post_variables/app.go @@ -1,19 +1,19 @@ package main import ( - "net/http" - "nginx/unit" + "net/http" + "nginx/unit" ) func handler(w http.ResponseWriter, r *http.Request) { - r.ParseForm() + r.ParseForm() - w.Header().Set("X-Var-1", r.Form.Get("var1")) - w.Header().Set("X-Var-2", r.Form.Get("var2")) - w.Header().Set("X-Var-3", r.Form.Get("var3")) + w.Header().Set("X-Var-1", r.Form.Get("var1")) + w.Header().Set("X-Var-2", r.Form.Get("var2")) + w.Header().Set("X-Var-3", r.Form.Get("var3")) } func main() { - http.HandleFunc("/", handler) - unit.ListenAndServe(":7080", nil) + http.HandleFunc("/", handler) + unit.ListenAndServe(":7080", nil) } diff --git a/test/go/variables/app.go b/test/go/variables/app.go index 5db4ac67..fdcbf7e8 100644 --- a/test/go/variables/app.go +++ b/test/go/variables/app.go @@ -1,30 +1,30 @@ package main import ( - "io" - "fmt" - "net/http" - "nginx/unit" + "fmt" + "io" + "net/http" + "nginx/unit" ) func handler(w http.ResponseWriter, r *http.Request) { - var buf [4096]byte; - len, _ := r.Body.Read(buf[:]) + var buf [4096]byte + len, _ := r.Body.Read(buf[:]) - w.Header().Set("Request-Method", r.Method) - w.Header().Set("Request-Uri", r.RequestURI) - w.Header().Set("Server-Protocol", r.Proto) - w.Header().Set("Server-Protocol-Major", fmt.Sprintf("%v", r.ProtoMajor)) - w.Header().Set("Server-Protocol-Minor", fmt.Sprintf("%v", r.ProtoMinor)) - w.Header().Set("Content-Length", fmt.Sprintf("%v", len)) - w.Header().Set("Content-Type", r.Header.Get("Content-Type")) - w.Header().Set("Custom-Header", r.Header.Get("Custom-Header")) - w.Header().Set("Http-Host", r.Header.Get("Host")) + w.Header().Set("Request-Method", r.Method) + w.Header().Set("Request-Uri", r.RequestURI) + w.Header().Set("Server-Protocol", r.Proto) + w.Header().Set("Server-Protocol-Major", fmt.Sprintf("%v", r.ProtoMajor)) + w.Header().Set("Server-Protocol-Minor", fmt.Sprintf("%v", r.ProtoMinor)) + w.Header().Set("Content-Length", fmt.Sprintf("%v", len)) + w.Header().Set("Content-Type", r.Header.Get("Content-Type")) + w.Header().Set("Custom-Header", r.Header.Get("Custom-Header")) + w.Header().Set("Http-Host", r.Header.Get("Host")) - io.WriteString(w, string(buf[:len])) + io.WriteString(w, string(buf[:len])) } func main() { - http.HandleFunc("/", handler) - unit.ListenAndServe(":7080", nil) + http.HandleFunc("/", handler) + unit.ListenAndServe(":7080", nil) } -- cgit From 4bef4256c05c3f905e3d65e40585bba5ce0f3327 Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Wed, 14 Aug 2019 15:24:41 +0300 Subject: Java: implementing multipart message support. This closes #265 issue on GitHub. --- src/java/nginx/unit/Context.java | 31 ++++- src/java/nginx/unit/ForwardRequestWrapper.java | 12 ++ src/java/nginx/unit/IncludeRequestWrapper.java | 12 ++ src/java/nginx/unit/Request.java | 151 +++++++++++++++++++++++-- test/test_java_application.py | 1 - 5 files changed, 192 insertions(+), 15 deletions(-) diff --git a/src/java/nginx/unit/Context.java b/src/java/nginx/unit/Context.java index 3c64e99f..e1482903 100644 --- a/src/java/nginx/unit/Context.java +++ b/src/java/nginx/unit/Context.java @@ -81,6 +81,7 @@ import javax.servlet.ServletSecurityElement; import javax.servlet.SessionCookieConfig; import javax.servlet.SessionTrackingMode; import javax.servlet.annotation.HandlesTypes; +import javax.servlet.annotation.MultipartConfig; import javax.servlet.annotation.WebInitParam; import javax.servlet.annotation.WebServlet; import javax.servlet.annotation.WebFilter; @@ -954,6 +955,8 @@ public class Context implements ServletContext, InitParams ServletReg servlet = findServlet(path, req); + req.setMultipartConfig(servlet.multipart_config_); + FilterChain fc = new CtxFilterChain(servlet, req.getFilterPath(), DispatcherType.REQUEST); fc.doFilter(req, resp); @@ -1074,6 +1077,8 @@ public class Context implements ServletContext, InitParams ServletReg servlet = findServlet(path, req); + req.setMultipartConfig(servlet.multipart_config_); + FilterChain fc = new CtxFilterChain(servlet, req.getFilterPath(), DispatcherType.ERROR); fc.doFilter(req, resp); @@ -1852,11 +1857,13 @@ public class Context implements ServletContext, InitParams private boolean initialized_ = false; private final List filters_ = new ArrayList<>(); private boolean system_jsp_servlet_ = false; + private MultipartConfigElement multipart_config_; public ServletReg(String name, Class servlet_class) { super(name, servlet_class.getName()); servlet_class_ = servlet_class; + getAnnotationMultipartConfig(); } public ServletReg(String name, Servlet servlet) @@ -1893,6 +1900,7 @@ public class Context implements ServletContext, InitParams try { if (servlet_class_ == null) { servlet_class_ = loader_.loadClass(getClassName()); + getAnnotationMultipartConfig(); } Constructor ctor = servlet_class_.getConstructor(); @@ -1948,6 +1956,20 @@ public class Context implements ServletContext, InitParams super.setClassName(servlet_class.getName()); servlet_class_ = servlet_class; + getAnnotationMultipartConfig(); + } + + private void getAnnotationMultipartConfig() { + if (servlet_class_ == null) { + return; + } + + MultipartConfig mpc = servlet_class_.getAnnotation(MultipartConfig.class); + if (mpc == null) { + return; + } + + multipart_config_ = new MultipartConfigElement(mpc); } public void service(ServletRequest request, ServletResponse response) @@ -2027,7 +2049,8 @@ public class Context implements ServletContext, InitParams public void setMultipartConfig( MultipartConfigElement multipartConfig) { - log("ServletReg.setMultipartConfig"); + trace("ServletReg.setMultipartConfig"); + multipart_config_ = multipartConfig; } @Override @@ -2508,6 +2531,8 @@ public class Context implements ServletContext, InitParams ServletReg servlet = findServlet(path, req); + req.setMultipartConfig(servlet.multipart_config_); + req.setRequestURI(uri_.getRawPath()); req.setQueryString(uri_.getRawQuery()); req.setDispatcherType(DispatcherType.FORWARD); @@ -2577,6 +2602,8 @@ public class Context implements ServletContext, InitParams ServletReg servlet = findServlet(path, req); + req.setMultipartConfig(servlet.multipart_config_); + req.setRequestURI(uri_.getRawPath()); req.setQueryString(uri_.getRawQuery()); req.setDispatcherType(DispatcherType.INCLUDE); @@ -2757,7 +2784,7 @@ public class Context implements ServletContext, InitParams { trace("getRealPath for " + path); - File f = new File(webapp_, path.substring(1)); + File f = new File(webapp_, path.isEmpty() ? "" : path.substring(1)); return f.getAbsolutePath(); } diff --git a/src/java/nginx/unit/ForwardRequestWrapper.java b/src/java/nginx/unit/ForwardRequestWrapper.java index f88b6aef..fe8adf8a 100644 --- a/src/java/nginx/unit/ForwardRequestWrapper.java +++ b/src/java/nginx/unit/ForwardRequestWrapper.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.Map; import javax.servlet.DispatcherType; +import javax.servlet.MultipartConfigElement; import javax.servlet.RequestDispatcher; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; @@ -24,6 +25,8 @@ public class ForwardRequestWrapper implements DynamicPathRequest private final String orig_context_path; private final String orig_query; + private final MultipartConfigElement orig_multipart_config; + private final DispatcherType orig_dtype; private MultiMap orig_parameters; @@ -46,6 +49,8 @@ public class ForwardRequestWrapper implements DynamicPathRequest orig_uri = request_.getRequestURI(); orig_context_path = request_.getContextPath(); orig_query = request_.getQueryString(); + + orig_multipart_config = request_.getMultipartConfig(); } @Override @@ -125,6 +130,11 @@ public class ForwardRequestWrapper implements DynamicPathRequest return request_.getFilterPath(); } + public void setMultipartConfig(MultipartConfigElement mce) + { + request_.setMultipartConfig(mce); + } + public void close() { request_.setDispatcherType(orig_dtype); @@ -137,6 +147,8 @@ public class ForwardRequestWrapper implements DynamicPathRequest request_.setParameters(orig_parameters); } + request_.setMultipartConfig(orig_multipart_config); + if (keep_attrs) { return; } diff --git a/src/java/nginx/unit/IncludeRequestWrapper.java b/src/java/nginx/unit/IncludeRequestWrapper.java index 67a51b24..761a0d52 100644 --- a/src/java/nginx/unit/IncludeRequestWrapper.java +++ b/src/java/nginx/unit/IncludeRequestWrapper.java @@ -1,6 +1,7 @@ package nginx.unit; import javax.servlet.DispatcherType; +import javax.servlet.MultipartConfigElement; import javax.servlet.RequestDispatcher; import javax.servlet.ServletRequest; @@ -14,6 +15,8 @@ public class IncludeRequestWrapper implements DynamicPathRequest private final Object orig_context_path_attr; private final Object orig_query_string_attr; + private final MultipartConfigElement orig_multipart_config; + private final DispatcherType orig_dtype; private String filter_path_; @@ -32,6 +35,8 @@ public class IncludeRequestWrapper implements DynamicPathRequest orig_context_path_attr = request_.getAttribute(RequestDispatcher.INCLUDE_CONTEXT_PATH); orig_query_string_attr = request_.getAttribute(RequestDispatcher.INCLUDE_QUERY_STRING); + orig_multipart_config = request_.getMultipartConfig(); + orig_dtype = request_.getDispatcherType(); request_.setAttribute_(RequestDispatcher.INCLUDE_CONTEXT_PATH, request_.getContextPath()); @@ -75,6 +80,11 @@ public class IncludeRequestWrapper implements DynamicPathRequest return filter_path_; } + public void setMultipartConfig(MultipartConfigElement mce) + { + request_.setMultipartConfig(mce); + } + public void close() { request_.setDispatcherType(orig_dtype); @@ -84,5 +94,7 @@ public class IncludeRequestWrapper implements DynamicPathRequest request_.setAttribute_(RequestDispatcher.INCLUDE_REQUEST_URI, orig_uri_attr); request_.setAttribute_(RequestDispatcher.INCLUDE_CONTEXT_PATH, orig_context_path_attr); request_.setAttribute_(RequestDispatcher.INCLUDE_QUERY_STRING, orig_query_string_attr); + + request_.setMultipartConfig(orig_multipart_config); } } diff --git a/src/java/nginx/unit/Request.java b/src/java/nginx/unit/Request.java index 3ba46f6c..98584efe 100644 --- a/src/java/nginx/unit/Request.java +++ b/src/java/nginx/unit/Request.java @@ -1,6 +1,8 @@ package nginx.unit; import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.InputStreamReader; import java.io.IOException; import java.io.UnsupportedEncodingException; @@ -14,6 +16,9 @@ import java.lang.StringBuffer; import java.net.URI; import java.net.URISyntaxException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + import java.text.ParseException; import java.text.SimpleDateFormat; @@ -32,6 +37,7 @@ import java.security.Principal; import javax.servlet.AsyncContext; import javax.servlet.DispatcherType; +import javax.servlet.MultipartConfigElement; import javax.servlet.RequestDispatcher; import javax.servlet.ServletContext; import javax.servlet.ServletException; @@ -49,11 +55,14 @@ import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpUpgradeHandler; import javax.servlet.http.Part; +import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.MultiMap; import org.eclipse.jetty.util.UrlEncoded; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.server.CookieCutter; +import org.eclipse.jetty.http.MultiPartFormInputStream; +import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.MimeTypes; public class Request implements HttpServletRequest, DynamicPathRequest @@ -109,6 +118,9 @@ public class Request implements HttpServletRequest, DynamicPathRequest public static final String BARE = "nginx.unit.request.bare"; + private MultiPartFormInputStream multi_parts; + private MultipartConfigElement multipart_config; + public Request(Context ctx, long req_info, long req) { context = ctx; req_info_ptr = req_info; @@ -271,17 +283,64 @@ public class Request implements HttpServletRequest, DynamicPathRequest @Override public Part getPart(String name) throws IOException, ServletException { - log("getPart: " + name); + trace("getPart: " + name); - return null; + if (multi_parts == null) { + parseMultiParts(); + } + + return multi_parts.getPart(name); } @Override public Collection getParts() throws IOException, ServletException { - log("getParts"); + trace("getParts"); + + if (multi_parts == null) { + parseMultiParts(); + } + + return multi_parts.getParts(); + } + + private boolean checkMultiPart(String content_type) + { + return content_type != null + && MimeTypes.Type.MULTIPART_FORM_DATA.is(HttpFields.valueParameters(content_type, null)); + } + + private void parseMultiParts() throws IOException, ServletException, IllegalStateException + { + String content_type = getContentType(); + + if (!checkMultiPart(content_type)) { + throw new ServletException("Content-Type != multipart/form-data"); + } + + if (multipart_config == null) { + throw new IllegalStateException("No multipart config for servlet"); + } - return Collections.emptyList(); + parseMultiParts(content_type); + } + + private void parseMultiParts(String content_type) throws IOException + { + File tmpDir = (File) context.getAttribute(ServletContext.TEMPDIR); + + multi_parts = new MultiPartFormInputStream(getInputStream(), + content_type, multipart_config, tmpDir); + } + + public void setMultipartConfig(MultipartConfigElement mce) + { + multipart_config = mce; + } + + public MultipartConfigElement getMultipartConfig() + { + return multipart_config; } @Override @@ -766,16 +825,84 @@ public class Request implements HttpServletRequest, DynamicPathRequest UrlEncoded.decodeUtf8To(query, parameters); } - if (getContentLength() > 0 && - getMethod().equals("POST") && - getContentType().startsWith("application/x-www-form-urlencoded")) - { - try { + int content_length = getContentLength(); + + if (content_length == 0 || !getMethod().equals("POST")) { + return parameters; + } + + String content_type = getContentType(); + + try { + if (content_type.startsWith("application/x-www-form-urlencoded")) { UrlEncoded.decodeUtf8To(new InputStream(req_info_ptr), - parameters, getContentLength(), -1); - } catch (IOException e) { - log("Unhandled IOException: " + e); + parameters, content_length, -1); + } else if (checkMultiPart(content_type) && multipart_config != null) { + if (multi_parts == null) { + parseMultiParts(content_type); + } + + if (multi_parts != null) { + Collection parts = multi_parts.getParts(); + + String _charset_ = null; + Part charset_part = multi_parts.getPart("_charset_"); + if (charset_part != null) { + try (java.io.InputStream is = charset_part.getInputStream()) + { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + IO.copy(is, os); + _charset_ = new String(os.toByteArray(),StandardCharsets.UTF_8); + } + } + + /* + Select Charset to use for this part. (NOTE: charset behavior is for the part value only and not the part header/field names) + 1. Use the part specific charset as provided in that part's Content-Type header; else + 2. Use the overall default charset. Determined by: + a. if part name _charset_ exists, use that part's value. + b. if the request.getCharacterEncoding() returns a value, use that. + (note, this can be either from the charset field on the request Content-Type + header, or from a manual call to request.setCharacterEncoding()) + c. use utf-8. + */ + Charset def_charset; + if (_charset_ != null) { + def_charset = Charset.forName(_charset_); + } else if (getCharacterEncoding() != null) { + def_charset = Charset.forName(getCharacterEncoding()); + } else { + def_charset = StandardCharsets.UTF_8; + } + + ByteArrayOutputStream os = null; + for (Part p : parts) { + if (p.getSubmittedFileName() != null) { + continue; + } + + // Servlet Spec 3.0 pg 23, parts without filename must be put into params. + String charset = null; + if (p.getContentType() != null) { + charset = MimeTypes.getCharsetFromContentType(p.getContentType()); + } + + try (java.io.InputStream is = p.getInputStream()) + { + if (os == null) { + os = new ByteArrayOutputStream(); + } + IO.copy(is, os); + + String content = new String(os.toByteArray(), charset == null ? def_charset : Charset.forName(charset)); + parameters.add(p.getName(), content); + } + os.reset(); + } + } } + } catch (IOException e) { + log("Unhandled IOException: " + e); } return parameters; diff --git a/test/test_java_application.py b/test/test_java_application.py index 2e22e545..526be565 100644 --- a/test/test_java_application.py +++ b/test/test_java_application.py @@ -1214,7 +1214,6 @@ class TestJavaApplication(TestApplicationJava): ) self.assertEqual(headers['X-Get-Date'], date, 'get date header') - @unittest.skip('not yet') def test_java_application_multipart(self): self.load('multipart') -- cgit From ac316ff7a5a29c4d3a3c965147fd3fd9401a0efc Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Wed, 14 Aug 2019 15:53:58 +0300 Subject: Tests: fixed port reusing to avoid access issues. --- test/test_php_basic.py | 2 +- test/test_python_basic.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_php_basic.py b/test/test_php_basic.py index be5064ba..0c84f206 100644 --- a/test/test_php_basic.py +++ b/test/test_php_basic.py @@ -187,7 +187,7 @@ class TestPHPBasic(TestControl): self.assertIn( 'success', - self.conf({"*:7080": {"pass": "applications/app"}}, 'listeners'), + self.conf({"*:7081": {"pass": "applications/app"}}, 'listeners'), 'applications restore', ) diff --git a/test/test_python_basic.py b/test/test_python_basic.py index f7a7e354..e63158e5 100644 --- a/test/test_python_basic.py +++ b/test/test_python_basic.py @@ -200,7 +200,7 @@ class TestPythonBasic(TestControl): self.assertIn( 'success', - self.conf({"*:7080": {"pass": "applications/app"}}, 'listeners'), + self.conf({"*:7081": {"pass": "applications/app"}}, 'listeners'), 'applications restore', ) -- cgit From 1b095ff417272aa570dd39f4bd94133ab244e789 Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Wed, 14 Aug 2019 23:59:46 +0300 Subject: Renaming supplemental request structures in router. - nxt_req_app_link_t -> nxt_request_app_link_t - nxt_req_conn_link_t -> nxt_request_rpc_data_t Corresponding abbreviated field names also changed: - ra -> req_app_link - rc -> req_rpc_data --- src/nxt_router.c | 765 ++++++++++++++++++++++++++++++------------------------- src/nxt_router.h | 4 +- 2 files changed, 420 insertions(+), 349 deletions(-) diff --git a/src/nxt_router.c b/src/nxt_router.c index df2557fc..f09779bc 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -55,41 +55,54 @@ typedef struct nxt_msg_info_s { } nxt_msg_info_t; -typedef struct nxt_req_app_link_s nxt_req_app_link_t; +typedef struct nxt_request_app_link_s nxt_request_app_link_t; + + +typedef enum { + NXT_APR_NEW_PORT, + NXT_APR_REQUEST_FAILED, + NXT_APR_GOT_RESPONSE, + NXT_APR_CLOSE, +} nxt_apr_action_t; typedef struct { - uint32_t stream; - nxt_app_t *app; - nxt_port_t *app_port; - nxt_http_request_t *request; - nxt_msg_info_t msg_info; - nxt_req_app_link_t *ra; + uint32_t stream; + nxt_app_t *app; + + nxt_port_t *app_port; + nxt_apr_action_t apr_action; + + nxt_http_request_t *request; + nxt_msg_info_t msg_info; + nxt_request_app_link_t *req_app_link; +} nxt_request_rpc_data_t; - nxt_queue_link_t link; /* for nxt_conn_t.requests */ -} nxt_req_conn_link_t; +struct nxt_request_app_link_s { + uint32_t stream; + nxt_atomic_t use_count; -struct nxt_req_app_link_s { - uint32_t stream; - nxt_atomic_t use_count; - nxt_port_t *app_port; - nxt_port_t *reply_port; - nxt_http_request_t *request; - nxt_msg_info_t msg_info; - nxt_req_conn_link_t *rc; + nxt_port_t *app_port; + nxt_apr_action_t apr_action; - nxt_nsec_t res_time; + nxt_port_t *reply_port; + nxt_http_request_t *request; + nxt_msg_info_t msg_info; + nxt_request_rpc_data_t *req_rpc_data; - nxt_queue_link_t link_app_requests; /* for nxt_app_t.requests */ - nxt_queue_link_t link_port_pending; /* for nxt_port_t.pending_requests */ - nxt_queue_link_t link_app_pending; /* for nxt_app_t.pending */ + nxt_nsec_t res_time; - nxt_mp_t *mem_pool; - nxt_work_t work; + nxt_queue_link_t link_app_requests; /* for nxt_app_t.requests */ + /* for nxt_port_t.pending_requests */ + nxt_queue_link_t link_port_pending; + nxt_queue_link_t link_app_pending; /* for nxt_app_t.pending */ - int err_code; - const char *err_str; + nxt_mp_t *mem_pool; + nxt_work_t work; + + int err_code; + const char *err_str; }; @@ -106,15 +119,15 @@ typedef struct { struct nxt_port_select_state_s { - nxt_app_t *app; - nxt_req_app_link_t *ra; + nxt_app_t *app; + nxt_request_app_link_t *req_app_link; - nxt_port_t *failed_port; - int failed_port_use_delta; + nxt_port_t *failed_port; + int failed_port_use_delta; - uint8_t start_process; /* 1 bit */ - nxt_req_app_link_t *shared_ra; - nxt_port_t *port; + uint8_t start_process; /* 1 bit */ + nxt_request_app_link_t *shared_ra; + nxt_port_t *port; }; typedef struct nxt_port_select_state_s nxt_port_select_state_t; @@ -129,28 +142,32 @@ static nxt_int_t nxt_router_port_post_select(nxt_task_t *task, nxt_port_select_state_t *state); static nxt_int_t nxt_router_start_app_process(nxt_task_t *task, nxt_app_t *app); +static void nxt_request_app_link_update_peer(nxt_task_t *task, + nxt_request_app_link_t *req_app_link); + nxt_inline void -nxt_router_ra_inc_use(nxt_req_app_link_t *ra) +nxt_request_app_link_inc_use(nxt_request_app_link_t *req_app_link) { - nxt_atomic_fetch_add(&ra->use_count, 1); + nxt_atomic_fetch_add(&req_app_link->use_count, 1); } nxt_inline void -nxt_router_ra_dec_use(nxt_req_app_link_t *ra) +nxt_request_app_link_dec_use(nxt_request_app_link_t *req_app_link) { #if (NXT_DEBUG) int c; - c = nxt_atomic_fetch_add(&ra->use_count, -1); + c = nxt_atomic_fetch_add(&req_app_link->use_count, -1); nxt_assert(c > 1); #else - (void) nxt_atomic_fetch_add(&ra->use_count, -1); + (void) nxt_atomic_fetch_add(&req_app_link->use_count, -1); #endif } -static void nxt_router_ra_use(nxt_task_t *task, nxt_req_app_link_t *ra, int i); +static void nxt_request_app_link_use(nxt_task_t *task, + nxt_request_app_link_t *req_app_link, int i); static nxt_router_temp_conf_t *nxt_router_temp_conf(nxt_task_t *task); static void nxt_router_conf_apply(nxt_task_t *task, void *obj, void *data); @@ -257,13 +274,14 @@ static void nxt_router_app_port_error(nxt_task_t *task, nxt_port_recv_msg_t *msg, void *data); static void nxt_router_app_unlink(nxt_task_t *task, nxt_app_t *app); + static void nxt_router_app_port_release(nxt_task_t *task, nxt_port_t *port, - uint32_t request_failed, uint32_t got_response); + nxt_apr_action_t action); static nxt_int_t nxt_router_app_port(nxt_task_t *task, nxt_app_t *app, - nxt_req_app_link_t *ra); + nxt_request_app_link_t *req_app_link); static void nxt_router_app_prepare_request(nxt_task_t *task, - nxt_req_app_link_t *ra); + nxt_request_app_link_t *req_app_link); static nxt_buf_t *nxt_router_prepare_msg(nxt_task_t *task, nxt_http_request_t *r, nxt_port_t *port, const nxt_str_t *prefix); @@ -493,58 +511,63 @@ nxt_router_start_app_process(nxt_task_t *task, nxt_app_t *app) nxt_inline void -nxt_router_ra_init(nxt_task_t *task, nxt_req_app_link_t *ra, - nxt_req_conn_link_t *rc) +nxt_request_app_link_init(nxt_task_t *task, + nxt_request_app_link_t *req_app_link, nxt_request_rpc_data_t *req_rpc_data) { nxt_event_engine_t *engine; engine = task->thread->engine; - nxt_memzero(ra, sizeof(nxt_req_app_link_t)); + nxt_memzero(req_app_link, sizeof(nxt_request_app_link_t)); - ra->stream = rc->stream; - ra->use_count = 1; - ra->rc = rc; - rc->ra = ra; - ra->reply_port = engine->port; - ra->request = rc->request; + req_app_link->stream = req_rpc_data->stream; + req_app_link->use_count = 1; + req_app_link->req_rpc_data = req_rpc_data; + req_rpc_data->req_app_link = req_app_link; + req_app_link->reply_port = engine->port; + req_app_link->request = req_rpc_data->request; + req_app_link->apr_action = NXT_APR_GOT_RESPONSE; - ra->work.handler = NULL; - ra->work.task = &engine->task; - ra->work.obj = ra; - ra->work.data = engine; + req_app_link->work.handler = NULL; + req_app_link->work.task = &engine->task; + req_app_link->work.obj = req_app_link; + req_app_link->work.data = engine; } -nxt_inline nxt_req_app_link_t * -nxt_router_ra_create(nxt_task_t *task, nxt_req_app_link_t *ra_src) +nxt_inline nxt_request_app_link_t * +nxt_request_app_link_alloc(nxt_task_t *task, + nxt_request_app_link_t *ra_src, nxt_request_rpc_data_t *req_rpc_data) { - nxt_mp_t *mp; - nxt_req_app_link_t *ra; + nxt_mp_t *mp; + nxt_request_app_link_t *req_app_link; - if (ra_src->mem_pool != NULL) { + if (ra_src != NULL && ra_src->mem_pool != NULL) { return ra_src; } - mp = ra_src->request->mem_pool; + mp = req_rpc_data->request->mem_pool; + + req_app_link = nxt_mp_alloc(mp, sizeof(nxt_request_app_link_t)); - ra = nxt_mp_alloc(mp, sizeof(nxt_req_app_link_t)); + if (nxt_slow_path(req_app_link == NULL)) { - if (nxt_slow_path(ra == NULL)) { + req_rpc_data->req_app_link = NULL; - ra_src->rc->ra = NULL; - ra_src->rc = NULL; + if (ra_src != NULL) { + ra_src->req_rpc_data = NULL; + } return NULL; } nxt_mp_retain(mp); - nxt_router_ra_init(task, ra, ra_src->rc); + nxt_request_app_link_init(task, req_app_link, req_rpc_data); - ra->mem_pool = mp; + req_app_link->mem_pool = mp; - return ra; + return req_app_link; } @@ -584,177 +607,187 @@ nxt_router_msg_cancel(nxt_task_t *task, nxt_msg_info_t *msg_info, static void -nxt_router_ra_update_peer(nxt_task_t *task, nxt_req_app_link_t *ra); - - -static void -nxt_router_ra_update_peer_handler(nxt_task_t *task, void *obj, void *data) +nxt_request_app_link_update_peer_handler(nxt_task_t *task, void *obj, + void *data) { - nxt_req_app_link_t *ra; + nxt_request_app_link_t *req_app_link; - ra = obj; + req_app_link = obj; - nxt_router_ra_update_peer(task, ra); + nxt_request_app_link_update_peer(task, req_app_link); - nxt_router_ra_use(task, ra, -1); + nxt_request_app_link_use(task, req_app_link, -1); } static void -nxt_router_ra_update_peer(nxt_task_t *task, nxt_req_app_link_t *ra) +nxt_request_app_link_update_peer(nxt_task_t *task, + nxt_request_app_link_t *req_app_link) { - nxt_event_engine_t *engine; - nxt_req_conn_link_t *rc; + nxt_event_engine_t *engine; + nxt_request_rpc_data_t *req_rpc_data; - engine = ra->work.data; + engine = req_app_link->work.data; if (task->thread->engine != engine) { - nxt_router_ra_inc_use(ra); + nxt_request_app_link_inc_use(req_app_link); - ra->work.handler = nxt_router_ra_update_peer_handler; - ra->work.task = &engine->task; - ra->work.next = NULL; + req_app_link->work.handler = nxt_request_app_link_update_peer_handler; + req_app_link->work.task = &engine->task; + req_app_link->work.next = NULL; - nxt_debug(task, "ra stream #%uD post update peer to %p", - ra->stream, engine); + nxt_debug(task, "req_app_link stream #%uD post update peer to %p", + req_app_link->stream, engine); - nxt_event_engine_post(engine, &ra->work); + nxt_event_engine_post(engine, &req_app_link->work); return; } - nxt_debug(task, "ra stream #%uD update peer", ra->stream); + nxt_debug(task, "req_app_link stream #%uD update peer", + req_app_link->stream); - rc = ra->rc; + req_rpc_data = req_app_link->req_rpc_data; - if (rc != NULL && ra->app_port != NULL) { - nxt_port_rpc_ex_set_peer(task, engine->port, rc, ra->app_port->pid); + if (req_rpc_data != NULL && req_app_link->app_port != NULL) { + nxt_port_rpc_ex_set_peer(task, engine->port, req_rpc_data, + req_app_link->app_port->pid); } - nxt_router_ra_use(task, ra, -1); + nxt_request_app_link_use(task, req_app_link, -1); } static void -nxt_router_ra_release(nxt_task_t *task, nxt_req_app_link_t *ra) +nxt_request_app_link_release(nxt_task_t *task, + nxt_request_app_link_t *req_app_link) { nxt_mp_t *mp; - nxt_req_conn_link_t *rc; + nxt_request_rpc_data_t *req_rpc_data; - nxt_assert(task->thread->engine == ra->work.data); - nxt_assert(ra->use_count == 0); + nxt_assert(task->thread->engine == req_app_link->work.data); + nxt_assert(req_app_link->use_count == 0); - nxt_debug(task, "ra stream #%uD release", ra->stream); + nxt_debug(task, "req_app_link stream #%uD release", req_app_link->stream); - rc = ra->rc; + req_rpc_data = req_app_link->req_rpc_data; - if (rc != NULL) { - if (nxt_slow_path(ra->err_code != 0)) { - nxt_http_request_error(task, rc->request, ra->err_code); + if (req_rpc_data != NULL) { + if (nxt_slow_path(req_app_link->err_code != 0)) { + nxt_http_request_error(task, req_rpc_data->request, + req_app_link->err_code); } else { - rc->app_port = ra->app_port; - rc->msg_info = ra->msg_info; - - if (rc->app->timeout != 0) { - rc->request->timer.handler = nxt_router_app_timeout; - rc->request->timer_data = rc; - nxt_timer_add(task->thread->engine, &rc->request->timer, - rc->app->timeout); + req_rpc_data->app_port = req_app_link->app_port; + req_rpc_data->apr_action = req_app_link->apr_action; + req_rpc_data->msg_info = req_app_link->msg_info; + + if (req_rpc_data->app->timeout != 0) { + req_rpc_data->request->timer.handler = nxt_router_app_timeout; + req_rpc_data->request->timer_data = req_rpc_data; + nxt_timer_add(task->thread->engine, + &req_rpc_data->request->timer, + req_rpc_data->app->timeout); } - ra->app_port = NULL; - ra->msg_info.buf = NULL; + req_app_link->app_port = NULL; + req_app_link->msg_info.buf = NULL; } - rc->ra = NULL; - ra->rc = NULL; + req_rpc_data->req_app_link = NULL; + req_app_link->req_rpc_data = NULL; } - if (ra->app_port != NULL) { - nxt_router_app_port_release(task, ra->app_port, 0, 1); + if (req_app_link->app_port != NULL) { + nxt_router_app_port_release(task, req_app_link->app_port, + req_app_link->apr_action); - ra->app_port = NULL; + req_app_link->app_port = NULL; } - nxt_router_msg_cancel(task, &ra->msg_info, ra->stream); + nxt_router_msg_cancel(task, &req_app_link->msg_info, req_app_link->stream); - mp = ra->mem_pool; + mp = req_app_link->mem_pool; if (mp != NULL) { - nxt_mp_free(mp, ra); + nxt_mp_free(mp, req_app_link); nxt_mp_release(mp); } } static void -nxt_router_ra_release_handler(nxt_task_t *task, void *obj, void *data) +nxt_request_app_link_release_handler(nxt_task_t *task, void *obj, void *data) { - nxt_req_app_link_t *ra; + nxt_request_app_link_t *req_app_link; - ra = obj; + req_app_link = obj; - nxt_assert(ra->work.data == data); + nxt_assert(req_app_link->work.data == data); - nxt_atomic_fetch_add(&ra->use_count, -1); + nxt_atomic_fetch_add(&req_app_link->use_count, -1); - nxt_router_ra_release(task, ra); + nxt_request_app_link_release(task, req_app_link); } static void -nxt_router_ra_use(nxt_task_t *task, nxt_req_app_link_t *ra, int i) +nxt_request_app_link_use(nxt_task_t *task, nxt_request_app_link_t *req_app_link, + int i) { int c; nxt_event_engine_t *engine; - c = nxt_atomic_fetch_add(&ra->use_count, i); + c = nxt_atomic_fetch_add(&req_app_link->use_count, i); if (i < 0 && c == -i) { - engine = ra->work.data; + engine = req_app_link->work.data; if (task->thread->engine == engine) { - nxt_router_ra_release(task, ra); + nxt_request_app_link_release(task, req_app_link); return; } - nxt_router_ra_inc_use(ra); + nxt_request_app_link_inc_use(req_app_link); - ra->work.handler = nxt_router_ra_release_handler; - ra->work.task = &engine->task; - ra->work.next = NULL; + req_app_link->work.handler = nxt_request_app_link_release_handler; + req_app_link->work.task = &engine->task; + req_app_link->work.next = NULL; - nxt_debug(task, "ra stream #%uD post release to %p", - ra->stream, engine); + nxt_debug(task, "req_app_link stream #%uD post release to %p", + req_app_link->stream, engine); - nxt_event_engine_post(engine, &ra->work); + nxt_event_engine_post(engine, &req_app_link->work); } } nxt_inline void -nxt_router_ra_error(nxt_req_app_link_t *ra, int code, const char *str) +nxt_request_app_link_error(nxt_request_app_link_t *req_app_link, int code, + const char *str) { - ra->app_port = NULL; - ra->err_code = code; - ra->err_str = str; + req_app_link->app_port = NULL; + req_app_link->err_code = code; + req_app_link->err_str = str; } nxt_inline void -nxt_router_ra_pending(nxt_task_t *task, nxt_app_t *app, nxt_req_app_link_t *ra) +nxt_request_app_link_pending(nxt_task_t *task, nxt_app_t *app, + nxt_request_app_link_t *req_app_link) { - nxt_queue_insert_tail(&ra->app_port->pending_requests, - &ra->link_port_pending); - nxt_queue_insert_tail(&app->pending, &ra->link_app_pending); + nxt_queue_insert_tail(&req_app_link->app_port->pending_requests, + &req_app_link->link_port_pending); + nxt_queue_insert_tail(&app->pending, &req_app_link->link_app_pending); - nxt_router_ra_inc_use(ra); + nxt_request_app_link_inc_use(req_app_link); - ra->res_time = nxt_thread_monotonic_time(task->thread) + app->res_timeout; + req_app_link->res_time = nxt_thread_monotonic_time(task->thread) + + app->res_timeout; - nxt_debug(task, "ra stream #%uD enqueue to pending_requests", ra->stream); + nxt_debug(task, "req_app_link stream #%uD enqueue to pending_requests", + req_app_link->stream); } @@ -774,60 +807,63 @@ nxt_queue_chk_remove(nxt_queue_link_t *lnk) nxt_inline void -nxt_router_rc_unlink(nxt_task_t *task, nxt_req_conn_link_t *rc) +nxt_request_rpc_data_unlink(nxt_task_t *task, + nxt_request_rpc_data_t *req_rpc_data) { - int ra_use_delta; - nxt_req_app_link_t *ra; + int ra_use_delta; + nxt_request_app_link_t *req_app_link; - if (rc->app_port != NULL) { - nxt_router_app_port_release(task, rc->app_port, 0, 1); + if (req_rpc_data->app_port != NULL) { + nxt_router_app_port_release(task, req_rpc_data->app_port, + req_rpc_data->apr_action); - rc->app_port = NULL; + req_rpc_data->app_port = NULL; } - nxt_router_msg_cancel(task, &rc->msg_info, rc->stream); - - ra = rc->ra; + nxt_router_msg_cancel(task, &req_rpc_data->msg_info, req_rpc_data->stream); - if (ra != NULL) { - rc->ra = NULL; - ra->rc = NULL; + req_app_link = req_rpc_data->req_app_link; + if (req_app_link != NULL) { + req_rpc_data->req_app_link = NULL; + req_app_link->req_rpc_data = NULL; ra_use_delta = 0; - nxt_thread_mutex_lock(&rc->app->mutex); + nxt_thread_mutex_lock(&req_rpc_data->app->mutex); - if (ra->link_app_requests.next == NULL - && ra->link_port_pending.next == NULL - && ra->link_app_pending.next == NULL) + if (req_app_link->link_app_requests.next == NULL + && req_app_link->link_port_pending.next == NULL + && req_app_link->link_app_pending.next == NULL) { - ra = NULL; + req_app_link = NULL; } else { - ra_use_delta -= nxt_queue_chk_remove(&ra->link_app_requests); - ra_use_delta -= nxt_queue_chk_remove(&ra->link_port_pending); - nxt_queue_chk_remove(&ra->link_app_pending); + ra_use_delta -= + nxt_queue_chk_remove(&req_app_link->link_app_requests) + + nxt_queue_chk_remove(&req_app_link->link_port_pending); + + nxt_queue_chk_remove(&req_app_link->link_app_pending); } - nxt_thread_mutex_unlock(&rc->app->mutex); + nxt_thread_mutex_unlock(&req_rpc_data->app->mutex); - if (ra != NULL) { - nxt_router_ra_use(task, ra, ra_use_delta); + if (req_app_link != NULL) { + nxt_request_app_link_use(task, req_app_link, ra_use_delta); } } - if (rc->app != NULL) { - nxt_router_app_use(task, rc->app, -1); + if (req_rpc_data->app != NULL) { + nxt_router_app_use(task, req_rpc_data->app, -1); - rc->app = NULL; + req_rpc_data->app = NULL; } - if (rc->request != NULL) { - rc->request->timer_data = NULL; + if (req_rpc_data->request != NULL) { + req_rpc_data->request->timer_data = NULL; - nxt_router_http_request_done(task, rc->request); + nxt_router_http_request_done(task, req_rpc_data->request); - rc->request = NULL; + req_rpc_data->request = NULL; } } @@ -3380,28 +3416,28 @@ static void nxt_router_response_ready_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, void *data) { - nxt_int_t ret; - nxt_buf_t *b; - nxt_unit_field_t *f; - nxt_http_field_t *field; - nxt_http_request_t *r; - nxt_req_conn_link_t *rc; - nxt_unit_response_t *resp; + nxt_int_t ret; + nxt_buf_t *b; + nxt_unit_field_t *f; + nxt_http_field_t *field; + nxt_http_request_t *r; + nxt_unit_response_t *resp; + nxt_request_rpc_data_t *req_rpc_data; b = msg->buf; - rc = data; + req_rpc_data = data; if (msg->size == 0) { b = NULL; } - r = rc->request; + r = req_rpc_data->request; if (nxt_slow_path(r == NULL)) { return; } if (r->error) { - nxt_router_rc_unlink(task, rc); + nxt_request_rpc_data_unlink(task, req_rpc_data); return; } @@ -3410,13 +3446,14 @@ nxt_router_response_ready_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, nxt_buf_chain_add(&b, nxt_http_buf_last(r)); - nxt_router_rc_unlink(task, rc); + nxt_request_rpc_data_unlink(task, req_rpc_data); } else { - if (rc->app != NULL && rc->app->timeout != 0) { + if (req_rpc_data->app != NULL && req_rpc_data->app->timeout != 0) { r->timer.handler = nxt_router_app_timeout; - r->timer_data = rc; - nxt_timer_add(task->thread->engine, &r->timer, rc->app->timeout); + r->timer_data = req_rpc_data; + nxt_timer_add(task->thread->engine, &r->timer, + req_rpc_data->app->timeout); } } @@ -3504,7 +3541,7 @@ fail: nxt_http_request_error(task, r, NXT_HTTP_SERVICE_UNAVAILABLE); - nxt_router_rc_unlink(task, rc); + nxt_request_rpc_data_unlink(task, req_rpc_data); } @@ -3537,36 +3574,37 @@ static void nxt_router_response_error_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, void *data) { - nxt_int_t res; - nxt_port_t *port; - nxt_bool_t cancelled; - nxt_req_app_link_t *ra; - nxt_req_conn_link_t *rc; + nxt_int_t res; + nxt_port_t *port; + nxt_bool_t cancelled; + nxt_request_app_link_t *req_app_link; + nxt_request_rpc_data_t *req_rpc_data; - rc = data; + req_rpc_data = data; - ra = rc->ra; - - if (ra != NULL) { - cancelled = nxt_router_msg_cancel(task, &ra->msg_info, ra->stream); + req_app_link = req_rpc_data->req_app_link; + if (req_app_link != NULL) { + cancelled = nxt_router_msg_cancel(task, &req_app_link->msg_info, + req_app_link->stream); if (cancelled) { - nxt_router_ra_inc_use(ra); + nxt_request_app_link_inc_use(req_app_link); - res = nxt_router_app_port(task, rc->app, ra); + res = nxt_router_app_port(task, req_rpc_data->app, req_app_link); if (res == NXT_OK) { - port = ra->app_port; + port = req_app_link->app_port; if (nxt_slow_path(port == NULL)) { - nxt_log(task, NXT_LOG_ERR, "port is NULL in cancelled ra"); + nxt_log(task, NXT_LOG_ERR, + "port is NULL in cancelled req_app_link"); return; } - nxt_port_rpc_ex_set_peer(task, task->thread->engine->port, rc, - port->pid); + nxt_port_rpc_ex_set_peer(task, task->thread->engine->port, + req_rpc_data, port->pid); - nxt_router_app_prepare_request(task, ra); + nxt_router_app_prepare_request(task, req_app_link); } msg->port_msg.last = 0; @@ -3575,12 +3613,12 @@ nxt_router_response_error_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, } } - if (rc->request != NULL) { - nxt_http_request_error(task, rc->request, + if (req_rpc_data->request != NULL) { + nxt_http_request_error(task, req_rpc_data->request, NXT_HTTP_SERVICE_UNAVAILABLE); } - nxt_router_rc_unlink(task, rc); + nxt_request_rpc_data_unlink(task, req_rpc_data); } @@ -3624,7 +3662,7 @@ nxt_router_app_port_ready(nxt_task_t *task, nxt_port_recv_msg_t *msg, nxt_debug(task, "app '%V' new port ready, pid %PI, %d/%d", &app->name, port->pid, app->processes, app->pending_processes); - nxt_router_app_port_release(task, port, 0, 0); + nxt_router_app_port_release(task, port, NXT_APR_NEW_PORT); } @@ -3632,10 +3670,10 @@ static void nxt_router_app_port_error(nxt_task_t *task, nxt_port_recv_msg_t *msg, void *data) { - nxt_app_t *app; - nxt_app_joint_t *app_joint; - nxt_queue_link_t *lnk; - nxt_req_app_link_t *ra; + nxt_app_t *app; + nxt_app_joint_t *app_joint; + nxt_queue_link_t *lnk; + nxt_request_app_link_t *req_app_link; app_joint = data; @@ -3664,20 +3702,22 @@ nxt_router_app_port_error(nxt_task_t *task, nxt_port_recv_msg_t *msg, nxt_queue_remove(lnk); lnk->next = NULL; - ra = nxt_queue_link_data(lnk, nxt_req_app_link_t, link_app_requests); + req_app_link = nxt_queue_link_data(lnk, nxt_request_app_link_t, + link_app_requests); } else { - ra = NULL; + req_app_link = NULL; } nxt_thread_mutex_unlock(&app->mutex); - if (ra != NULL) { + if (req_app_link != NULL) { nxt_debug(task, "app '%V' %p abort next stream #%uD", - &app->name, app, ra->stream); + &app->name, app, req_app_link->stream); - nxt_router_ra_error(ra, 500, "Failed to start application process"); - nxt_router_ra_use(task, ra, -1); + nxt_request_app_link_error(req_app_link, 500, + "Failed to start application process"); + nxt_request_app_link_use(task, req_app_link, -1); } } @@ -3811,9 +3851,9 @@ nxt_router_app_unlink(nxt_task_t *task, nxt_app_t *app) static void nxt_router_app_process_request(nxt_task_t *task, void *obj, void *data) { - nxt_req_app_link_t *ra; + nxt_request_app_link_t *req_app_link; - ra = data; + req_app_link = data; #if (NXT_DEBUG) { @@ -3822,39 +3862,62 @@ nxt_router_app_process_request(nxt_task_t *task, void *obj, void *data) app = obj; nxt_assert(app != NULL); - nxt_assert(ra != NULL); - nxt_assert(ra->app_port != NULL); + nxt_assert(req_app_link != NULL); + nxt_assert(req_app_link->app_port != NULL); nxt_debug(task, "app '%V' %p process next stream #%uD", - &app->name, app, ra->stream); + &app->name, app, req_app_link->stream); } #endif - nxt_router_app_prepare_request(task, ra); + nxt_router_app_prepare_request(task, req_app_link); } static void nxt_router_app_port_release(nxt_task_t *task, nxt_port_t *port, - uint32_t request_failed, uint32_t got_response) + nxt_apr_action_t action) { + int inc_use; + uint32_t dec_pending, got_response; nxt_app_t *app; nxt_bool_t port_unchained; nxt_bool_t send_quit, cancelled, adjust_idle_timer; nxt_queue_link_t *lnk; - nxt_req_app_link_t *ra, *pending_ra, *re_ra; + nxt_request_app_link_t *req_app_link, *pending_ra, *re_ra; nxt_port_select_state_t state; nxt_assert(port != NULL); nxt_assert(port->app != NULL); - ra = NULL; + req_app_link = NULL; app = port->app; + inc_use = 0; + dec_pending = 0; + got_response = 0; + + switch (action) { + case NXT_APR_NEW_PORT: + break; + case NXT_APR_REQUEST_FAILED: + dec_pending = 1; + inc_use = -1; + break; + case NXT_APR_GOT_RESPONSE: + dec_pending = 1; + got_response = 1; + inc_use = -1; + break; + case NXT_APR_CLOSE: + inc_use = -1; + break; + } + nxt_thread_mutex_lock(&app->mutex); - port->app_pending_responses -= request_failed + got_response; + port->app_pending_responses -= dec_pending; port->app_responses += got_response; if (port->pair[1] != -1 @@ -3891,24 +3954,25 @@ nxt_router_app_port_release(nxt_task_t *task, nxt_port_t *port, nxt_queue_remove(lnk); lnk->next = NULL; - ra = nxt_queue_link_data(lnk, nxt_req_app_link_t, link_app_requests); + req_app_link = nxt_queue_link_data(lnk, nxt_request_app_link_t, + link_app_requests); - ra->app_port = nxt_router_pop_first_port(app); + req_app_link->app_port = nxt_router_pop_first_port(app); - if (ra->app_port->app_pending_responses > 1) { - nxt_router_ra_pending(task, app, ra); + if (req_app_link->app_port->app_pending_responses > 1) { + nxt_request_app_link_pending(task, app, req_app_link); } } /* Pop first pending request for this port. */ - if ((request_failed > 0 || got_response > 0) + if (dec_pending > 0 && !nxt_queue_is_empty(&port->pending_requests)) { lnk = nxt_queue_first(&port->pending_requests); nxt_queue_remove(lnk); lnk->next = NULL; - pending_ra = nxt_queue_link_data(lnk, nxt_req_app_link_t, + pending_ra = nxt_queue_link_data(lnk, nxt_request_app_link_t, link_port_pending); nxt_assert(pending_ra->link_app_pending.next != NULL); @@ -3924,7 +3988,8 @@ nxt_router_app_port_release(nxt_task_t *task, nxt_port_t *port, if (got_response > 0 && !nxt_queue_is_empty(&app->pending)) { lnk = nxt_queue_first(&app->pending); - re_ra = nxt_queue_link_data(lnk, nxt_req_app_link_t, link_app_pending); + re_ra = nxt_queue_link_data(lnk, nxt_request_app_link_t, + link_app_pending); if (re_ra->res_time <= nxt_thread_monotonic_time(task->thread)) { @@ -3935,9 +4000,9 @@ nxt_router_app_port_release(nxt_task_t *task, nxt_port_t *port, re_ra->stream); if (cancelled) { - nxt_router_ra_inc_use(re_ra); + nxt_request_app_link_inc_use(re_ra); - state.ra = re_ra; + state.req_app_link = re_ra; state.app = app; nxt_router_port_select(task, &state); @@ -3998,7 +4063,7 @@ re_ra_cancelled: } if (pending_ra != NULL) { - nxt_router_ra_use(task, pending_ra, -1); + nxt_request_app_link_use(task, pending_ra, -1); } if (re_ra != NULL) { @@ -4009,12 +4074,12 @@ re_ra_cancelled: } } - if (ra != NULL) { - nxt_router_ra_use(task, ra, -1); + if (req_app_link != NULL) { + nxt_request_app_link_use(task, req_app_link, -1); nxt_work_queue_add(&task->thread->engine->fast_work_queue, nxt_router_app_process_request, - &task->thread->engine->task, app, ra); + &task->thread->engine->task, app, req_app_link); goto adjust_use; } @@ -4046,9 +4111,7 @@ re_ra_cancelled: adjust_use: - if (request_failed > 0 || got_response > 0) { - nxt_port_use(task, port, -1); - } + nxt_port_use(task, port, inc_use); } @@ -4265,33 +4328,33 @@ nxt_router_free_app(nxt_task_t *task, void *obj, void *data) static void nxt_router_port_select(nxt_task_t *task, nxt_port_select_state_t *state) { - nxt_app_t *app; - nxt_bool_t can_start_process; - nxt_req_app_link_t *ra; + nxt_app_t *app; + nxt_bool_t can_start_process; + nxt_request_app_link_t *req_app_link; - ra = state->ra; + req_app_link = state->req_app_link; app = state->app; state->failed_port_use_delta = 0; - if (nxt_queue_chk_remove(&ra->link_app_requests)) + if (nxt_queue_chk_remove(&req_app_link->link_app_requests)) { - nxt_router_ra_dec_use(ra); + nxt_request_app_link_dec_use(req_app_link); } - if (nxt_queue_chk_remove(&ra->link_port_pending)) + if (nxt_queue_chk_remove(&req_app_link->link_port_pending)) { - nxt_assert(ra->link_app_pending.next != NULL); + nxt_assert(req_app_link->link_app_pending.next != NULL); - nxt_queue_remove(&ra->link_app_pending); - ra->link_app_pending.next = NULL; + nxt_queue_remove(&req_app_link->link_app_pending); + req_app_link->link_app_pending.next = NULL; - nxt_router_ra_dec_use(ra); + nxt_request_app_link_dec_use(req_app_link); } - state->failed_port = ra->app_port; + state->failed_port = req_app_link->app_port; - if (ra->app_port != NULL) { + if (req_app_link->app_port != NULL) { state->failed_port_use_delta--; state->failed_port->app_pending_responses--; @@ -4300,7 +4363,7 @@ nxt_router_port_select(nxt_task_t *task, nxt_port_select_state_t *state) state->failed_port_use_delta--; } - ra->app_port = NULL; + req_app_link->app_port = NULL; } can_start_process = nxt_router_app_can_start(app); @@ -4311,22 +4374,25 @@ nxt_router_port_select(nxt_task_t *task, nxt_port_select_state_t *state) if (nxt_queue_is_empty(&app->ports) || (can_start_process && nxt_router_app_first_port_busy(app)) ) { - ra = nxt_router_ra_create(task, ra); - - if (nxt_slow_path(ra == NULL)) { + req_app_link = nxt_request_app_link_alloc(task, req_app_link, + req_app_link->req_rpc_data); + if (nxt_slow_path(req_app_link == NULL)) { goto fail; } if (nxt_slow_path(state->failed_port != NULL)) { - nxt_queue_insert_head(&app->requests, &ra->link_app_requests); + nxt_queue_insert_head(&app->requests, + &req_app_link->link_app_requests); } else { - nxt_queue_insert_tail(&app->requests, &ra->link_app_requests); + nxt_queue_insert_tail(&app->requests, + &req_app_link->link_app_requests); } - nxt_router_ra_inc_use(ra); + nxt_request_app_link_inc_use(req_app_link); - nxt_debug(task, "ra stream #%uD enqueue to app->requests", ra->stream); + nxt_debug(task, "req_app_link stream #%uD enqueue to app->requests", + req_app_link->stream); if (can_start_process) { app->pending_processes++; @@ -4337,15 +4403,15 @@ nxt_router_port_select(nxt_task_t *task, nxt_port_select_state_t *state) state->port = nxt_router_pop_first_port(app); if (state->port->app_pending_responses > 1) { - ra = nxt_router_ra_create(task, ra); - - if (nxt_slow_path(ra == NULL)) { + req_app_link = nxt_request_app_link_alloc(task, req_app_link, + req_app_link->req_rpc_data); + if (nxt_slow_path(req_app_link == NULL)) { goto fail; } - ra->app_port = state->port; + req_app_link->app_port = state->port; - nxt_router_ra_pending(task, app, ra); + nxt_request_app_link_pending(task, app, req_app_link); } if (can_start_process && nxt_router_app_need_start(app)) { @@ -4356,32 +4422,32 @@ nxt_router_port_select(nxt_task_t *task, nxt_port_select_state_t *state) fail: - state->shared_ra = ra; + state->shared_ra = req_app_link; } static nxt_int_t nxt_router_port_post_select(nxt_task_t *task, nxt_port_select_state_t *state) { - nxt_int_t res; - nxt_app_t *app; - nxt_req_app_link_t *ra; + nxt_int_t res; + nxt_app_t *app; + nxt_request_app_link_t *req_app_link; - ra = state->shared_ra; + req_app_link = state->shared_ra; app = state->app; if (state->failed_port_use_delta != 0) { nxt_port_use(task, state->failed_port, state->failed_port_use_delta); } - if (nxt_slow_path(ra == NULL)) { + if (nxt_slow_path(req_app_link == NULL)) { if (state->port != NULL) { nxt_port_use(task, state->port, -1); } - nxt_router_ra_error(state->ra, 500, + nxt_request_app_link_error(state->req_app_link, 500, "Failed to allocate shared req<->app link"); - nxt_router_ra_use(task, state->ra, -1); + nxt_request_app_link_use(task, state->req_app_link, -1); return NXT_ERROR; } @@ -4389,7 +4455,7 @@ nxt_router_port_post_select(nxt_task_t *task, nxt_port_select_state_t *state) if (state->port != NULL) { nxt_debug(task, "already have port for app '%V' %p ", &app->name, app); - ra->app_port = state->port; + req_app_link->app_port = state->port; if (state->start_process) { nxt_router_start_app_process(task, app); @@ -4408,8 +4474,9 @@ nxt_router_port_post_select(nxt_task_t *task, nxt_port_select_state_t *state) res = nxt_router_start_app_process(task, app); if (nxt_slow_path(res != NXT_OK)) { - nxt_router_ra_error(ra, 500, "Failed to start app process"); - nxt_router_ra_use(task, ra, -1); + nxt_request_app_link_error(req_app_link, 500, + "Failed to start app process"); + nxt_request_app_link_use(task, req_app_link, -1); return NXT_ERROR; } @@ -4419,11 +4486,12 @@ nxt_router_port_post_select(nxt_task_t *task, nxt_port_select_state_t *state) static nxt_int_t -nxt_router_app_port(nxt_task_t *task, nxt_app_t *app, nxt_req_app_link_t *ra) +nxt_router_app_port(nxt_task_t *task, nxt_app_t *app, + nxt_request_app_link_t *req_app_link) { nxt_port_select_state_t state; - state.ra = ra; + state.req_app_link = req_app_link; state.app = app; nxt_thread_mutex_lock(&app->mutex); @@ -4440,48 +4508,47 @@ void nxt_router_process_http_request(nxt_task_t *task, nxt_http_request_t *r, nxt_app_t *app) { - nxt_int_t res; - nxt_port_t *port; - nxt_event_engine_t *engine; - nxt_req_app_link_t ra_local, *ra; - nxt_req_conn_link_t *rc; + nxt_int_t res; + nxt_port_t *port; + nxt_event_engine_t *engine; + nxt_request_app_link_t ra_local, *req_app_link; + nxt_request_rpc_data_t *req_rpc_data; engine = task->thread->engine; - rc = nxt_port_rpc_register_handler_ex(task, engine->port, + req_rpc_data = nxt_port_rpc_register_handler_ex(task, engine->port, nxt_router_response_ready_handler, nxt_router_response_error_handler, - sizeof(nxt_req_conn_link_t)); - - if (nxt_slow_path(rc == NULL)) { + sizeof(nxt_request_rpc_data_t)); + if (nxt_slow_path(req_rpc_data == NULL)) { nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR); return; } - rc->stream = nxt_port_rpc_ex_stream(rc); - rc->app = app; + req_rpc_data->stream = nxt_port_rpc_ex_stream(req_rpc_data); + req_rpc_data->app = app; nxt_router_app_use(task, app, 1); - rc->request = r; + req_rpc_data->request = r; - ra = &ra_local; - nxt_router_ra_init(task, ra, rc); + req_app_link = &ra_local; + nxt_request_app_link_init(task, req_app_link, req_rpc_data); - res = nxt_router_app_port(task, app, ra); + res = nxt_router_app_port(task, app, req_app_link); if (res != NXT_OK) { return; } - ra = rc->ra; - port = ra->app_port; + req_app_link = req_rpc_data->req_app_link; + port = req_app_link->app_port; nxt_assert(port != NULL); - nxt_port_rpc_ex_set_peer(task, engine->port, rc, port->pid); + nxt_port_rpc_ex_set_peer(task, engine->port, req_rpc_data, port->pid); - nxt_router_app_prepare_request(task, ra); + nxt_router_app_prepare_request(task, req_app_link); } @@ -4492,19 +4559,20 @@ nxt_router_dummy_buf_completion(nxt_task_t *task, void *obj, void *data) static void -nxt_router_app_prepare_request(nxt_task_t *task, nxt_req_app_link_t *ra) +nxt_router_app_prepare_request(nxt_task_t *task, + nxt_request_app_link_t *req_app_link) { - uint32_t request_failed; - nxt_buf_t *buf; - nxt_int_t res; - nxt_port_t *port, *c_port, *reply_port; + nxt_buf_t *buf; + nxt_int_t res; + nxt_port_t *port, *c_port, *reply_port; + nxt_apr_action_t apr_action; - nxt_assert(ra->app_port != NULL); + nxt_assert(req_app_link->app_port != NULL); - port = ra->app_port; - reply_port = ra->reply_port; + port = req_app_link->app_port; + reply_port = req_app_link->reply_port; - request_failed = 1; + apr_action = NXT_APR_REQUEST_FAILED; c_port = nxt_process_connected_port_find(port->process, reply_port->pid, reply_port->id); @@ -4512,7 +4580,7 @@ nxt_router_app_prepare_request(nxt_task_t *task, nxt_req_app_link_t *ra) res = nxt_port_send_port(task, port, reply_port, 0); if (nxt_slow_path(res != NXT_OK)) { - nxt_router_ra_error(ra, 500, + nxt_request_app_link_error(req_app_link, 500, "Failed to send reply port to application"); goto release_port; } @@ -4520,11 +4588,11 @@ nxt_router_app_prepare_request(nxt_task_t *task, nxt_req_app_link_t *ra) nxt_process_connected_port_add(port->process, reply_port); } - buf = nxt_router_prepare_msg(task, ra->request, port, + buf = nxt_router_prepare_msg(task, req_app_link->request, port, nxt_app_msg_prefix[port->app->type]); if (nxt_slow_path(buf == NULL)) { - nxt_router_ra_error(ra, 500, + nxt_request_app_link_error(req_app_link, 500, "Failed to prepare message for application"); goto release_port; } @@ -4533,40 +4601,41 @@ nxt_router_app_prepare_request(nxt_task_t *task, nxt_req_app_link_t *ra) nxt_buf_used_size(buf), port->socket.fd); - request_failed = 0; + apr_action = NXT_APR_NEW_PORT; - ra->msg_info.buf = buf; - ra->msg_info.completion_handler = buf->completion_handler; + req_app_link->msg_info.buf = buf; + req_app_link->msg_info.completion_handler = buf->completion_handler; for (; buf; buf = buf->next) { buf->completion_handler = nxt_router_dummy_buf_completion; } - buf = ra->msg_info.buf; + buf = req_app_link->msg_info.buf; - res = nxt_port_mmap_get_tracking(task, port, &ra->msg_info.tracking, - ra->stream); + res = nxt_port_mmap_get_tracking(task, port, + &req_app_link->msg_info.tracking, + req_app_link->stream); if (nxt_slow_path(res != NXT_OK)) { - nxt_router_ra_error(ra, 500, - "Failed to get tracking area"); + nxt_request_app_link_error(req_app_link, 500, + "Failed to get tracking area"); goto release_port; } res = nxt_port_socket_twrite(task, port, NXT_PORT_MSG_DATA, - -1, ra->stream, reply_port->id, buf, - &ra->msg_info.tracking); + -1, req_app_link->stream, reply_port->id, buf, + &req_app_link->msg_info.tracking); if (nxt_slow_path(res != NXT_OK)) { - nxt_router_ra_error(ra, 500, - "Failed to send message to application"); + nxt_request_app_link_error(req_app_link, 500, + "Failed to send message to application"); goto release_port; } release_port: - nxt_router_app_port_release(task, port, request_failed, 0); + nxt_router_app_port_release(task, port, apr_action); - nxt_router_ra_update_peer(task, ra); + nxt_request_app_link_update_peer(task, req_app_link); } @@ -4897,8 +4966,8 @@ nxt_router_app_timeout(nxt_task_t *task, void *obj, void *data) nxt_timer_t *timer; nxt_queue_link_t *lnk; nxt_http_request_t *r; - nxt_req_app_link_t *pending_ra; - nxt_req_conn_link_t *rc; + nxt_request_app_link_t *pending_ra; + nxt_request_rpc_data_t *req_rpc_data; nxt_port_select_state_t state; timer = obj; @@ -4906,8 +4975,8 @@ nxt_router_app_timeout(nxt_task_t *task, void *obj, void *data) nxt_debug(task, "router app timeout"); r = nxt_timer_data(timer, nxt_http_request_t, timer); - rc = r->timer_data; - app = rc->app; + req_rpc_data = r->timer_data; + app = req_rpc_data->app; if (app == NULL) { goto generate_error; @@ -4916,14 +4985,16 @@ nxt_router_app_timeout(nxt_task_t *task, void *obj, void *data) port = NULL; pending_ra = NULL; - if (rc->app_port != NULL) { - port = rc->app_port; - rc->app_port = NULL; + if (req_rpc_data->app_port != NULL) { + port = req_rpc_data->app_port; + req_rpc_data->app_port = NULL; } - if (port == NULL && rc->ra != NULL && rc->ra->app_port != NULL) { - port = rc->ra->app_port; - rc->ra->app_port = NULL; + if (port == NULL && req_rpc_data->req_app_link != NULL + && req_rpc_data->req_app_link->app_port != NULL) + { + port = req_rpc_data->req_app_link->app_port; + req_rpc_data->req_app_link->app_port = NULL; } if (port == NULL) { @@ -4937,7 +5008,7 @@ nxt_router_app_timeout(nxt_task_t *task, void *obj, void *data) if (!nxt_queue_is_empty(&port->pending_requests)) { lnk = nxt_queue_first(&port->pending_requests); - pending_ra = nxt_queue_link_data(lnk, nxt_req_app_link_t, + pending_ra = nxt_queue_link_data(lnk, nxt_request_app_link_t, link_port_pending); nxt_assert(pending_ra->link_app_pending.next != NULL); @@ -4949,9 +5020,9 @@ nxt_router_app_timeout(nxt_task_t *task, void *obj, void *data) pending_ra->stream); if (cancelled) { - nxt_router_ra_inc_use(pending_ra); + nxt_request_app_link_inc_use(pending_ra); - state.ra = pending_ra; + state.req_app_link = pending_ra; state.app = app; nxt_router_port_select(task, &state); @@ -4979,7 +5050,7 @@ generate_error: nxt_http_request_error(task, r, NXT_HTTP_SERVICE_UNAVAILABLE); - nxt_router_rc_unlink(task, rc); + nxt_request_rpc_data_unlink(task, req_rpc_data); } diff --git a/src/nxt_router.h b/src/nxt_router.h index d9fbfe05..ff791e3d 100644 --- a/src/nxt_router.h +++ b/src/nxt_router.h @@ -108,8 +108,8 @@ struct nxt_app_s { nxt_work_t adjust_idle_work; nxt_event_engine_t *engine; - nxt_queue_t requests; /* of nxt_req_app_link_t */ - nxt_queue_t pending; /* of nxt_req_app_link_t */ + nxt_queue_t requests; /* of nxt_request_app_link_t */ + nxt_queue_t pending; /* of nxt_request_app_link_t */ nxt_str_t name; uint32_t pending_processes; -- cgit From 4d7576d3239b3be6db2b1083a3d99b31ce4f4cd0 Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Wed, 14 Aug 2019 16:26:47 +0300 Subject: Tests: print decoded strings in detailed mode, if possible. --- test/unit/http.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/test/unit/http.py b/test/unit/http.py index 1ce86e5a..c0af8a9e 100644 --- a/test/unit/http.py +++ b/test/unit/http.py @@ -81,7 +81,11 @@ class TestHTTP(TestUnit): sock.sendall(req) if TestUnit.detailed: - print('>>>', req, sep='\n') + print('>>>') + try: + print(req.decode('utf-8', 'ignore')) + except UnicodeEncodeError: + print(req) resp = '' @@ -93,7 +97,11 @@ class TestHTTP(TestUnit): resp = self.recvall(sock, read_timeout=read_timeout).decode(enc) if TestUnit.detailed: - print('<<<', resp.encode('utf-8'), sep='\n') + print('<<<') + try: + print(resp) + except UnicodeEncodeError: + print(resp.encode()) if 'raw_resp' not in kwargs: resp = self._resp_to_dict(resp) -- cgit From caea9d3c07543fecf9035ff2b406c190b714989e Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Fri, 16 Aug 2019 00:48:11 +0300 Subject: Fixing multi-thread port write racing conditions. --- src/nxt_port.h | 8 +- src/nxt_port_memory.c | 6 +- src/nxt_port_memory.h | 2 +- src/nxt_port_socket.c | 308 ++++++++++++++++++++++++++------------------------ 4 files changed, 165 insertions(+), 159 deletions(-) diff --git a/src/nxt_port.h b/src/nxt_port.h index 76faa7d2..e4e76693 100644 --- a/src/nxt_port.h +++ b/src/nxt_port.h @@ -134,11 +134,10 @@ typedef struct { nxt_buf_t *buf; size_t share; nxt_fd_t fd; - nxt_bool_t close_fd; nxt_port_msg_t port_msg; uint32_t tracking_msg[2]; - - nxt_work_t work; + uint8_t close_fd; /* 1 bit */ + uint8_t allocated; /* 1 bit */ } nxt_port_send_msg_t; @@ -202,9 +201,6 @@ struct nxt_port_s { nxt_atomic_t use_count; nxt_process_type_t type; - - struct iovec *iov; - void *mmsg_buf; }; diff --git a/src/nxt_port_memory.c b/src/nxt_port_memory.c index b908041c..b7068c88 100644 --- a/src/nxt_port_memory.c +++ b/src/nxt_port_memory.c @@ -798,7 +798,7 @@ nxt_port_mmap_get_incoming_buf(nxt_task_t *task, nxt_port_t *port, void nxt_port_mmap_write(nxt_task_t *task, nxt_port_t *port, - nxt_port_send_msg_t *msg, nxt_sendbuf_coalesce_t *sb) + nxt_port_send_msg_t *msg, nxt_sendbuf_coalesce_t *sb, void *mmsg_buf) { size_t bsize; nxt_buf_t *bmem; @@ -811,7 +811,7 @@ nxt_port_mmap_write(nxt_task_t *task, nxt_port_t *port, "via shared memory", sb->size, port->pid); bsize = sb->niov * sizeof(nxt_port_mmap_msg_t); - mmap_msg = port->mmsg_buf; + mmap_msg = mmsg_buf; bmem = msg->buf; @@ -841,7 +841,7 @@ nxt_port_mmap_write(nxt_task_t *task, nxt_port_t *port, port->pid); } - sb->iobuf[0].iov_base = port->mmsg_buf; + sb->iobuf[0].iov_base = mmsg_buf; sb->iobuf[0].iov_len = bsize; sb->niov = 1; sb->size = bsize; diff --git a/src/nxt_port_memory.h b/src/nxt_port_memory.h index c6a49ccf..748549b1 100644 --- a/src/nxt_port_memory.h +++ b/src/nxt_port_memory.h @@ -55,7 +55,7 @@ nxt_port_incoming_port_mmap(nxt_task_t *task, nxt_process_t *process, void nxt_port_mmap_write(nxt_task_t *task, nxt_port_t *port, - nxt_port_send_msg_t *msg, nxt_sendbuf_coalesce_t *sb); + nxt_port_send_msg_t *msg, nxt_sendbuf_coalesce_t *sb, void *mmsg_buf); void nxt_port_mmap_read(nxt_task_t *task, nxt_port_recv_msg_t *msg); diff --git a/src/nxt_port_socket.c b/src/nxt_port_socket.c index fe113a68..4edc423a 100644 --- a/src/nxt_port_socket.c +++ b/src/nxt_port_socket.c @@ -7,9 +7,15 @@ #include +static nxt_int_t nxt_port_msg_chk_insert(nxt_task_t *task, nxt_port_t *port, + nxt_port_send_msg_t *msg); +static nxt_port_send_msg_t *nxt_port_msg_alloc(nxt_port_send_msg_t *m); static void nxt_port_write_handler(nxt_task_t *task, void *obj, void *data); +static nxt_port_send_msg_t *nxt_port_msg_first(nxt_port_t *port); static nxt_buf_t *nxt_port_buf_completion(nxt_task_t *task, nxt_work_queue_t *wq, nxt_buf_t *b, size_t sent, nxt_bool_t mmap_mode); +static nxt_port_send_msg_t *nxt_port_msg_insert_tail(nxt_port_t *port, + nxt_port_send_msg_t *msg); static void nxt_port_read_handler(nxt_task_t *task, void *obj, void *data); static void nxt_port_read_msg_process(nxt_task_t *task, nxt_port_t *port, nxt_port_recv_msg_t *msg); @@ -116,13 +122,6 @@ nxt_port_write_enable(nxt_task_t *task, nxt_port_t *port) port->socket.write_work_queue = &port->engine->fast_work_queue; port->socket.write_handler = nxt_port_write_handler; port->socket.error_handler = nxt_port_error_handler; - - if (port->iov == NULL) { - port->iov = nxt_mp_get(port->mem_pool, - sizeof(struct iovec) * NXT_IOBUF_MAX * 10); - port->mmsg_buf = nxt_mp_get(port->mem_pool, - sizeof(uint32_t) * 3 * NXT_IOBUF_MAX * 10); - } } @@ -135,109 +134,11 @@ nxt_port_write_close(nxt_port_t *port) static void -nxt_port_release_send_msg(nxt_task_t *task, void *obj, void *data) -{ - nxt_event_engine_t *engine; - nxt_port_send_msg_t *msg; - - msg = obj; - engine = data; - - nxt_assert(data == msg->work.data); - - if (engine != task->thread->engine) { - - nxt_debug(task, "current thread is %PT, expected %PT", - task->thread->tid, engine->task.thread->tid); - - nxt_event_engine_post(engine, &msg->work); - - return; - } - - nxt_mp_free(engine->mem_pool, obj); - nxt_mp_release(engine->mem_pool); -} - - -static nxt_port_send_msg_t * -nxt_port_msg_create(nxt_task_t *task, nxt_port_send_msg_t *m) -{ - nxt_mp_t *mp; - nxt_port_send_msg_t *msg; - - mp = task->thread->engine->mem_pool; - - msg = nxt_mp_alloc(mp, sizeof(nxt_port_send_msg_t)); - if (nxt_slow_path(msg == NULL)) { - return NULL; - } - - nxt_mp_retain(mp); - - msg->link.next = NULL; - msg->link.prev = NULL; - - msg->buf = m->buf; - msg->share = m->share; - msg->fd = m->fd; - msg->close_fd = m->close_fd; - msg->port_msg = m->port_msg; - - msg->work.next = NULL; - msg->work.handler = nxt_port_release_send_msg; - msg->work.task = task; - msg->work.obj = msg; - msg->work.data = task->thread->engine; - - return msg; -} - - -static nxt_port_send_msg_t * -nxt_port_msg_insert_head(nxt_task_t *task, nxt_port_t *port, - nxt_port_send_msg_t *msg) +nxt_port_release_send_msg(nxt_port_send_msg_t *msg) { - if (msg->work.data == NULL) { - msg = nxt_port_msg_create(task, msg); + if (msg->allocated) { + nxt_free(msg); } - - if (msg != NULL) { - nxt_queue_insert_head(&port->messages, &msg->link); - } - - return msg; -} - - -static nxt_port_send_msg_t * -nxt_port_msg_insert_tail(nxt_task_t *task, nxt_port_t *port, - nxt_port_send_msg_t *msg) -{ - if (msg->work.data == NULL) { - msg = nxt_port_msg_create(task, msg); - } - - if (msg != NULL) { - nxt_queue_insert_tail(&port->messages, &msg->link); - } - - return msg; -} - - -static nxt_port_send_msg_t * -nxt_port_msg_first(nxt_task_t *task, nxt_port_t *port, nxt_port_send_msg_t *msg) -{ - nxt_queue_link_t *lnk; - - lnk = nxt_queue_first(&port->messages); - - if (lnk == nxt_queue_tail(&port->messages)) { - return msg; - } - - return nxt_queue_link_data(lnk, nxt_port_send_msg_t, link); } @@ -246,15 +147,17 @@ nxt_port_socket_twrite(nxt_task_t *task, nxt_port_t *port, nxt_uint_t type, nxt_fd_t fd, uint32_t stream, nxt_port_id_t reply_port, nxt_buf_t *b, void *tracking) { - nxt_port_send_msg_t msg, *res; + nxt_int_t res; + nxt_port_send_msg_t msg; msg.link.next = NULL; msg.link.prev = NULL; msg.buf = b; + msg.share = 0; msg.fd = fd; msg.close_fd = (type & NXT_PORT_MSG_CLOSE_FD) != 0; - msg.share = 0; + msg.allocated = 0; if (tracking != NULL) { nxt_port_mmap_tracking_write(msg.tracking_msg, tracking); @@ -270,25 +173,63 @@ nxt_port_socket_twrite(nxt_task_t *task, nxt_port_t *port, nxt_uint_t type, msg.port_msg.mf = 0; msg.port_msg.tracking = tracking != NULL; - msg.work.data = NULL; - - if (port->socket.write_ready) { + res = nxt_port_msg_chk_insert(task, port, &msg); + if (nxt_fast_path(res == NXT_DECLINED)) { nxt_port_write_handler(task, &port->socket, &msg); - } else { - nxt_thread_mutex_lock(&port->write_mutex); + res = NXT_OK; + } + + return res; +} + - res = nxt_port_msg_insert_tail(task, port, &msg); +static nxt_int_t +nxt_port_msg_chk_insert(nxt_task_t *task, nxt_port_t *port, + nxt_port_send_msg_t *msg) +{ + nxt_int_t res; + + nxt_thread_mutex_lock(&port->write_mutex); + + if (nxt_fast_path(port->socket.write_ready + && nxt_queue_is_empty(&port->messages))) + { + res = NXT_DECLINED; + + } else { + msg = nxt_port_msg_alloc(msg); - nxt_thread_mutex_unlock(&port->write_mutex); + if (nxt_fast_path(msg != NULL)) { + nxt_queue_insert_tail(&port->messages, &msg->link); + nxt_port_use(task, port, 1); + res = NXT_OK; - if (res == NULL) { - return NXT_ERROR; + } else { + res = NXT_ERROR; } + } + + nxt_thread_mutex_unlock(&port->write_mutex); + + return res; +} + + +static nxt_port_send_msg_t * +nxt_port_msg_alloc(nxt_port_send_msg_t *m) +{ + nxt_port_send_msg_t *msg; - nxt_port_use(task, port, 1); + msg = nxt_malloc(sizeof(nxt_port_send_msg_t)); + if (nxt_slow_path(msg == NULL)) { + return NULL; } - return NXT_OK; + *msg = *m; + + msg->allocated = 1; + + return msg; } @@ -312,9 +253,10 @@ nxt_port_write_handler(nxt_task_t *task, void *obj, void *data) int use_delta; size_t plain_size; ssize_t n; + uint32_t mmsg_buf[3 * NXT_IOBUF_MAX * 10]; nxt_bool_t block_write, enable_write; nxt_port_t *port; - struct iovec *iov; + struct iovec iov[NXT_IOBUF_MAX * 10]; nxt_work_queue_t *wq; nxt_port_method_t m; nxt_port_send_msg_t *msg; @@ -326,20 +268,23 @@ nxt_port_write_handler(nxt_task_t *task, void *obj, void *data) enable_write = 0; use_delta = 0; - nxt_thread_mutex_lock(&port->write_mutex); - - iov = port->iov; - wq = &task->thread->engine->fast_work_queue; do { - msg = nxt_port_msg_first(task, port, data); + if (data) { + msg = data; - if (msg == NULL) { - block_write = 1; - goto unlock_mutex; + } else { + msg = nxt_port_msg_first(port); + + if (msg == NULL) { + block_write = 1; + goto cleanup; + } } +next_fragment: + iov[0].iov_base = &msg->port_msg; iov[0].iov_len = sizeof(nxt_port_msg_t); @@ -377,7 +322,7 @@ nxt_port_write_handler(nxt_task_t *task, void *obj, void *data) * is bigger than PORT_MMAP_MIN_SIZE. */ if (m == NXT_PORT_METHOD_MMAP && plain_size > PORT_MMAP_MIN_SIZE) { - nxt_port_mmap_write(task, port, msg, &sb); + nxt_port_mmap_write(task, port, msg, &sb, mmsg_buf); } else { m = NXT_PORT_METHOD_PLAIN; @@ -402,7 +347,7 @@ nxt_port_write_handler(nxt_task_t *task, void *obj, void *data) } msg->buf = nxt_port_buf_completion(task, wq, msg->buf, plain_size, - m == NXT_PORT_METHOD_MMAP); + m == NXT_PORT_METHOD_MMAP); if (msg->buf != NULL) { nxt_debug(task, "port %d: frag stream #%uD", port->socket.fd, @@ -421,36 +366,58 @@ nxt_port_write_handler(nxt_task_t *task, void *obj, void *data) msg->share = 0; if (msg->link.next != NULL) { + nxt_thread_mutex_lock(&port->write_mutex); + nxt_queue_remove(&msg->link); - use_delta--; - } - data = NULL; + nxt_queue_insert_tail(&port->messages, &msg->link); + + nxt_thread_mutex_unlock(&port->write_mutex); + + } else { + msg = nxt_port_msg_insert_tail(port, msg); + if (nxt_slow_path(msg == NULL)) { + goto fail; + } - if (nxt_port_msg_insert_tail(task, port, msg) != NULL) { use_delta++; } + + } else { + goto next_fragment; } } else { if (msg->link.next != NULL) { + nxt_thread_mutex_lock(&port->write_mutex); + nxt_queue_remove(&msg->link); + msg->link.next = NULL; + + nxt_thread_mutex_unlock(&port->write_mutex); + use_delta--; - nxt_work_queue_add(wq, nxt_port_release_send_msg, task, msg, - msg->work.data); } - data = NULL; + + nxt_port_release_send_msg(msg); } - } else { - if (msg->link.next == NULL) { - if (nxt_port_msg_insert_head(task, port, msg) != NULL) { - use_delta++; - } + if (data != NULL) { + goto cleanup; } + } else { if (nxt_slow_path(n == NXT_ERROR)) { goto fail; } + + if (msg->link.next == NULL) { + msg = nxt_port_msg_insert_tail(port, msg); + if (nxt_slow_path(msg == NULL)) { + goto fail; + } + + use_delta++; + } } } while (port->socket.write_ready); @@ -459,7 +426,7 @@ nxt_port_write_handler(nxt_task_t *task, void *obj, void *data) enable_write = 1; } - goto unlock_mutex; + goto cleanup; fail: @@ -468,8 +435,7 @@ fail: nxt_work_queue_add(wq, nxt_port_error_handler, task, &port->socket, &port->socket); -unlock_mutex: - nxt_thread_mutex_unlock(&port->write_mutex); +cleanup: if (block_write && nxt_fd_event_is_active(port->socket.write)) { nxt_port_post(task, port, nxt_port_fd_block_write, NULL); @@ -485,6 +451,29 @@ unlock_mutex: } +static nxt_port_send_msg_t * +nxt_port_msg_first(nxt_port_t *port) +{ + nxt_queue_link_t *lnk; + nxt_port_send_msg_t *msg; + + nxt_thread_mutex_lock(&port->write_mutex); + + lnk = nxt_queue_first(&port->messages); + + if (lnk == nxt_queue_tail(&port->messages)) { + msg = NULL; + + } else { + msg = nxt_queue_link_data(lnk, nxt_port_send_msg_t, link); + } + + nxt_thread_mutex_unlock(&port->write_mutex); + + return msg; +} + + static nxt_buf_t * nxt_port_buf_completion(nxt_task_t *task, nxt_work_queue_t *wq, nxt_buf_t *b, size_t sent, nxt_bool_t mmap_mode) @@ -546,6 +535,27 @@ nxt_port_buf_completion(nxt_task_t *task, nxt_work_queue_t *wq, nxt_buf_t *b, } +static nxt_port_send_msg_t * +nxt_port_msg_insert_tail(nxt_port_t *port, nxt_port_send_msg_t *msg) +{ + if (msg->allocated == 0) { + msg = nxt_port_msg_alloc(msg); + + if (nxt_slow_path(msg == NULL)) { + return NULL; + } + } + + nxt_thread_mutex_lock(&port->write_mutex); + + nxt_queue_insert_tail(&port->messages, &msg->link); + + nxt_thread_mutex_unlock(&port->write_mutex); + + return msg; +} + + void nxt_port_read_enable(nxt_task_t *task, nxt_port_t *port) { @@ -986,8 +996,8 @@ nxt_port_error_handler(nxt_task_t *task, void *obj, void *data) nxt_queue_remove(&msg->link); use_delta--; - nxt_work_queue_add(wq, nxt_port_release_send_msg, task, msg, - msg->work.data); + + nxt_port_release_send_msg(msg); } nxt_queue_loop; -- cgit From 29911538ea91705fcdcbcf0e271cfbc5c8ed674b Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Fri, 16 Aug 2019 00:56:38 +0300 Subject: Improving response header fields processing. Fields are filtered one by one before being added to fields list. This avoids adding and then skipping connection-specific fields. --- src/nxt_http_parse.c | 23 ++++------------------- src/nxt_http_parse.h | 24 ++++++++++++++++++++++++ src/nxt_router.c | 26 ++++++++++++++++++-------- 3 files changed, 46 insertions(+), 27 deletions(-) diff --git a/src/nxt_http_parse.c b/src/nxt_http_parse.c index 05df245e..8b4bf47c 100644 --- a/src/nxt_http_parse.c +++ b/src/nxt_http_parse.c @@ -1110,7 +1110,7 @@ done: } -static const nxt_lvlhsh_proto_t nxt_http_fields_hash_proto nxt_aligned(64) = { +const nxt_lvlhsh_proto_t nxt_http_fields_hash_proto nxt_aligned(64) = { NXT_LVLHSH_BUCKET_SIZE(64), { NXT_HTTP_FIELD_LVLHSH_SHIFT, 0, 0, 0, 0, 0, 0, 0 }, nxt_http_field_hash_test, @@ -1240,27 +1240,12 @@ nxt_http_fields_hash_collisions(nxt_lvlhsh_t *hash, nxt_mp_t *mp, nxt_int_t nxt_http_fields_process(nxt_list_t *fields, nxt_lvlhsh_t *hash, void *ctx) { - nxt_int_t ret; - nxt_http_field_t *field; - nxt_lvlhsh_query_t lhq; - nxt_http_field_proc_t *proc; - - lhq.proto = &nxt_http_fields_hash_proto; + nxt_int_t ret; + nxt_http_field_t *field; nxt_list_each(field, fields) { - lhq.key_hash = field->hash; - lhq.key.length = field->name_length; - lhq.key.start = field->name; - - if (nxt_lvlhsh_find(hash, &lhq) != NXT_OK) { - continue; - } - - proc = lhq.value; - - ret = proc->handler(ctx, field, proc->data); - + ret = nxt_http_field_process(field, hash, ctx); if (nxt_slow_path(ret != NXT_OK)) { return ret; } diff --git a/src/nxt_http_parse.h b/src/nxt_http_parse.h index 6c629936..c5b11bf3 100644 --- a/src/nxt_http_parse.h +++ b/src/nxt_http_parse.h @@ -113,4 +113,28 @@ nxt_int_t nxt_http_fields_process(nxt_list_t *fields, nxt_lvlhsh_t *hash, void *ctx); +const nxt_lvlhsh_proto_t nxt_http_fields_hash_proto; + +nxt_inline nxt_int_t +nxt_http_field_process(nxt_http_field_t *field, nxt_lvlhsh_t *hash, void *ctx) +{ + nxt_lvlhsh_query_t lhq; + nxt_http_field_proc_t *proc; + + lhq.proto = &nxt_http_fields_hash_proto; + + lhq.key_hash = field->hash; + lhq.key.length = field->name_length; + lhq.key.start = field->name; + + if (nxt_lvlhsh_find(hash, &lhq) != NXT_OK) { + return NXT_OK; + } + + proc = lhq.value; + + return proc->handler(ctx, field, proc->data); +} + + #endif /* _NXT_HTTP_PARSER_H_INCLUDED_ */ diff --git a/src/nxt_router.c b/src/nxt_router.c index f09779bc..509f487a 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -3483,7 +3483,13 @@ nxt_router_response_ready_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, goto fail; } + field = NULL; + for (f = resp->fields; f < resp->fields + resp->fields_count; f++) { + if (f->skip) { + continue; + } + field = nxt_list_add(r->resp.fields); if (nxt_slow_path(field == NULL)) { @@ -3491,26 +3497,30 @@ nxt_router_response_ready_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, } field->hash = f->hash; - field->skip = f->skip; + field->skip = 0; field->name_length = f->name_length; field->value_length = f->value_length; field->name = nxt_unit_sptr_get(&f->name); field->value = nxt_unit_sptr_get(&f->value); - nxt_debug(task, "header: %*s: %*s", + ret = nxt_http_field_process(field, &nxt_response_fields_hash, r); + if (nxt_slow_path(ret != NXT_OK)) { + goto fail; + } + + nxt_debug(task, "header%s: %*s: %*s", + (field->skip ? " skipped" : ""), (size_t) field->name_length, field->name, (size_t) field->value_length, field->value); + + if (field->skip) { + r->resp.fields->last->nelts--; + } } r->status = resp->status; - ret = nxt_http_fields_process(r->resp.fields, - &nxt_response_fields_hash, r); - if (nxt_slow_path(ret != NXT_OK)) { - goto fail; - } - if (resp->piggyback_content_length != 0) { b->mem.pos = nxt_unit_sptr_get(&resp->piggyback_content); b->mem.free = b->mem.pos + resp->piggyback_content_length; -- cgit From 686f5b1436a318135f288b563cb6e97fcdc9d8f8 Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Fri, 16 Aug 2019 14:55:18 +0300 Subject: Changing the sequence of body send execution. Request state ready_handler required for further websocket events processing. It is not required for regular response transferring. --- src/nxt_h1proto.c | 8 +------- src/nxt_http_error.c | 6 ++++-- src/nxt_router.c | 8 ++++++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/nxt_h1proto.c b/src/nxt_h1proto.c index b0bd39e1..a92dfe3b 100644 --- a/src/nxt_h1proto.c +++ b/src/nxt_h1proto.c @@ -908,7 +908,6 @@ nxt_h1p_request_header_send(nxt_task_t *task, nxt_http_request_t *r) nxt_h1proto_t *h1p; const nxt_str_t *status; nxt_http_field_t *field; - nxt_event_engine_t *engine; u_char buf[UNKNOWN_STATUS_LENGTH]; static const char chunked[] = "Transfer-Encoding: chunked\r\n"; @@ -1031,12 +1030,7 @@ nxt_h1p_request_header_send(nxt_task_t *task, nxt_http_request_t *r) c->write = header; c->write_state = &nxt_h1p_request_send_state; - engine = task->thread->engine; - - nxt_work_queue_add(&engine->fast_work_queue, r->state->ready_handler, - task, r, NULL); - - nxt_conn_write(engine, c); + nxt_conn_write(task->thread->engine, c); } diff --git a/src/nxt_http_error.c b/src/nxt_http_error.c index 99d27903..c7c7e81a 100644 --- a/src/nxt_http_error.c +++ b/src/nxt_http_error.c @@ -51,9 +51,12 @@ nxt_http_request_error(nxt_task_t *task, nxt_http_request_t *r, r->resp.content_length = NULL; r->resp.content_length_n = nxt_length(error); + nxt_http_request_header_send(task, r); + r->state = &nxt_http_request_send_error_body_state; - nxt_http_request_header_send(task, r); + nxt_work_queue_add(&task->thread->engine->fast_work_queue, + nxt_http_request_send_error_body, task, r, NULL); return; fail: @@ -65,7 +68,6 @@ fail: static const nxt_http_request_state_t nxt_http_request_send_error_body_state nxt_aligned(64) = { - .ready_handler = nxt_http_request_send_error_body, .error_handler = nxt_http_request_error_handler, }; diff --git a/src/nxt_router.c b/src/nxt_router.c index 509f487a..566e0c65 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -3540,9 +3540,14 @@ nxt_router_response_ready_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, nxt_buf_chain_add(&r->out, b); } + nxt_http_request_header_send(task, r); + r->state = &nxt_http_request_send_state; - nxt_http_request_header_send(task, r); + if (r->out) { + nxt_work_queue_add(&task->thread->engine->fast_work_queue, + nxt_http_request_send_body, task, r, NULL); + } } return; @@ -3558,7 +3563,6 @@ fail: static const nxt_http_request_state_t nxt_http_request_send_state nxt_aligned(64) = { - .ready_handler = nxt_http_request_send_body, .error_handler = nxt_http_request_error_handler, }; -- cgit From b4c09e29548a2de0bb967a9d7f6c0601cb3e429f Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Wed, 21 Aug 2019 14:24:23 +0300 Subject: Tests: reverted rerun for Java tests in 5e429a7f133c. Each testcase should also recompile to be recompiled. So backed out for now. --- test/unit/applications/lang/java.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/unit/applications/lang/java.py b/test/unit/applications/lang/java.py index ec492d06..ec1c95d9 100644 --- a/test/unit/applications/lang/java.py +++ b/test/unit/applications/lang/java.py @@ -5,8 +5,6 @@ from unit.applications.proto import TestApplicationProto class TestApplicationJava(TestApplicationProto): - application_type = "java" - def load(self, script, name='app'): app_path = self.testdir + '/java' @@ -66,7 +64,7 @@ class TestApplicationJava(TestApplicationProto): "applications": { script: { "unit_jars": self.pardir + '/build', - "type": self.application_type, + "type": 'java', "processes": {"spare": 0}, "working_directory": script_path, "webapp": app_path, -- cgit From 08601bbbf07a462924e4c6894b5fd6e83b7725ac Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Thu, 22 Aug 2019 15:26:15 +0300 Subject: Tests: "--unsafe" option introduced. --- test/unit/main.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/unit/main.py b/test/unit/main.py index 27f8bff9..1f19343e 100644 --- a/test/unit/main.py +++ b/test/unit/main.py @@ -333,6 +333,13 @@ class TestUnit(unittest.TestCase): action='store_true', help='Save unit.log after the test execution', ) + parser.add_argument( + '-u', + '--unsafe', + dest='unsafe', + action='store_true', + help='Run unsafe tests', + ) return parser.parse_known_args() @@ -340,6 +347,7 @@ class TestUnit(unittest.TestCase): def _set_args(args): TestUnit.detailed = args.detailed TestUnit.save_log = args.save_log + TestUnit.unsafe = args.unsafe if TestUnit.detailed: fcntl.fcntl(sys.stdout.fileno(), fcntl.F_SETFL, 0) -- cgit From 9bbf54e23e185e94054072fff2673f6f5cd203e9 Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Thu, 22 Aug 2019 15:33:41 +0300 Subject: Tests: Node.js websockets. --- test/node/websockets/mirror/app.js | 31 + test/node/websockets/mirror_fragmentation/app.js | 26 + test/test_node_websockets.py | 1568 ++++++++++++++++++++++ test/unit/applications/websockets.py | 215 +++ test/unit/main.py | 1 + 5 files changed, 1841 insertions(+) create mode 100755 test/node/websockets/mirror/app.js create mode 100755 test/node/websockets/mirror_fragmentation/app.js create mode 100644 test/test_node_websockets.py create mode 100644 test/unit/applications/websockets.py diff --git a/test/node/websockets/mirror/app.js b/test/node/websockets/mirror/app.js new file mode 100755 index 00000000..23746465 --- /dev/null +++ b/test/node/websockets/mirror/app.js @@ -0,0 +1,31 @@ +#!/usr/bin/env node + +server = require('unit-http').createServer(function() {}); +webSocketServer = require('unit-http/websocket').server; +//server = require('http').createServer(function() {}); +//webSocketServer = require('websocket').server; + +server.listen(7080, function() {}); + +var wsServer = new webSocketServer({ + maxReceivedMessageSize: 0x1000000000, + maxReceivedFrameSize: 0x1000000000, + fragmentOutgoingMessages: false, + fragmentationThreshold: 0x1000000000, + httpServer: server, +}); + +wsServer.on('request', function(request) { + var connection = request.accept(null); + + connection.on('message', function(message) { + if (message.type === 'utf8') { + connection.send(message.utf8Data); + } else if (message.type === 'binary') { + connection.send(message.binaryData); + } + + }); + + connection.on('close', function(r) {}); +}); diff --git a/test/node/websockets/mirror_fragmentation/app.js b/test/node/websockets/mirror_fragmentation/app.js new file mode 100755 index 00000000..7024252a --- /dev/null +++ b/test/node/websockets/mirror_fragmentation/app.js @@ -0,0 +1,26 @@ +#!/usr/bin/env node + +server = require('unit-http').createServer(function() {}); +webSocketServer = require('unit-http/websocket').server; +//server = require('http').createServer(function() {}); +//webSocketServer = require('websocket').server; + +server.listen(7080, function() {}); + +var wsServer = new webSocketServer({ + httpServer: server +}); + +wsServer.on('request', function(request) { + //console.log('request'); + var connection = request.accept(null); + + connection.on('message', function(message) { + //console.log('message'); + connection.send(message.utf8Data); + }); + + connection.on('close', function(r) { + //console.log('close'); + }); +}); diff --git a/test/test_node_websockets.py b/test/test_node_websockets.py new file mode 100644 index 00000000..655b5202 --- /dev/null +++ b/test/test_node_websockets.py @@ -0,0 +1,1568 @@ +import time +import struct +import unittest +from unit.applications.lang.node import TestApplicationNode +from unit.applications.websockets import TestApplicationWebsocket + +class TestNodeWebsockets(TestApplicationNode): + prerequisites = ['node'] + + ws = TestApplicationWebsocket() + + @classmethod + def setUpClass(cls): + raise unittest.SkipTest('Websockets is not available') + + def close_connection(self, sock): + self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty sock') + + self.ws.frame_write(sock, self.ws.OP_CLOSE, self.ws.serialize_close()) + + self.check_close(sock) + + def check_close(self, sock, code = 1000, no_close = False): + frame = self.ws.frame_read(sock) + + self.assertEqual(frame['fin'], True, 'close fin') + self.assertEqual(frame['opcode'], self.ws.OP_CLOSE, 'close opcode') + self.assertEqual(frame['code'], code, 'close code') + + if not no_close: + sock.close() + + def check_frame(self, frame, fin, opcode, payload, decode=True): + if opcode == self.ws.OP_BINARY or not decode: + data = frame['data'] + else: + data = frame['data'].decode('utf-8') + + self.assertEqual(frame['fin'], fin, 'fin') + self.assertEqual(frame['opcode'], opcode, 'opcode') + self.assertEqual(data, payload, 'payload') + + def test_node_websockets_handshake(self): + self.load('websockets/mirror') + + resp, sock, key = self.ws.upgrade() + sock.close() + + self.assertEqual(resp['status'], 101, 'status') + self.assertEqual( + resp['headers']['Upgrade'], 'websocket', 'upgrade' + ) + self.assertEqual( + resp['headers']['Connection'], 'Upgrade', 'connection' + ) + self.assertEqual( + resp['headers']['Sec-WebSocket-Accept'], self.ws.accept(key), 'key' + ) + + def test_node_websockets_mirror(self): + self.load('websockets/mirror') + + message = 'blah' + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, message) + frame = self.ws.frame_read(sock) + + self.assertEqual( + message, frame['data'].decode('utf-8'), 'mirror' + ) + + self.ws.frame_write(sock, self.ws.OP_TEXT, message) + frame = self.ws.frame_read(sock) + + self.assertEqual( + message, frame['data'].decode('utf-8'), 'mirror 2' + ) + + sock.close() + + def test_node_websockets_no_mask(self): + self.load('websockets/mirror') + + message = 'blah' + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, message, mask=False) + + frame = self.ws.frame_read(sock) + + self.assertEqual(frame['opcode'], self.ws.OP_CLOSE, 'no mask opcode') + self.assertEqual(frame['code'], 1002, 'no mask close code') + + sock.close() + + def test_node_websockets_fragmentation(self): + self.load('websockets/mirror') + + message = 'blah' + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, message, fin=False) + self.ws.frame_write(sock, self.ws.OP_CONT, ' ', fin=False) + self.ws.frame_write(sock, self.ws.OP_CONT, message) + + frame = self.ws.frame_read(sock) + + self.assertEqual( + message + ' ' + message, + frame['data'].decode('utf-8'), + 'mirror framing', + ) + + sock.close() + + def test_node_websockets_frame_fragmentation_invalid(self): + self.load('websockets/mirror') + + message = 'blah' + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_PING, message, fin=False) + + frame = self.ws.frame_read(sock) + + frame.pop('data') + self.assertDictEqual( + frame, + { + 'fin': True, + 'rsv1': False, + 'rsv2': False, + 'rsv3': False, + 'opcode': self.ws.OP_CLOSE, + 'mask': 0, + 'code': 1002, + 'reason': 'Fragmented control frame', + }, + 'close frame', + ) + + sock.close() + + def test_node_websockets_partial_send(self): + self.load('websockets/mirror') + + message = 'blah' + + _, sock, _ = self.ws.upgrade() + + frame = self.ws.frame_to_send(self.ws.OP_TEXT, message) + sock.sendall(frame[:1]) + sock.sendall(frame[1:2]) + sock.sendall(frame[2:3]) + sock.sendall(frame[3:]) + + frame = self.ws.frame_read(sock) + + self.assertEqual( + message, + frame['data'].decode('utf-8'), + 'partial send', + ) + + sock.close() + + def test_node_websockets_large(self): + self.load('websockets/mirror_fragmentation') + + message = '0123456789' * 3000 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, message) + + frame = self.ws.frame_read(sock) + data = frame['data'].decode('utf-8') + + frame = self.ws.frame_read(sock) + data += frame['data'].decode('utf-8') + + self.assertEqual(message, data, 'large') + + sock.close() + + def test_node_websockets_frame_invalid_opcode(self): + self.load('websockets/mirror') + + message = 'blah' + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, message, fin=False) + self.ws.frame_write(sock, self.ws.OP_TEXT, message) + + frame = self.ws.frame_read(sock) + + frame.pop('data') + frame.pop('reason') + self.assertDictEqual( + frame, + { + 'fin': True, + 'rsv1': False, + 'rsv2': False, + 'rsv3': False, + 'opcode': self.ws.OP_CLOSE, + 'mask': 0, + 'code': 1002, + }, + 'close frame', + ) + + sock.close() + + def test_node_websockets_frame_invalid_opcode_2(self): + self.load('websockets/mirror') + + message = 'blah' + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_CONT, message) + + frame = self.ws.frame_read(sock) + + frame.pop('data') + self.assertDictEqual( + frame, + { + 'fin': True, + 'rsv1': False, + 'rsv2': False, + 'rsv3': False, + 'opcode': self.ws.OP_CLOSE, + 'mask': 0, + 'code': 1002, + 'reason': 'Unrecognized opcode 0', + }, + 'close frame', + ) + + sock.close() + + def test_node_websockets_two_clients(self): + self.load('websockets/mirror') + + message1 = 'blah1' + message2 = 'blah2' + + _, sock1, _ = self.ws.upgrade() + _, sock2, _ = self.ws.upgrade() + + self.ws.frame_write(sock1, self.ws.OP_TEXT, message1) + self.ws.frame_write(sock2, self.ws.OP_TEXT, message2) + + frame1 = self.ws.frame_read(sock1) + frame2 = self.ws.frame_read(sock2) + + self.assertEqual( + message1, frame1['data'].decode('utf-8'), 'client 1' + ) + self.assertEqual( + message2, frame2['data'].decode('utf-8'), 'client 2' + ) + + sock1.close() + sock2.close() + + @unittest.skip('not yet') + def test_node_websockets_handshake_upgrade_absent(self): # FAIL https://tools.ietf.org/html/rfc6455#section-4.2.1 + self.load('websockets/mirror') + + key = self.ws.key() + resp = self.get(headers={ + 'Host': 'localhost', + 'Connection': 'Upgrade', + 'Sec-WebSocket-Key': key, + 'Sec-WebSocket-Protocol': 'chat', + 'Sec-WebSocket-Version': 13, + }, read_timeout=1) + + self.assertEqual(resp['status'], 400, 'upgrade absent') + + def test_node_websockets_handshake_case_insensitive(self): + self.load('websockets/mirror') + + key = self.ws.key() + resp = self.get(headers={ + 'Host': 'localhost', + 'Upgrade': 'WEBSOCKET', + 'Connection': 'UPGRADE', + 'Sec-WebSocket-Key': key, + 'Sec-WebSocket-Protocol': 'chat', + 'Sec-WebSocket-Version': 13, + }, read_timeout=1) + + self.assertEqual(resp['status'], 101, 'status') + + @unittest.skip('not yet') + def test_node_websockets_handshake_connection_absent(self): # FAIL + self.load('websockets/mirror') + + key = self.ws.key() + resp = self.get(headers={ + 'Host': 'localhost', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Key': key, + 'Sec-WebSocket-Protocol': 'chat', + 'Sec-WebSocket-Version': 13, + }, read_timeout=1) + + self.assertEqual(resp['status'], 400, 'status') + + def test_node_websockets_handshake_version_absent(self): + self.load('websockets/mirror') + + key = self.ws.key() + resp = self.get(headers={ + 'Host': 'localhost', + 'Upgrade': 'websocket', + 'Connection': 'Upgrade', + 'Sec-WebSocket-Key': key, + 'Sec-WebSocket-Protocol': 'chat' + }, read_timeout=1) + + self.assertEqual(resp['status'], 426, 'status') + + @unittest.skip('not yet') + def test_node_websockets_handshake_key_invalid(self): + self.load('websockets/mirror') + + resp = self.get(headers={ + 'Host': 'localhost', + 'Upgrade': 'websocket', + 'Connection': 'Upgrade', + 'Sec-WebSocket-Key': '!', + 'Sec-WebSocket-Protocol': 'chat', + 'Sec-WebSocket-Version': 13 + }, read_timeout=1) + + self.assertEqual(resp['status'], 400, 'key length') + + key = self.ws.key() + resp = self.get(headers={ + 'Host': 'localhost', + 'Upgrade': 'websocket', + 'Connection': 'Upgrade', + 'Sec-WebSocket-Key': [key, key], + 'Sec-WebSocket-Protocol': 'chat', + 'Sec-WebSocket-Version': 13 + }, read_timeout=1) + + self.assertEqual(resp['status'], 400, 'key double') # FAIL https://tools.ietf.org/html/rfc6455#section-11.3.1 + + def test_node_websockets_handshake_method_invalid(self): + self.load('websockets/mirror') + + key = self.ws.key() + resp = self.post(headers={ + 'Host': 'localhost', + 'Upgrade': 'websocket', + 'Connection': 'Upgrade', + 'Sec-WebSocket-Key': key, + 'Sec-WebSocket-Protocol': 'chat', + 'Sec-WebSocket-Version': 13 + }, read_timeout=1) + + self.assertEqual(resp['status'], 400, 'status') + + def test_node_websockets_handshake_http_10(self): + self.load('websockets/mirror') + + key = self.ws.key() + resp = self.get(headers={ + 'Host': 'localhost', + 'Upgrade': 'websocket', + 'Connection': 'Upgrade', + 'Sec-WebSocket-Key': key, + 'Sec-WebSocket-Protocol': 'chat', + 'Sec-WebSocket-Version': 13 + }, http_10=True, read_timeout=1) + + self.assertEqual(resp['status'], 400, 'status') + + def test_node_websockets_handshake_uri_invalid(self): + self.load('websockets/mirror') + + key = self.ws.key() + resp = self.get(headers={ + 'Host': 'localhost', + 'Upgrade': 'websocket', + 'Connection': 'Upgrade', + 'Sec-WebSocket-Key': key, + 'Sec-WebSocket-Protocol': 'chat', + 'Sec-WebSocket-Version': 13 + }, url='!', read_timeout=1) + + self.assertEqual(resp['status'], 400, 'status') + + def test_node_websockets_protocol_absent(self): + self.load('websockets/mirror') + + key = self.ws.key() + resp = self.get(headers={ + 'Host': 'localhost', + 'Upgrade': 'websocket', + 'Connection': 'Upgrade', + 'Sec-WebSocket-Key': key, + 'Sec-WebSocket-Version': 13 + }, read_timeout=1) + + self.assertEqual(resp['status'], 101, 'status') + self.assertEqual( + resp['headers']['Upgrade'], 'websocket', 'upgrade' + ) + self.assertEqual( + resp['headers']['Connection'], 'Upgrade', 'connection' + ) + self.assertEqual( + resp['headers']['Sec-WebSocket-Accept'], self.ws.accept(key), 'key' + ) + + # autobahn-testsuite + + # Some following tests fail because of Unit does not support UTF-8 + # validation for websocket frames. It should be implemented + # by application, if necessary. + + def test_node_websockets_1_1_1__1_1_8(self): + self.load('websockets/mirror') + + opcode = self.ws.OP_TEXT + + _, sock, _ = self.ws.upgrade() + + def check_length(length, chopsize=None): + payload = '*' * length + + self.ws.frame_write(sock, opcode, payload, chopsize=chopsize) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, opcode, payload) + + check_length(0) # 1_1_1 + check_length(125) # 1_1_2 + check_length(126) # 1_1_3 + check_length(127) # 1_1_4 + check_length(128) # 1_1_5 + check_length(65535) # 1_1_6 + check_length(65536) # 1_1_7 + check_length(65536, chopsize = 997) # 1_1_8 + + self.close_connection(sock) + + def test_node_websockets_1_2_1__1_2_8(self): + self.load('websockets/mirror') + + opcode = self.ws.OP_BINARY + + _, sock, _ = self.ws.upgrade() + + def check_length(length, chopsize=None): + payload = b'\xfe' * length + + self.ws.frame_write(sock, opcode, payload, chopsize=chopsize) + frame = self.ws.frame_read(sock) + + self.check_frame(frame, True, opcode, payload) + + check_length(0) # 1_2_1 + check_length(125) # 1_2_2 + check_length(126) # 1_2_3 + check_length(127) # 1_2_4 + check_length(128) # 1_2_5 + check_length(65535) # 1_2_6 + check_length(65536) # 1_2_7 + check_length(65536, chopsize = 997) # 1_2_8 + + self.close_connection(sock) + + def test_node_websockets_2_1__2_6(self): + self.load('websockets/mirror') + + op_ping = self.ws.OP_PING + op_pong = self.ws.OP_PONG + + _, sock, _ = self.ws.upgrade() + + def check_ping(payload, chopsize=None, decode=True): + self.ws.frame_write(sock, op_ping, payload, chopsize=chopsize) + frame = self.ws.frame_read(sock) + + self.check_frame(frame, True, op_pong, payload, decode=decode) + + check_ping('') # 2_1 + check_ping('Hello, world!') # 2_2 + check_ping(b'\x00\xff\xfe\xfd\xfc\xfb\x00\xff', decode=False) # 2_3 + check_ping(b'\xfe' * 125, decode=False) # 2_4 + check_ping(b'\xfe' * 125, chopsize=1, decode=False) # 2_6 + + self.close_connection(sock) + + # 2_5 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_PING, b'\xfe' * 126) + self.check_close(sock, 1002) + + def test_node_websockets_2_7__2_9(self): + self.load('websockets/mirror') + + # 2_7 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_PONG, '') + self.assertEqual(self.recvall(sock, read_timeout=1), b'', '2_7') + + # 2_8 + + self.ws.frame_write(sock, self.ws.OP_PONG, 'unsolicited pong payload') + self.assertEqual(self.recvall(sock, read_timeout=1), b'', '2_8') + + # 2_9 + + payload = 'ping payload' + + self.ws.frame_write(sock, self.ws.OP_PONG, 'unsolicited pong payload') + self.ws.frame_write(sock, self.ws.OP_PING, payload) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_PONG, payload) + + self.close_connection(sock) + + def test_node_websockets_2_10__2_11(self): + self.load('websockets/mirror') + + # 2_10 + + _, sock, _ = self.ws.upgrade() + + for i in range(0, 10): + self.ws.frame_write(sock, self.ws.OP_PING, 'payload-%d' % i) + + for i in range(0, 10): + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_PONG, 'payload-%d' % i) + + # 2_11 + + for i in range(0, 10): + opcode = self.ws.OP_PING + self.ws.frame_write(sock, opcode, 'payload-%d' % i, chopsize=1) + + for i in range(0, 10): + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_PONG, 'payload-%d' % i) + + self.close_connection(sock) + + @unittest.skip('not yet') + def test_node_websockets_3_1__3_7(self): + self.load('websockets/mirror') + + payload = 'Hello, world!' + + # 3_1 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, payload, rsv1=True) + self.check_close(sock, 1002) + + # 3_2 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, payload) + self.ws.frame_write(sock, self.ws.OP_TEXT, payload, rsv2=True) + self.ws.frame_write(sock, self.ws.OP_PING, '') + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, payload) + + self.check_close(sock, 1002, no_close = True) + + self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty 3_2') + sock.close() + + # 3_3 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, payload) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, payload) + + self.ws.frame_write( + sock, + self.ws.OP_TEXT, + payload, + rsv1=True, + rsv2=True, + ) + + self.check_close(sock, 1002, no_close = True) + + self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty 3_3') + sock.close() + + # 3_4 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, payload, chopsize=1) + self.ws.frame_write( + sock, + self.ws.OP_TEXT, + payload, + rsv3=True, + chopsize=1 + ) + self.ws.frame_write(sock, self.ws.OP_PING, '') + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, payload) + + self.check_close(sock, 1002, no_close = True) + + self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty 3_4') + sock.close() + + # 3_5 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write( + sock, + self.ws.OP_BINARY, + b'\x00\xff\xfe\xfd\xfc\xfb\x00\xff', + rsv1=True, + rsv3=True, + ) + + self.check_close(sock, 1002) + + # 3_6 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write( + sock, + self.ws.OP_PING, + payload, + rsv2=True, + rsv3=True, + ) + + self.check_close(sock, 1002) + + # 3_7 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write( + sock, + self.ws.OP_CLOSE, + payload, + rsv1=True, + rsv2=True, + rsv3=True, + ) + + self.check_close(sock, 1002) + + def test_node_websockets_4_1_1__4_2_5(self): + self.load('websockets/mirror') + + payload = 'Hello, world!' + + # 4_1_1 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, 0x03, '') + self.check_close(sock, 1002) + + # 4_1_2 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, 0x04, 'reserved opcode payload') + self.check_close(sock, 1002) + + # 4_1_3 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, payload) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, payload) + + self.ws.frame_write(sock, 0x05, '') + self.ws.frame_write(sock, self.ws.OP_PING, '') + + self.check_close(sock, 1002) + + # 4_1_4 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, payload) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, payload) + + self.ws.frame_write(sock, 0x06, payload) + self.ws.frame_write(sock, self.ws.OP_PING, '') + + self.check_close(sock, 1002) + + # 4_1_5 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, payload, chopsize=1) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, payload) + + self.ws.frame_write(sock, 0x07, payload, chopsize=1) + self.ws.frame_write(sock, self.ws.OP_PING, '') + + self.check_close(sock, 1002) + + # 4_2_1 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, 0x0B, '') + self.check_close(sock, 1002) + + # 4_2_2 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, 0x0C, 'reserved opcode payload') + self.check_close(sock, 1002) + + # 4_2_3 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, payload) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, payload) + + self.ws.frame_write(sock, 0x0D, '') + self.ws.frame_write(sock, self.ws.OP_PING, '') + + self.check_close(sock, 1002) + + # 4_2_4 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, payload) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, payload) + + self.ws.frame_write(sock, 0x0E, payload) + self.ws.frame_write(sock, self.ws.OP_PING, '') + + self.check_close(sock, 1002) + + # 4_2_5 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, payload, chopsize=1) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, payload) + + self.ws.frame_write(sock, 0x0F, payload, chopsize=1) + self.ws.frame_write(sock, self.ws.OP_PING, '') + + self.check_close(sock, 1002) + + def test_node_websockets_5_1__5_20(self): + self.load('websockets/mirror') + + # 5_1 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_PING, 'fragment1', fin=False) + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment2', fin=True) + self.check_close(sock, 1002) + + # 5_2 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_PONG, 'fragment1', fin=False) + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment2', fin=True) + self.check_close(sock, 1002) + + # 5_3 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment1', fin=False) + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment2', fin=True) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, 'fragment1fragment2') + + # 5_4 + + self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment1', fin=False) + self.assertEqual(self.recvall(sock, read_timeout=1), b'', '5_4') + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment2', fin=True) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, 'fragment1fragment2') + + # 5_5 + + self.ws.frame_write( + sock, + self.ws.OP_TEXT, + 'fragment1', + fin=False, + chopsize=1, + ) + self.ws.frame_write( + sock, + self.ws.OP_CONT, + 'fragment2', + fin=True, + chopsize=1, + ) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, 'fragment1fragment2') + + # 5_6 + + ping_payload = 'ping payload' + + self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment1', fin=False) + self.ws.frame_write(sock, self.ws.OP_PING, ping_payload) + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment2', fin=True) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_PONG, ping_payload) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, 'fragment1fragment2') + + # 5_7 + + ping_payload = 'ping payload' + + self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment1', fin=False) + self.assertEqual(self.recvall(sock, read_timeout=1), b'', '5_7') + + self.ws.frame_write(sock, self.ws.OP_PING, ping_payload) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_PONG, ping_payload) + + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment2', fin=True) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, 'fragment1fragment2') + + # 5_8 + + ping_payload = 'ping payload' + + self.ws.frame_write( + sock, + self.ws.OP_TEXT, + 'fragment1', + fin=False, + chopsize=1, + ) + self.ws.frame_write(sock, self.ws.OP_PING, ping_payload, chopsize=1) + self.ws.frame_write( + sock, + self.ws.OP_CONT, + 'fragment2', + fin=True, + chopsize=1, + ) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_PONG, ping_payload) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, 'fragment1fragment2') + + # 5_9 + + self.ws.frame_write( + sock, + self.ws.OP_CONT, + 'non-continuation payload', + fin=True, + ) + self.ws.frame_write(sock, self.ws.OP_TEXT, 'Hello, world!', fin=True) + self.check_close(sock, 1002) + + # 5_10 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write( + sock, + self.ws.OP_CONT, + 'non-continuation payload', + fin=True, + ) + self.ws.frame_write(sock, self.ws.OP_TEXT, 'Hello, world!', fin=True) + self.check_close(sock, 1002) + + # 5_11 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write( + sock, + self.ws.OP_CONT, + 'non-continuation payload', + fin=True, + chopsize=1, + ) + self.ws.frame_write( + sock, + self.ws.OP_TEXT, + 'Hello, world!', + fin=True, + chopsize=1, + ) + self.check_close(sock, 1002) + + # 5_12 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write( + sock, + self.ws.OP_CONT, + 'non-continuation payload', + fin=False, + ) + self.ws.frame_write(sock, self.ws.OP_TEXT, 'Hello, world!', fin=True) + self.check_close(sock, 1002) + + # 5_13 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write( + sock, + self.ws.OP_CONT, + 'non-continuation payload', + fin=False, + ) + self.ws.frame_write(sock, self.ws.OP_TEXT, 'Hello, world!', fin=True) + self.check_close(sock, 1002) + + # 5_14 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write( + sock, + self.ws.OP_CONT, + 'non-continuation payload', + fin=False, + chopsize=1, + ) + self.ws.frame_write( + sock, + self.ws.OP_TEXT, + 'Hello, world!', + fin=True, + chopsize=1, + ) + self.check_close(sock, 1002) + + # 5_15 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment1', fin=False) + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment2', fin=True) + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment3', fin=False) + self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment4', fin=True) + self.check_close(sock, 1002) + + # 5_16 + + _, sock, _ = self.ws.upgrade() + + for i in range(0, 2): + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment1', fin=False) + self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment2', fin=False) + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment3', fin=True) + self.check_close(sock, 1002) + + # 5_17 + + _, sock, _ = self.ws.upgrade() + + for i in range(0, 2): + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment1', fin=True) + self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment2', fin=False) + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment3', fin=True) + self.check_close(sock, 1002) + + # 5_18 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment1', fin=False) + self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment2') + self.check_close(sock, 1002) + + # 5_19 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment1', fin=False) + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment2', fin=False) + self.ws.frame_write(sock, self.ws.OP_PING, 'pongme 1!') + + time.sleep(1) + + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment3', fin=False) + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment4', fin=False) + self.ws.frame_write(sock, self.ws.OP_PING, 'pongme 2!') + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment5') + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_PONG, 'pongme 1!') + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_PONG, 'pongme 2!') + + self.check_frame( + self.ws.frame_read(sock), + True, + self.ws.OP_TEXT, + 'fragment1fragment2fragment3fragment4fragment5', + ) + + # 5_20 + + self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment1', fin=False) + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment2', fin=False) + self.ws.frame_write(sock, self.ws.OP_PING, 'pongme 1!') + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_PONG, 'pongme 1!') + + time.sleep(1) + + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment3', fin=False) + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment4', fin=False) + self.ws.frame_write(sock, self.ws.OP_PING, 'pongme 2!') + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_PONG, 'pongme 2!') + + self.assertEqual(self.recvall(sock, read_timeout=1), b'', '5_20') + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment5') + + self.check_frame( + self.ws.frame_read(sock), + True, + self.ws.OP_TEXT, + 'fragment1fragment2fragment3fragment4fragment5', + ) + + self.close_connection(sock) + + def test_node_websockets_6_1_1__6_4_4(self): + self.load('websockets/mirror') + + # 6_1_1 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, '') + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, '') + + # 6_1_2 + + self.ws.frame_write(sock, self.ws.OP_TEXT, '', fin=False) + self.ws.frame_write(sock, self.ws.OP_CONT, '', fin=False) + self.ws.frame_write(sock, self.ws.OP_CONT, '') + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, '') + + # 6_1_3 + + payload = 'middle frame payload' + + self.ws.frame_write(sock, self.ws.OP_TEXT, '', fin=False) + self.ws.frame_write(sock, self.ws.OP_CONT, payload, fin=False) + self.ws.frame_write(sock, self.ws.OP_CONT, '') + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, payload) + + # 6_2_1 + + payload = 'Hello-µ@ßöäüàá-UTF-8!!' + + self.ws.frame_write(sock, self.ws.OP_TEXT, payload) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, payload) + + # 6_2_2 + + self.ws.frame_write(sock, self.ws.OP_TEXT, payload[:12], fin=False) + self.ws.frame_write(sock, self.ws.OP_CONT, payload[12:]) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, payload) + + # 6_2_3 + + self.ws.message(sock, self.ws.OP_TEXT, payload, fragmention_size=1) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, payload) + + # 6_2_4 + + payload = '\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5' + + self.ws.message(sock, self.ws.OP_TEXT, payload, fragmention_size=1) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, payload) + + self.close_connection(sock) + + # Unit does not support UTF-8 validation + +# # 6_3_1 FAIL +# +# payload_1 = '\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5' +# payload_2 = '\xed\xa0\x80' +# payload_3 = '\x65\x64\x69\x74\x65\x64' +# +# payload = payload_1 + payload_2 + payload_3 +# +# self.ws.message(sock, self.ws.OP_TEXT, payload) +# self.check_close(sock, 1007) +# +# # 6_3_2 FAIL +# +# _, sock, _ = self.ws.upgrade() +# +# self.ws.message(sock, self.ws.OP_TEXT, payload, fragmention_size=1) +# self.check_close(sock, 1007) +# +# # 6_4_1 ... 6_4_4 FAIL + + def test_node_websockets_7_1_1__7_5_1(self): + self.load('websockets/mirror') + + # 7_1_1 + + _, sock, _ = self.ws.upgrade() + + payload = "Hello World!" + + self.ws.frame_write(sock, self.ws.OP_TEXT, payload) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, payload) + + self.close_connection(sock) + + # 7_1_2 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_CLOSE, self.ws.serialize_close()) + self.ws.frame_write(sock, self.ws.OP_CLOSE, self.ws.serialize_close()) + + self.check_close(sock) + + # 7_1_3 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_CLOSE, self.ws.serialize_close()) + self.check_close(sock, no_close = True) + + self.ws.frame_write(sock, self.ws.OP_PING, '') + self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty sock') + + sock.close() + + # 7_1_4 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_CLOSE, self.ws.serialize_close()) + self.check_close(sock, no_close = True) + + self.ws.frame_write(sock, self.ws.OP_TEXT, payload) + self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty sock') + + sock.close() + + # 7_1_5 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment1', fin=False) + self.ws.frame_write(sock, self.ws.OP_CLOSE, self.ws.serialize_close()) + self.check_close(sock, no_close = True) + + self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment2') + self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty sock') + + sock.close() + + # 7_1_6 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, 'BAsd7&jh23' * 26 * 2**10) + self.ws.frame_write(sock, self.ws.OP_TEXT, payload) + self.ws.frame_write(sock, self.ws.OP_CLOSE, self.ws.serialize_close()) + + self.recvall(sock, read_timeout=1) + + self.ws.frame_write(sock, self.ws.OP_PING, '') + self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty sock') + + sock.close() + + # 7_3_1 # FAIL + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_CLOSE, '') + self.check_close(sock) + + # 7_3_2 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_CLOSE, 'a') + self.check_close(sock, 1002) + + # 7_3_3 + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_CLOSE, self.ws.serialize_close()) + self.check_close(sock) + + # 7_3_4 + + _, sock, _ = self.ws.upgrade() + + payload = self.ws.serialize_close(reason = 'Hello World!') + + self.ws.frame_write(sock, self.ws.OP_CLOSE, payload) + self.check_close(sock) + + # 7_3_5 + + _, sock, _ = self.ws.upgrade() + + payload = self.ws.serialize_close(reason = '*' * 123) + + self.ws.frame_write(sock, self.ws.OP_CLOSE, payload) + self.check_close(sock) + + # 7_3_6 + + _, sock, _ = self.ws.upgrade() + + payload = self.ws.serialize_close(reason = '*' * 124) + + self.ws.frame_write(sock, self.ws.OP_CLOSE, payload) + self.check_close(sock, 1002) + + # 7_5_1 FAIL Unit does not support UTF-8 validation + +# _, sock, _ = self.ws.upgrade() +# +# payload = self.ws.serialize_close(reason = '\xce\xba\xe1\xbd\xb9\xcf' \ +# '\x83\xce\xbc\xce\xb5\xed\xa0\x80\x65\x64\x69\x74\x65\x64') +# +# self.ws.frame_write(sock, self.ws.OP_CLOSE, payload) +# self.check_close(sock, 1007) + + def test_node_websockets_7_7_X__7_9_X(self): + self.load('websockets/mirror') + + valid_codes = [ + 1000, + 1001, + 1002, + 1003, + 1007, + 1008, + 1009, + 1010, + 1011, + 3000, + 3999, + 4000, + 4999, + ] + + invalid_codes = [0, 999, 1004, 1005, 1006, 1016, 1100, 2000, 2999] + + for code in valid_codes: + _, sock, _ = self.ws.upgrade() + + payload = self.ws.serialize_close(code = code) + + self.ws.frame_write(sock, self.ws.OP_CLOSE, payload) + self.check_close(sock) + + for code in invalid_codes: + _, sock, _ = self.ws.upgrade() + + payload = self.ws.serialize_close(code = code) + + self.ws.frame_write(sock, self.ws.OP_CLOSE, payload) + self.check_close(sock, 1002) + + def test_node_websockets_7_13_1__7_13_2(self): + self.load('websockets/mirror') + + # 7_13_1 + + _, sock, _ = self.ws.upgrade() + + payload = self.ws.serialize_close(code = 5000) + + self.ws.frame_write(sock, self.ws.OP_CLOSE, payload) + self.check_close(sock, 1002) + + # 7_13_2 + + _, sock, _ = self.ws.upgrade() + + payload = struct.pack('!I', 65536) + ''.encode('utf-8') + + self.ws.frame_write(sock, self.ws.OP_CLOSE, payload) + self.check_close(sock, 1002) + + def test_node_websockets_9_1_1__9_6_6(self): + if not self.unsafe: + self.skipTest("unsafe, long run") + + self.load('websockets/mirror') + + self.assertIn( + 'success', + self.conf( + { + 'http': { + 'websocket': { + 'max_frame_size': 33554432, + 'keepalive_interval': 0, + } + } + }, + 'settings', + ), + 'increase max_frame_size and keepalive_interval', + ) + + _, sock, _ = self.ws.upgrade() + + op_text = self.ws.OP_TEXT + op_binary = self.ws.OP_BINARY + + def check_payload(opcode, length, chopsize=None): + if opcode == self.ws.OP_TEXT: + payload = '*' * length + else: + payload = b'*' * length + + self.ws.frame_write(sock, opcode, payload, chopsize=chopsize) + frame = self.ws.frame_read(sock, read_timeout=5) + self.check_frame(frame, True, opcode, payload) + + def check_message(opcode, f_size): + if opcode == self.ws.OP_TEXT: + payload = '*' * 4 * 2**20 + else: + payload = b'*' * 4 * 2**20 + + self.ws.message(sock, opcode, payload, fragmention_size=f_size) + frame = self.ws.frame_read(sock, read_timeout=5) + self.check_frame(frame, True, opcode, payload) + + check_payload(op_text, 64 * 2**10) # 9_1_1 + check_payload(op_text, 256 * 2**10) # 9_1_2 + check_payload(op_text, 2**20) # 9_1_3 + check_payload(op_text, 4 * 2**20) # 9_1_4 + check_payload(op_text, 8 * 2**20) # 9_1_5 + check_payload(op_text, 16 * 2**20) # 9_1_6 + + check_payload(op_binary, 64 * 2**10) # 9_2_1 + check_payload(op_binary, 256 * 2**10) # 9_2_2 + check_payload(op_binary, 2**20) # 9_2_3 + check_payload(op_binary, 4 * 2**20) # 9_2_4 + check_payload(op_binary, 8 * 2**20) # 9_2_5 + check_payload(op_binary, 16 * 2**20) # 9_2_6 + + if self.system != 'Darwin' and self.system != 'FreeBSD': + check_message(op_text, 64) # 9_3_1 + check_message(op_text, 256) # 9_3_2 + check_message(op_text, 2**10) # 9_3_3 + check_message(op_text, 4 * 2**10) # 9_3_4 + check_message(op_text, 16 * 2**10) # 9_3_5 + check_message(op_text, 64 * 2**10) # 9_3_6 + check_message(op_text, 256 * 2**10) # 9_3_7 + check_message(op_text, 2**20) # 9_3_8 + check_message(op_text, 4 * 2**20) # 9_3_9 + + check_message(op_binary, 64) # 9_4_1 + check_message(op_binary, 256) # 9_4_2 + check_message(op_binary, 2**10) # 9_4_3 + check_message(op_binary, 4 * 2**10) # 9_4_4 + check_message(op_binary, 16 * 2**10) # 9_4_5 + check_message(op_binary, 64 * 2**10) # 9_4_6 + check_message(op_binary, 256 * 2**10) # 9_4_7 + check_message(op_binary, 2**20) # 9_4_8 + check_message(op_binary, 4 * 2**20) # 9_4_9 + + check_payload(op_text, 2**20, chopsize=64) # 9_5_1 + check_payload(op_text, 2**20, chopsize=128) # 9_5_2 + check_payload(op_text, 2**20, chopsize=256) # 9_5_3 + check_payload(op_text, 2**20, chopsize=512) # 9_5_4 + check_payload(op_text, 2**20, chopsize=1024) # 9_5_5 + check_payload(op_text, 2**20, chopsize=2048) # 9_5_6 + + check_payload(op_binary, 2**20, chopsize=64) # 9_6_1 + check_payload(op_binary, 2**20, chopsize=128) # 9_6_2 + check_payload(op_binary, 2**20, chopsize=256) # 9_6_3 + check_payload(op_binary, 2**20, chopsize=512) # 9_6_4 + check_payload(op_binary, 2**20, chopsize=1024) # 9_6_5 + check_payload(op_binary, 2**20, chopsize=2048) # 9_6_6 + + self.close_connection(sock) + + def test_node_websockets_10_1_1(self): + self.load('websockets/mirror') + + _, sock, _ = self.ws.upgrade() + + payload = '*' * 65536 + + self.ws.message(sock, self.ws.OP_TEXT, payload, fragmention_size=1300) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_TEXT, payload) + + self.close_connection(sock) + + # settings + + def test_node_websockets_max_frame_size(self): + self.load('websockets/mirror') + + self.assertIn( + 'success', + self.conf( + {'http': {'websocket': {'max_frame_size': 100}}}, 'settings' + ), + 'configure max_frame_size', + ) + + _, sock, _ = self.ws.upgrade() + + payload = '*' * 94 + opcode = self.ws.OP_TEXT + + self.ws.frame_write(sock, opcode, payload) # frame length is 100 + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, opcode, payload) + + payload = '*' * 95 + + self.ws.frame_write(sock, opcode, payload) # frame length is 101 + self.check_close(sock, 1009) # 1009 - CLOSE_TOO_LARGE + + def test_node_websockets_read_timeout(self): + self.load('websockets/mirror') + + self.assertIn( + 'success', + self.conf( + {'http': {'websocket': {'read_timeout': 1}}}, 'settings' + ), + 'configure read_timeout', + ) + + _, sock, _ = self.ws.upgrade() + + frame = self.ws.frame_to_send(self.ws.OP_TEXT, 'blah') + sock.sendall(frame[:2]) + + time.sleep(2) + + self.check_close(sock, 1001) # 1001 - CLOSE_GOING_AWAY + + def test_node_websockets_keepalive_interval(self): + self.load('websockets/mirror') + + self.assertIn( + 'success', + self.conf( + {'http': {'websocket': {'keepalive_interval': 1}}}, 'settings' + ), + 'configure keepalive_interval', + ) + + _, sock, _ = self.ws.upgrade() + + frame = self.ws.frame_to_send(self.ws.OP_TEXT, 'blah') + sock.sendall(frame[:2]) + + time.sleep(2) + + frame = self.ws.frame_read(sock) + self.check_frame(frame, True, self.ws.OP_PING, '') # PING frame + + sock.close() + +if __name__ == '__main__': + TestNodeWebsockets.main() diff --git a/test/unit/applications/websockets.py b/test/unit/applications/websockets.py new file mode 100644 index 00000000..7b516239 --- /dev/null +++ b/test/unit/applications/websockets.py @@ -0,0 +1,215 @@ +import random +import base64 +import struct +import select +import hashlib +import itertools +from unit.applications.proto import TestApplicationProto + +GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" + + +class TestApplicationWebsocket(TestApplicationProto): + + OP_CONT = 0x00 + OP_TEXT = 0x01 + OP_BINARY = 0x02 + OP_CLOSE = 0x08 + OP_PING = 0x09 + OP_PONG = 0x0A + CLOSE_CODES = [1000, 1001, 1002, 1003, 1007, 1008, 1009, 1010, 1011] + + def __init__(self, preinit=False): + self.preinit = preinit + + def key(self): + raw_key = bytes(random.getrandbits(8) for _ in range(16)) + return base64.b64encode(raw_key).decode() + + def accept(self, key): + sha1 = hashlib.sha1((key + GUID).encode()).digest() + return base64.b64encode(sha1).decode() + + def upgrade(self): + key = self.key() + + if self.preinit: + self.get() + + resp, sock = self.get( + headers={ + 'Host': 'localhost', + 'Upgrade': 'websocket', + 'Connection': 'Upgrade', + 'Sec-WebSocket-Key': key, + 'Sec-WebSocket-Protocol': 'chat', + 'Sec-WebSocket-Version': 13, + }, + read_timeout=1, + start=True, + ) + + return (resp, sock, key) + + def apply_mask(self, data, mask): + return bytes(b ^ m for b, m in zip(data, itertools.cycle(mask))) + + def serialize_close(self, code = 1000, reason = ''): + return struct.pack('!H', code) + reason.encode('utf-8') + + def frame_read(self, sock, read_timeout=1): + def recv_bytes(sock, bytes): + data = b'' + while select.select([sock], [], [], read_timeout)[0]: + try: + if bytes < 65536: + data = sock.recv(bytes) + else: + data = self.recvall( + sock, + read_timeout=read_timeout, + buff_size=bytes, + ) + break + except: + break + + return data + + frame = {} + + head1, = struct.unpack('!B', recv_bytes(sock, 1)) + head2, = struct.unpack('!B', recv_bytes(sock, 1)) + + frame['fin'] = bool(head1 & 0b10000000) + frame['rsv1'] = bool(head1 & 0b01000000) + frame['rsv2'] = bool(head1 & 0b00100000) + frame['rsv3'] = bool(head1 & 0b00010000) + frame['opcode'] = head1 & 0b00001111 + frame['mask'] = head2 & 0b10000000 + + length = head2 & 0b01111111 + if length == 126: + data = recv_bytes(sock, 2) + length, = struct.unpack('!H', data) + elif length == 127: + data = recv_bytes(sock, 8) + length, = struct.unpack('!Q', data) + + if frame['mask']: + mask_bits = recv_bytes(sock, 4) + + data = recv_bytes(sock, length) + if frame['mask']: + data = self.apply_mask(data, mask_bits) + + if frame['opcode'] == self.OP_CLOSE: + if length >= 2: + code, = struct.unpack('!H', data[:2]) + reason = data[2:].decode('utf-8') + if not (code in self.CLOSE_CODES or 3000 <= code < 5000): + self.fail('Invalid status code') + frame['code'] = code + frame['reason'] = reason + elif length == 0: + frame['code'] = 1005 + frame['reason'] = '' + else: + self.fail('Close frame too short') + + frame['data'] = data + + if frame['mask']: + self.fail('Received frame with mask') + + return frame + + def frame_to_send( + self, + opcode, + data, + fin=True, + length=None, + rsv1=False, + rsv2=False, + rsv3=False, + mask=True, + ): + frame = b'' + + if isinstance(data, str): + data = data.encode('utf-8') + + head1 = ( + (0b10000000 if fin else 0) + | (0b01000000 if rsv1 else 0) + | (0b00100000 if rsv2 else 0) + | (0b00010000 if rsv3 else 0) + | opcode + ) + + head2 = 0b10000000 if mask else 0 + + data_length = len(data) if length is None else length + if data_length < 126: + frame += struct.pack('!BB', head1, head2 | data_length) + elif data_length < 65536: + frame += struct.pack('!BBH', head1, head2 | 126, data_length) + else: + frame += struct.pack('!BBQ', head1, head2 | 127, data_length) + + if mask: + mask_bits = struct.pack('!I', random.getrandbits(32)) + frame += mask_bits + + if mask: + frame += self.apply_mask(data, mask_bits) + else: + frame += data + + return frame + + def frame_write(self, sock, *args, **kwargs): + chopsize = kwargs.pop('chopsize') if 'chopsize' in kwargs else None + + frame = self.frame_to_send(*args, **kwargs) + + if chopsize is None: + sock.sendall(frame) + + else: + pos = 0 + frame_len = len(frame) + while (pos < frame_len): + end = min(pos + chopsize, frame_len) + sock.sendall(frame[pos:end]) + pos = end + + def message(self, sock, type, message, fragmention_size=None, **kwargs): + message_len = len(message) + + if fragmention_size is None: + fragmention_size = message_len + + if message_len <= fragmention_size: + self.frame_write(sock, type, message, **kwargs) + return + + pos = 0 + op_code = type + while(pos < message_len): + end = min(pos + fragmention_size, message_len) + fin = (end == message_len) + self.frame_write(sock, op_code, message[pos:end], fin=fin, **kwargs) + op_code = self.OP_CONT + pos = end + + def message_read(self, sock, read_timeout=1): + frame = self.frame_read(sock, read_timeout=read_timeout) + + while(not frame['fin']): + temp = self.frame_read(sock, read_timeout=read_timeout) + frame['data'] += temp['data'] + frame['fin'] = temp['fin'] + + return frame diff --git a/test/unit/main.py b/test/unit/main.py index 1f19343e..6a167a9e 100644 --- a/test/unit/main.py +++ b/test/unit/main.py @@ -23,6 +23,7 @@ class TestUnit(unittest.TestCase): os.path.join(os.path.dirname(__file__), os.pardir, os.pardir) ) architecture = platform.architecture()[0] + system = platform.system() maxDiff = None detailed = False -- cgit From e501c74ddceab86e48c031ca9b5e154f52dcdae0 Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Tue, 20 Aug 2019 16:31:53 +0300 Subject: Introducing websocket support in router and libunit. --- auto/make | 24 +- auto/modules/go | 1 + auto/sources | 5 + src/nxt_conf_validation.c | 25 + src/nxt_h1proto.c | 370 +++++++++--- src/nxt_h1proto.h | 48 ++ src/nxt_h1proto_websocket.c | 719 ++++++++++++++++++++++ src/nxt_http.h | 28 + src/nxt_http_request.c | 21 +- src/nxt_http_response.c | 2 + src/nxt_http_websocket.c | 161 +++++ src/nxt_port.c | 1 + src/nxt_port.h | 15 + src/nxt_router.c | 181 +++--- src/nxt_router.h | 9 + src/nxt_router_request.h | 71 +++ src/nxt_sha1.c | 295 +++++++++ src/nxt_sha1.h | 24 + src/nxt_unit.c | 1169 +++++++++++++++++++++++++++--------- src/nxt_unit.h | 32 +- src/nxt_unit_request.h | 1 + src/nxt_unit_typedefs.h | 27 +- src/nxt_unit_websocket.h | 27 + src/nxt_websocket.c | 122 ++++ src/nxt_websocket.h | 21 + src/nxt_websocket_accept.c | 68 +++ src/nxt_websocket_header.h | 68 +++ src/test/nxt_unit_websocket_chat.c | 348 +++++++++++ src/test/nxt_unit_websocket_echo.c | 105 ++++ 29 files changed, 3541 insertions(+), 447 deletions(-) create mode 100644 src/nxt_h1proto.h create mode 100644 src/nxt_h1proto_websocket.c create mode 100644 src/nxt_http_websocket.c create mode 100644 src/nxt_router_request.h create mode 100644 src/nxt_sha1.c create mode 100644 src/nxt_sha1.h create mode 100644 src/nxt_unit_websocket.h create mode 100644 src/nxt_websocket.c create mode 100644 src/nxt_websocket.h create mode 100644 src/nxt_websocket_accept.c create mode 100644 src/nxt_websocket_header.h create mode 100644 src/test/nxt_unit_websocket_chat.c create mode 100644 src/test/nxt_unit_websocket_echo.c diff --git a/auto/make b/auto/make index 1eee2a78..44d0bdfc 100644 --- a/auto/make +++ b/auto/make @@ -57,6 +57,7 @@ $echo >> $NXT_MAKEFILE $echo "NXT_LIB_UNIT_OBJS = \\" >> $NXT_MAKEFILE $echo " $NXT_BUILD_DIR/src/nxt_lvlhsh.o \\" >> $NXT_MAKEFILE $echo " $NXT_BUILD_DIR/src/nxt_murmur_hash.o \\" >> $NXT_MAKEFILE +$echo " $NXT_BUILD_DIR/src/nxt_websocket.o \\" >> $NXT_MAKEFILE for nxt_src in $NXT_LIB_UNIT_SRCS do @@ -108,7 +109,9 @@ END # Object files. for nxt_src in $NXT_LIB_SRCS $NXT_TEST_SRCS $NXT_LIB_UNIT_SRCS \ - src/test/nxt_unit_app_test.c + src/test/nxt_unit_app_test.c \ + src/test/nxt_unit_websocket_chat.c \ + src/test/nxt_unit_websocket_echo.c do nxt_obj=${nxt_src%.c}.o nxt_dep=${nxt_src%.c}.dep @@ -150,7 +153,8 @@ if [ $NXT_TESTS = YES ]; then .PHONY: tests tests: $NXT_BUILD_DIR/tests $NXT_BUILD_DIR/utf8_file_name_test \\ - $NXT_BUILD_DIR/unit_app_test + $NXT_BUILD_DIR/unit_app_test $NXT_BUILD_DIR/unit_websocket_chat \\ + $NXT_BUILD_DIR/unit_websocket_echo $NXT_BUILD_DIR/tests: \$(NXT_TEST_OBJS) \\ $NXT_BUILD_DIR/$NXT_LIB_STATIC @@ -174,6 +178,22 @@ $NXT_BUILD_DIR/unit_app_test: $NXT_BUILD_DIR/src/test/nxt_unit_app_test.o \\ $NXT_BUILD_DIR/$NXT_LIB_UNIT_STATIC \\ $NXT_LD_OPT $NXT_LIBM $NXT_LIBS $NXT_LIB_AUX_LIBS +$NXT_BUILD_DIR/unit_websocket_chat: \\ + $NXT_BUILD_DIR/src/test/nxt_unit_websocket_chat.o \\ + $NXT_BUILD_DIR/$NXT_LIB_UNIT_STATIC + \$(NXT_EXEC_LINK) -o $NXT_BUILD_DIR/unit_websocket_chat \\ + \$(CFLAGS) $NXT_BUILD_DIR/src/test/nxt_unit_websocket_chat.o \\ + $NXT_BUILD_DIR/$NXT_LIB_UNIT_STATIC \\ + $NXT_LD_OPT $NXT_LIBM $NXT_LIBS $NXT_LIB_AUX_LIBS + +$NXT_BUILD_DIR/unit_websocket_echo: \\ + $NXT_BUILD_DIR/src/test/nxt_unit_websocket_echo.o \\ + $NXT_BUILD_DIR/$NXT_LIB_UNIT_STATIC + \$(NXT_EXEC_LINK) -o $NXT_BUILD_DIR/unit_websocket_echo \\ + \$(CFLAGS) $NXT_BUILD_DIR/src/test/nxt_unit_websocket_echo.o \\ + $NXT_BUILD_DIR/$NXT_LIB_UNIT_STATIC \\ + $NXT_LD_OPT $NXT_LIBM $NXT_LIBS $NXT_LIB_AUX_LIBS + END else diff --git a/auto/modules/go b/auto/modules/go index 62c3743f..51b5979d 100644 --- a/auto/modules/go +++ b/auto/modules/go @@ -107,6 +107,7 @@ ${NXT_GO}-install-src: ${NXT_VERSION_H} install -d \$(DESTDIR)\$(NXT_GO_DST)/src/nginx/unit install -p -m644 ./src/*.h ./build/*.h ./src/go/unit/* \ ./src/nxt_unit.c ./src/nxt_lvlhsh.c ./src/nxt_murmur_hash.c \ + ./src/nxt_websocket.c \ \$(DESTDIR)\$(NXT_GO_DST)/src/nginx/unit/ ${NXT_GO}-install-build: ${NXT_GO}-install-src diff --git a/auto/sources b/auto/sources index 4c4fd742..8ac8fb19 100644 --- a/auto/sources +++ b/auto/sources @@ -86,6 +86,11 @@ NXT_LIB_SRCS=" \ src/nxt_application.c \ src/nxt_external.c \ src/nxt_port_hash.c \ + src/nxt_sha1.c \ + src/nxt_websocket.c \ + src/nxt_websocket_accept.c \ + src/nxt_http_websocket.c \ + src/nxt_h1proto_websocket.c \ " NXT_LIB_SRC0=" \ diff --git a/src/nxt_conf_validation.c b/src/nxt_conf_validation.c index 45c0eb41..ca8ec62e 100644 --- a/src/nxt_conf_validation.c +++ b/src/nxt_conf_validation.c @@ -102,6 +102,26 @@ static nxt_int_t nxt_conf_vldt_java_option(nxt_conf_validation_t *vldt, nxt_conf_value_t *value); +static nxt_conf_vldt_object_t nxt_conf_vldt_websocket_members[] = { + { nxt_string("read_timeout"), + NXT_CONF_VLDT_INTEGER, + NULL, + NULL }, + + { nxt_string("keepalive_interval"), + NXT_CONF_VLDT_INTEGER, + NULL, + NULL }, + + { nxt_string("max_frame_size"), + NXT_CONF_VLDT_INTEGER, + NULL, + NULL }, + + NXT_CONF_VLDT_END +}; + + static nxt_conf_vldt_object_t nxt_conf_vldt_http_members[] = { { nxt_string("header_read_timeout"), NXT_CONF_VLDT_INTEGER, @@ -128,6 +148,11 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_http_members[] = { NULL, NULL }, + { nxt_string("websocket"), + NXT_CONF_VLDT_OBJECT, + &nxt_conf_vldt_object, + (void *) &nxt_conf_vldt_websocket_members }, + NXT_CONF_VLDT_END }; diff --git a/src/nxt_h1proto.c b/src/nxt_h1proto.c index a92dfe3b..a60bdb36 100644 --- a/src/nxt_h1proto.c +++ b/src/nxt_h1proto.c @@ -6,26 +6,9 @@ #include #include - - -struct nxt_h1proto_s { - nxt_http_request_parse_t parser; - - uint8_t nbuffers; - uint8_t keepalive; /* 1 bit */ - uint8_t chunked; /* 1 bit */ - nxt_http_te_t transfer_encoding:8; /* 2 bits */ - - uint32_t header_size; - - nxt_http_request_t *request; - nxt_buf_t *buffers; - /* - * All fields before the conn field will - * be zeroed in a keep-alive connection. - */ - nxt_conn_t *conn; -}; +#include +#include +#include /* @@ -43,12 +26,18 @@ static void nxt_h1p_conn_proto_init(nxt_task_t *task, void *obj, void *data); static void nxt_h1p_conn_request_init(nxt_task_t *task, void *obj, void *data); static void nxt_h1p_conn_request_header_parse(nxt_task_t *task, void *obj, void *data); -static nxt_int_t nxt_h1p_header_process(nxt_h1proto_t *h1p, +static nxt_int_t nxt_h1p_header_process(nxt_task_t *task, nxt_h1proto_t *h1p, nxt_http_request_t *r); static nxt_int_t nxt_h1p_header_buffer_test(nxt_task_t *task, nxt_h1proto_t *h1p, nxt_conn_t *c, nxt_socket_conf_t *skcf); static nxt_int_t nxt_h1p_connection(void *ctx, nxt_http_field_t *field, uintptr_t data); +static nxt_int_t nxt_h1p_upgrade(void *ctx, nxt_http_field_t *field, + uintptr_t data); +static nxt_int_t nxt_h1p_websocket_key(void *ctx, nxt_http_field_t *field, + uintptr_t data); +static nxt_int_t nxt_h1p_websocket_version(void *ctx, nxt_http_field_t *field, + uintptr_t data); static nxt_int_t nxt_h1p_transfer_encoding(void *ctx, nxt_http_field_t *field, uintptr_t data); static void nxt_h1p_request_body_read(nxt_task_t *task, nxt_http_request_t *r); @@ -70,8 +59,6 @@ static void nxt_h1p_conn_request_timeout(nxt_task_t *task, void *obj, void *data); static void nxt_h1p_conn_request_send_timeout(nxt_task_t *task, void *obj, void *data); -static nxt_msec_t nxt_h1p_conn_request_timer_value(nxt_conn_t *c, - uintptr_t data); nxt_inline void nxt_h1p_request_error(nxt_task_t *task, nxt_h1proto_t *h1p, nxt_http_request_t *r); static void nxt_h1p_request_close(nxt_task_t *task, nxt_http_proto_t proto, @@ -91,16 +78,16 @@ static void nxt_h1p_idle_response_timeout(nxt_task_t *task, void *obj, static nxt_msec_t nxt_h1p_idle_response_timer_value(nxt_conn_t *c, uintptr_t data); static void nxt_h1p_shutdown(nxt_task_t *task, nxt_conn_t *c); +static void nxt_h1p_shutdown_(nxt_task_t *task, nxt_conn_t *c); +static void nxt_h1p_conn_ws_shutdown(nxt_task_t *task, void *obj, void *data); static void nxt_h1p_conn_closing(nxt_task_t *task, void *obj, void *data); static void nxt_h1p_conn_free(nxt_task_t *task, void *obj, void *data); - #if (NXT_TLS) static const nxt_conn_state_t nxt_http_idle_state; static const nxt_conn_state_t nxt_h1p_shutdown_state; #endif static const nxt_conn_state_t nxt_h1p_idle_state; -static const nxt_conn_state_t nxt_h1p_idle_close_state; static const nxt_conn_state_t nxt_h1p_header_parse_state; static const nxt_conn_state_t nxt_h1p_read_body_state; static const nxt_conn_state_t nxt_h1p_request_send_state; @@ -119,6 +106,7 @@ const nxt_http_proto_table_t nxt_http_proto[3] = { .body_bytes_sent = nxt_h1p_request_body_bytes_sent, .discard = nxt_h1p_request_discard, .close = nxt_h1p_request_close, + .ws_frame_start = nxt_h1p_websocket_frame_start, }, /* NXT_HTTP_PROTO_H2 */ /* NXT_HTTP_PROTO_DEVNULL */ @@ -129,6 +117,10 @@ static nxt_lvlhsh_t nxt_h1p_fields_hash; static nxt_http_field_proc_t nxt_h1p_fields[] = { { nxt_string("Connection"), &nxt_h1p_connection, 0 }, + { nxt_string("Upgrade"), &nxt_h1p_upgrade, 0 }, + { nxt_string("Sec-WebSocket-Key"), &nxt_h1p_websocket_key, 0 }, + { nxt_string("Sec-WebSocket-Version"), + &nxt_h1p_websocket_version, 0 }, { nxt_string("Transfer-Encoding"), &nxt_h1p_transfer_encoding, 0 }, { nxt_string("Host"), &nxt_http_request_host, 0 }, @@ -503,7 +495,7 @@ nxt_h1p_conn_request_header_parse(nxt_task_t *task, void *obj, void *data) */ h1p->keepalive = (h1p->parser.version.s.minor != '0'); - ret = nxt_h1p_header_process(h1p, r); + ret = nxt_h1p_header_process(task, h1p, r); if (nxt_fast_path(ret == NXT_OK)) { @@ -551,7 +543,7 @@ nxt_h1p_conn_request_header_parse(nxt_task_t *task, void *obj, void *data) break; } - (void) nxt_h1p_header_process(h1p, r); + (void) nxt_h1p_header_process(task, h1p, r); error: @@ -562,8 +554,12 @@ error: static nxt_int_t -nxt_h1p_header_process(nxt_h1proto_t *h1p, nxt_http_request_t *r) +nxt_h1p_header_process(nxt_task_t *task, nxt_h1proto_t *h1p, + nxt_http_request_t *r) { + u_char *m; + nxt_int_t ret; + r->target.start = h1p->parser.target_start; r->target.length = h1p->parser.target_end - h1p->parser.target_start; @@ -578,7 +574,46 @@ nxt_h1p_header_process(nxt_h1proto_t *h1p, nxt_http_request_t *r) r->fields = h1p->parser.fields; - return nxt_http_fields_process(r->fields, &nxt_h1p_fields_hash, r); + ret = nxt_http_fields_process(r->fields, &nxt_h1p_fields_hash, r); + if (nxt_slow_path(ret != NXT_OK)) { + return ret; + } + + if (h1p->connection_upgrade && h1p->upgrade_websocket) { + m = h1p->parser.method.start; + + if (nxt_slow_path(h1p->parser.method.length != 3 + || m[0] != 'G' + || m[1] != 'E' + || m[2] != 'T')) + { + nxt_log(task, NXT_LOG_INFO, "h1p upgrade: bad method"); + + return NXT_HTTP_BAD_REQUEST; + } + + if (nxt_slow_path(h1p->parser.version.s.minor != '1')) { + nxt_log(task, NXT_LOG_INFO, "h1p upgrade: bad protocol version"); + + return NXT_HTTP_BAD_REQUEST; + } + + if (nxt_slow_path(h1p->websocket_key == NULL)) { + nxt_log(task, NXT_LOG_INFO, "h1p upgrade: bad or absent websocket key"); + + return NXT_HTTP_BAD_REQUEST; + } + + if (nxt_slow_path(h1p->websocket_version_ok == 0)) { + nxt_log(task, NXT_LOG_INFO, "h1p upgrade: bad or absent websocket version"); + + return NXT_HTTP_UPGRADE_REQUIRED; + } + + r->websocket_handshake = 1; + } + + return ret; } @@ -620,12 +655,68 @@ nxt_h1p_header_buffer_test(nxt_task_t *task, nxt_h1proto_t *h1p, nxt_conn_t *c, static nxt_int_t nxt_h1p_connection(void *ctx, nxt_http_field_t *field, uintptr_t data) { - nxt_http_request_t *r; + nxt_http_request_t *r; + static const u_char *upgrade = (const u_char *) "upgrade"; r = ctx; if (field->value_length == 5 && nxt_memcmp(field->value, "close", 5) == 0) { r->proto.h1->keepalive = 0; + + } else if (field->value_length == 7 + && nxt_memcasecmp(field->value, upgrade, 7) == 0) + { + r->proto.h1->connection_upgrade = 1; + } + + return NXT_OK; +} + + +static nxt_int_t +nxt_h1p_upgrade(void *ctx, nxt_http_field_t *field, uintptr_t data) +{ + nxt_http_request_t *r; + static const u_char *websocket = (const u_char *) "websocket"; + + r = ctx; + + if (field->value_length == 9 + && nxt_memcasecmp(field->value, websocket, 9) == 0) + { + r->proto.h1->upgrade_websocket = 1; + } + + return NXT_OK; +} + + +static nxt_int_t +nxt_h1p_websocket_key(void *ctx, nxt_http_field_t *field, uintptr_t data) +{ + nxt_http_request_t *r; + + r = ctx; + + if (field->value_length == 24) { + r->proto.h1->websocket_key = field; + } + + return NXT_OK; +} + + +static nxt_int_t +nxt_h1p_websocket_version(void *ctx, nxt_http_field_t *field, uintptr_t data) +{ + nxt_http_request_t *r; + + r = ctx; + + if (field->value_length == 2 + && field->value[0] == '1' && field->value[1] == '3') + { + r->proto.h1->websocket_version_ok = 1; } return NXT_OK; @@ -727,6 +818,7 @@ nxt_h1p_request_body_read(nxt_task_t *task, nxt_http_request_t *r) if (size != 0) { in->next = h1p->buffers; h1p->buffers = in; + h1p->nbuffers++; c = h1p->conn; c->read = b; @@ -805,6 +897,15 @@ nxt_h1p_request_local_addr(nxt_task_t *task, nxt_http_request_t *r) } +#define NXT_HTTP_LAST_INFORMATIONAL \ + (NXT_HTTP_CONTINUE + nxt_nitems(nxt_http_informational) - 1) + +static const nxt_str_t nxt_http_informational[] = { + nxt_string("HTTP/1.1 100 Continue\r\n"), + nxt_string("HTTP/1.1 101 Switching Protocols\r\n"), +}; + + #define NXT_HTTP_LAST_SUCCESS \ (NXT_HTTP_OK + nxt_nitems(nxt_http_success) - 1) @@ -861,7 +962,7 @@ static const nxt_str_t nxt_http_client_error[] = { nxt_string("HTTP/1.1 423\r\n"), nxt_string("HTTP/1.1 424\r\n"), nxt_string("HTTP/1.1 425\r\n"), - nxt_string("HTTP/1.1 426\r\n"), + nxt_string("HTTP/1.1 426 Upgrade Required\r\n"), nxt_string("HTTP/1.1 427\r\n"), nxt_string("HTTP/1.1 428\r\n"), nxt_string("HTTP/1.1 429\r\n"), @@ -911,10 +1012,14 @@ nxt_h1p_request_header_send(nxt_task_t *task, nxt_http_request_t *r) u_char buf[UNKNOWN_STATUS_LENGTH]; static const char chunked[] = "Transfer-Encoding: chunked\r\n"; + static const char websocket_version[] = "Sec-WebSocket-Version: 13\r\n"; - static const nxt_str_t connection[2] = { + static const nxt_str_t connection[3] = { nxt_string("Connection: close\r\n"), nxt_string("Connection: keep-alive\r\n"), + nxt_string("Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Accept: "), }; nxt_debug(task, "h1p request header send"); @@ -923,7 +1028,10 @@ nxt_h1p_request_header_send(nxt_task_t *task, nxt_http_request_t *r) h1p = r->proto.h1; n = r->status; - if (n >= NXT_HTTP_OK && n <= NXT_HTTP_LAST_SUCCESS) { + if (n >= NXT_HTTP_CONTINUE && n <= NXT_HTTP_LAST_INFORMATIONAL) { + status = &nxt_http_informational[n - NXT_HTTP_CONTINUE]; + + } else if (n >= NXT_HTTP_OK && n <= NXT_HTTP_LAST_SUCCESS) { status = &nxt_http_success[n - NXT_HTTP_OK]; } else if (n >= NXT_HTTP_MULTIPLE_CHOICES @@ -955,27 +1063,41 @@ nxt_h1p_request_header_send(nxt_task_t *task, nxt_http_request_t *r) /* Trailing CRLF at the end of header. */ size += nxt_length("\r\n"); - http11 = (h1p->parser.version.s.minor != '0'); + conn = -1; + + if (r->websocket_handshake && n == NXT_HTTP_SWITCHING_PROTOCOLS) { + h1p->websocket = 1; + h1p->keepalive = 0; + conn = 2; + size += NXT_WEBSOCKET_ACCEPT_SIZE + 2; + + } else { + http11 = (h1p->parser.version.s.minor != '0'); + + if (r->resp.content_length == NULL || r->resp.content_length->skip) { - if (r->resp.content_length == NULL || r->resp.content_length->skip) { + if (http11) { + if (n != NXT_HTTP_NOT_MODIFIED + && n != NXT_HTTP_NO_CONTENT + && !h1p->websocket) + { + h1p->chunked = 1; + size += nxt_length(chunked); + /* Trailing CRLF will be added by the first chunk header. */ + size -= nxt_length("\r\n"); + } - if (http11) { - if (n != NXT_HTTP_NOT_MODIFIED && n != NXT_HTTP_NO_CONTENT) { - h1p->chunked = 1; - size += nxt_length(chunked); - /* Trailing CRLF will be added by the first chunk header. */ - size -= nxt_length("\r\n"); + } else { + h1p->keepalive = 0; } + } - } else { - h1p->keepalive = 0; + if (http11 ^ h1p->keepalive) { + conn = h1p->keepalive; } } - conn = -1; - - if (http11 ^ h1p->keepalive) { - conn = h1p->keepalive; + if (conn >= 0) { size += connection[conn].length; } @@ -988,15 +1110,17 @@ nxt_h1p_request_header_send(nxt_task_t *task, nxt_http_request_t *r) } nxt_list_loop; + if (nxt_slow_path(n == NXT_HTTP_UPGRADE_REQUIRED)) { + size += nxt_length(websocket_version); + } + header = nxt_http_buf_mem(task, r, size); if (nxt_slow_path(header == NULL)) { nxt_h1p_request_error(task, h1p, r); return; } - p = header->mem.free; - - p = nxt_cpymem(p, status->start, status->length); + p = nxt_cpymem(header->mem.free, status->start, status->length); nxt_list_each(field, r->resp.fields) { @@ -1013,6 +1137,17 @@ nxt_h1p_request_header_send(nxt_task_t *task, nxt_http_request_t *r) p = nxt_cpymem(p, connection[conn].start, connection[conn].length); } + if (h1p->websocket) { + nxt_websocket_accept(p, h1p->websocket_key->value); + p += NXT_WEBSOCKET_ACCEPT_SIZE; + + *p++ = '\r'; *p++ = '\n'; + } + + if (nxt_slow_path(n == NXT_HTTP_UPGRADE_REQUIRED)) { + p = nxt_cpymem(p, websocket_version, nxt_length(websocket_version)); + } + if (h1p->chunked) { p = nxt_cpymem(p, chunked, nxt_length(chunked)); /* Trailing CRLF will be added by the first chunk header. */ @@ -1031,6 +1166,58 @@ nxt_h1p_request_header_send(nxt_task_t *task, nxt_http_request_t *r) c->write_state = &nxt_h1p_request_send_state; nxt_conn_write(task->thread->engine, c); + + if (h1p->websocket) { + nxt_h1p_websocket_first_frame_start(task, r, c->read); + } +} + + +void +nxt_h1p_complete_buffers(nxt_task_t *task, nxt_h1proto_t *h1p) +{ + size_t size; + nxt_buf_t *b, *in, *next; + nxt_conn_t *c; + + nxt_debug(task, "h1p complete buffers"); + + b = h1p->buffers; + c = h1p->conn; + in = c->read; + + if (b != NULL) { + if (in == NULL) { + /* A request with large body. */ + in = b; + c->read = in; + + b = in->next; + in->next = NULL; + } + + while (b != NULL) { + next = b->next; + + nxt_work_queue_add(&task->thread->engine->fast_work_queue, + b->completion_handler, task, b, b->parent); + + b = next; + } + + h1p->buffers = NULL; + h1p->nbuffers = 0; + } + + if (in != NULL) { + size = nxt_buf_mem_used_size(&in->mem); + + if (size == 0) { + nxt_mp_free(c->mem_pool, in); + + c->read = NULL; + } + } } @@ -1181,8 +1368,13 @@ nxt_h1p_conn_request_error(nxt_task_t *task, void *obj, void *data) r = h1p->request; + if (nxt_slow_path(r == NULL)) { + nxt_h1p_shutdown(task, h1p->conn); + return; + } + if (r->fields == NULL) { - (void) nxt_h1p_header_process(h1p, r); + (void) nxt_h1p_header_process(task, h1p, r); } if (r->status == 0) { @@ -1218,7 +1410,7 @@ nxt_h1p_conn_request_timeout(nxt_task_t *task, void *obj, void *data) r = h1p->request; if (r->fields == NULL) { - (void) nxt_h1p_header_process(h1p, r); + (void) nxt_h1p_header_process(task, h1p, r); } nxt_http_request_error(task, r, NXT_HTTP_REQUEST_TIMEOUT); @@ -1244,7 +1436,7 @@ nxt_h1p_conn_request_send_timeout(nxt_task_t *task, void *obj, void *data) } -static nxt_msec_t +nxt_msec_t nxt_h1p_conn_request_timer_value(nxt_conn_t *c, uintptr_t data) { nxt_h1proto_t *h1p; @@ -1351,7 +1543,7 @@ static void nxt_h1p_keepalive(nxt_task_t *task, nxt_h1proto_t *h1p, nxt_conn_t *c) { size_t size; - nxt_buf_t *in, *b, *next; + nxt_buf_t *in; nxt_debug(task, "h1p keepalive"); @@ -1359,40 +1551,22 @@ nxt_h1p_keepalive(nxt_task_t *task, nxt_h1proto_t *h1p, nxt_conn_t *c) nxt_conn_tcp_nodelay_on(task, c); } - b = h1p->buffers; + nxt_h1p_complete_buffers(task, h1p); + + in = c->read; nxt_memzero(h1p, offsetof(nxt_h1proto_t, conn)); c->sent = 0; - in = c->read; - if (in == NULL) { - /* A request with large body. */ - in = b; - c->read = in; - - b = in->next; - in->next = NULL; - } - - while (b != NULL) { - next = b->next; - nxt_mp_free(c->mem_pool, b); - b = next; - } - - size = nxt_buf_mem_used_size(&in->mem); - - if (size == 0) { - nxt_mp_free(c->mem_pool, in); - - c->read = NULL; c->read_state = &nxt_h1p_keepalive_state; nxt_conn_read(task->thread->engine, c); } else { + size = nxt_buf_mem_used_size(&in->mem); + nxt_debug(task, "h1p pipelining"); nxt_memmove(in->mem.start, in->mem.pos, size); @@ -1421,7 +1595,7 @@ static const nxt_conn_state_t nxt_h1p_keepalive_state }; -static const nxt_conn_state_t nxt_h1p_idle_close_state +const nxt_conn_state_t nxt_h1p_idle_close_state nxt_aligned(64) = { .close_handler = nxt_h1p_idle_close, @@ -1564,8 +1738,33 @@ nxt_h1p_idle_response_timer_value(nxt_conn_t *c, uintptr_t data) static void nxt_h1p_shutdown(nxt_task_t *task, nxt_conn_t *c) { + nxt_timer_t *timer; + nxt_h1proto_t *h1p; + nxt_debug(task, "h1p shutdown"); + h1p = c->socket.data; + + if (nxt_slow_path(h1p != NULL && h1p->websocket_timer != NULL)) { + timer = &h1p->websocket_timer->timer; + + if (timer->handler != nxt_h1p_conn_ws_shutdown) { + timer->handler = nxt_h1p_conn_ws_shutdown; + nxt_timer_add(task->thread->engine, timer, 0); + + } else { + nxt_debug(task, "h1p already scheduled ws shutdown"); + } + + } else { + nxt_h1p_shutdown_(task, c); + } +} + + +static void +nxt_h1p_shutdown_(nxt_task_t *task, nxt_conn_t *c) +{ c->socket.data = NULL; #if (NXT_TLS) @@ -1596,6 +1795,21 @@ static const nxt_conn_state_t nxt_h1p_shutdown_state #endif +static void +nxt_h1p_conn_ws_shutdown(nxt_task_t *task, void *obj, void *data) +{ + nxt_timer_t *timer; + nxt_h1p_websocket_timer_t *ws_timer; + + nxt_debug(task, "h1p conn ws shutdown"); + + timer = obj; + ws_timer = nxt_timer_data(timer, nxt_h1p_websocket_timer_t, timer); + + nxt_h1p_shutdown_(task, ws_timer->h1p->conn); +} + + static void nxt_h1p_conn_closing(nxt_task_t *task, void *obj, void *data) { diff --git a/src/nxt_h1proto.h b/src/nxt_h1proto.h new file mode 100644 index 00000000..c6d3bd53 --- /dev/null +++ b/src/nxt_h1proto.h @@ -0,0 +1,48 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NXT_H1PROTO_H_INCLUDED_ +#define _NXT_H1PROTO_H_INCLUDED_ + + +#include +#include +#include +#include + + +typedef struct nxt_h1p_websocket_timer_s nxt_h1p_websocket_timer_t; + + +struct nxt_h1proto_s { + nxt_http_request_parse_t parser; + + uint8_t nbuffers; + uint8_t keepalive; /* 1 bit */ + uint8_t chunked; /* 1 bit */ + uint8_t websocket; /* 1 bit */ + uint8_t connection_upgrade; /* 1 bit */ + uint8_t upgrade_websocket; /* 1 bit */ + uint8_t websocket_version_ok; /* 1 bit */ + nxt_http_te_t transfer_encoding:8; /* 2 bits */ + + uint8_t websocket_cont_expected; /* 1 bit */ + uint8_t websocket_closed; /* 1 bit */ + + uint32_t header_size; + + nxt_http_field_t *websocket_key; + nxt_h1p_websocket_timer_t *websocket_timer; + + nxt_http_request_t *request; + nxt_buf_t *buffers; + /* + * All fields before the conn field will + * be zeroed in a keep-alive connection. + */ + nxt_conn_t *conn; +}; + +#endif /* _NXT_H1PROTO_H_INCLUDED_ */ diff --git a/src/nxt_h1proto_websocket.c b/src/nxt_h1proto_websocket.c new file mode 100644 index 00000000..dd9b6848 --- /dev/null +++ b/src/nxt_h1proto_websocket.c @@ -0,0 +1,719 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include +#include +#include +#include + +typedef struct { + uint16_t code; + uint8_t args; + nxt_str_t desc; +} nxt_ws_error_t; + +static void nxt_h1p_conn_ws_keepalive(nxt_task_t *task, void *obj, void *data); +static void nxt_h1p_conn_ws_frame_header_read(nxt_task_t *task, void *obj, + void *data); +static void nxt_h1p_conn_ws_keepalive_disable(nxt_task_t *task, + nxt_h1proto_t *h1p); +static void nxt_h1p_conn_ws_keepalive_enable(nxt_task_t *task, + nxt_h1proto_t *h1p); +static void nxt_h1p_conn_ws_frame_process(nxt_task_t *task, nxt_conn_t *c, + nxt_h1proto_t *h1p, nxt_websocket_header_t *wsh); +static void nxt_h1p_conn_ws_error(nxt_task_t *task, void *obj, void *data); +static ssize_t nxt_h1p_ws_io_read_handler(nxt_conn_t *c); +static void nxt_h1p_conn_ws_timeout(nxt_task_t *task, void *obj, void *data); +static void nxt_h1p_conn_ws_frame_payload_read(nxt_task_t *task, void *obj, + void *data); +static void hxt_h1p_send_ws_error(nxt_task_t *task, nxt_http_request_t *r, + const nxt_ws_error_t *err, ...); +static void nxt_h1p_conn_ws_error_sent(nxt_task_t *task, void *obj, void *data); +static void nxt_h1p_conn_ws_pong(nxt_task_t *task, void *obj, void *data); + +static const nxt_conn_state_t nxt_h1p_read_ws_frame_header_state; +static const nxt_conn_state_t nxt_h1p_read_ws_frame_payload_state; + +static const nxt_ws_error_t nxt_ws_err_out_of_memory = { + NXT_WEBSOCKET_CR_INTERNAL_SERVER_ERROR, + 0, nxt_string("Out of memory") }; +static const nxt_ws_error_t nxt_ws_err_too_big = { + NXT_WEBSOCKET_CR_MESSAGE_TOO_BIG, + 1, nxt_string("Message too big: %uL bytes") }; +static const nxt_ws_error_t nxt_ws_err_invalid_close_code = { + NXT_WEBSOCKET_CR_PROTOCOL_ERROR, + 1, nxt_string("Close code %ud is not valid") }; +static const nxt_ws_error_t nxt_ws_err_going_away = { + NXT_WEBSOCKET_CR_GOING_AWAY, + 0, nxt_string("Remote peer is going away") }; +static const nxt_ws_error_t nxt_ws_err_not_masked = { + NXT_WEBSOCKET_CR_PROTOCOL_ERROR, + 0, nxt_string("Not masked client frame") }; +static const nxt_ws_error_t nxt_ws_err_ctrl_fragmented = { + NXT_WEBSOCKET_CR_PROTOCOL_ERROR, + 0, nxt_string("Fragmented control frame") }; +static const nxt_ws_error_t nxt_ws_err_ctrl_too_big = { + NXT_WEBSOCKET_CR_PROTOCOL_ERROR, + 1, nxt_string("Control frame too big: %uL bytes") }; +static const nxt_ws_error_t nxt_ws_err_invalid_close_len = { + NXT_WEBSOCKET_CR_PROTOCOL_ERROR, + 0, nxt_string("Close frame payload length cannot be 1") }; +static const nxt_ws_error_t nxt_ws_err_invalid_opcode = { + NXT_WEBSOCKET_CR_PROTOCOL_ERROR, + 1, nxt_string("Unrecognized opcode %ud") }; +static const nxt_ws_error_t nxt_ws_err_cont_expected = { + NXT_WEBSOCKET_CR_PROTOCOL_ERROR, + 1, nxt_string("Continuation expected, but %ud opcode received") }; + +void +nxt_h1p_websocket_first_frame_start(nxt_task_t *task, nxt_http_request_t *r, + nxt_buf_t *ws_frame) +{ + nxt_conn_t *c; + nxt_timer_t *timer; + nxt_h1proto_t *h1p; + nxt_socket_conf_joint_t *joint; + + nxt_debug(task, "h1p ws first frame start"); + + h1p = r->proto.h1; + c = h1p->conn; + + if (!c->tcp_nodelay) { + nxt_conn_tcp_nodelay_on(task, c); + } + + joint = c->listen->socket.data; + + if (nxt_slow_path(joint != NULL + && joint->socket_conf->websocket_conf.keepalive_interval != 0)) + { + h1p->websocket_timer = nxt_mp_zget(c->mem_pool, + sizeof(nxt_h1p_websocket_timer_t)); + if (nxt_slow_path(h1p->websocket_timer == NULL)) { + hxt_h1p_send_ws_error(task, r, &nxt_ws_err_out_of_memory); + return; + } + + h1p->websocket_timer->keepalive_interval = + joint->socket_conf->websocket_conf.keepalive_interval; + h1p->websocket_timer->h1p = h1p; + + timer = &h1p->websocket_timer->timer; + timer->task = &c->task; + timer->work_queue = &task->thread->engine->fast_work_queue; + timer->log = &c->log; + timer->bias = NXT_TIMER_DEFAULT_BIAS; + timer->handler = nxt_h1p_conn_ws_keepalive; + } + + nxt_h1p_websocket_frame_start(task, r, ws_frame); +} + + +void +nxt_h1p_websocket_frame_start(nxt_task_t *task, nxt_http_request_t *r, + nxt_buf_t *ws_frame) +{ + size_t size; + nxt_buf_t *in; + nxt_conn_t *c; + nxt_h1proto_t *h1p; + + nxt_debug(task, "h1p ws frame start"); + + h1p = r->proto.h1; + + if (nxt_slow_path(h1p->websocket_closed)) { + return; + } + + c = h1p->conn; + c->read = ws_frame; + + nxt_h1p_complete_buffers(task, h1p); + + in = c->read; + c->read_state = &nxt_h1p_read_ws_frame_header_state; + + if (in == NULL) { + nxt_conn_read(task->thread->engine, c); + nxt_h1p_conn_ws_keepalive_enable(task, h1p); + + } else { + size = nxt_buf_mem_used_size(&in->mem); + + nxt_debug(task, "h1p read client ws frame"); + + nxt_memmove(in->mem.start, in->mem.pos, size); + + in->mem.pos = in->mem.start; + in->mem.free = in->mem.start + size; + + nxt_h1p_conn_ws_frame_header_read(task, c, h1p); + } +} + + +static void +nxt_h1p_conn_ws_keepalive(nxt_task_t *task, void *obj, void *data) +{ + nxt_buf_t *out; + nxt_timer_t *timer; + nxt_h1proto_t *h1p; + nxt_http_request_t *r; + nxt_websocket_header_t *wsh; + nxt_h1p_websocket_timer_t *ws_timer; + + nxt_debug(task, "h1p conn ws keepalive"); + + timer = obj; + ws_timer = nxt_timer_data(timer, nxt_h1p_websocket_timer_t, timer); + h1p = ws_timer->h1p; + + r = h1p->request; + if (nxt_slow_path(r == NULL)) { + return; + } + + out = nxt_http_buf_mem(task, r, 2); + if (nxt_slow_path(out == NULL)) { + nxt_http_request_error_handler(task, r, r->proto.any); + return; + } + + out->mem.start[0] = 0; + out->mem.start[1] = 0; + + wsh = (nxt_websocket_header_t *) out->mem.start; + out->mem.free = nxt_websocket_frame_init(wsh, 0); + + wsh->fin = 1; + wsh->opcode = NXT_WEBSOCKET_OP_PING; + + nxt_http_request_send(task, r, out); +} + + +static const nxt_conn_state_t nxt_h1p_read_ws_frame_header_state + nxt_aligned(64) = +{ + .ready_handler = nxt_h1p_conn_ws_frame_header_read, + .close_handler = nxt_h1p_conn_ws_error, + .error_handler = nxt_h1p_conn_ws_error, + + .io_read_handler = nxt_h1p_ws_io_read_handler, + + .timer_handler = nxt_h1p_conn_ws_timeout, + .timer_value = nxt_h1p_conn_request_timer_value, + .timer_data = offsetof(nxt_socket_conf_t, websocket_conf.read_timeout), + .timer_autoreset = 1, +}; + + +static void +nxt_h1p_conn_ws_frame_header_read(nxt_task_t *task, void *obj, void *data) +{ + size_t size, hsize, frame_size, max_frame_size; + uint64_t payload_len; + nxt_conn_t *c; + nxt_h1proto_t *h1p; + nxt_http_request_t *r; + nxt_event_engine_t *engine; + nxt_websocket_header_t *wsh; + nxt_socket_conf_joint_t *joint; + + c = obj; + h1p = data; + + nxt_h1p_conn_ws_keepalive_disable(task, h1p); + + size = nxt_buf_mem_used_size(&c->read->mem); + + engine = task->thread->engine; + + if (size < 2) { + nxt_debug(task, "h1p conn ws frame header read %z", size); + + nxt_conn_read(engine, c); + nxt_h1p_conn_ws_keepalive_enable(task, h1p); + + return; + } + + wsh = (nxt_websocket_header_t *) c->read->mem.pos; + + hsize = nxt_websocket_frame_header_size(wsh); + + if (size < hsize) { + nxt_debug(task, "h1p conn ws frame header read %z < %z", size, hsize); + + nxt_conn_read(engine, c); + nxt_h1p_conn_ws_keepalive_enable(task, h1p); + + return; + } + + r = h1p->request; + if (nxt_slow_path(r == NULL)) { + return; + } + + r->ws_frame = c->read; + + joint = c->listen->socket.data; + + if (nxt_slow_path(joint == NULL)) { + /* + * Listening socket had been closed while + * connection was in keep-alive state. + */ + c->read_state = &nxt_h1p_idle_close_state; + return; + } + + if (nxt_slow_path(wsh->mask == 0)) { + hxt_h1p_send_ws_error(task, r, &nxt_ws_err_not_masked); + return; + } + + if ((wsh->opcode & NXT_WEBSOCKET_OP_CTRL) != 0) { + if (nxt_slow_path(wsh->fin == 0)) { + hxt_h1p_send_ws_error(task, r, &nxt_ws_err_ctrl_fragmented); + return; + } + + if (nxt_slow_path(wsh->opcode != NXT_WEBSOCKET_OP_PING + && wsh->opcode != NXT_WEBSOCKET_OP_PONG + && wsh->opcode != NXT_WEBSOCKET_OP_CLOSE)) + { + hxt_h1p_send_ws_error(task, r, &nxt_ws_err_invalid_opcode, + wsh->opcode); + return; + } + + if (nxt_slow_path(wsh->payload_len > 125)) { + hxt_h1p_send_ws_error(task, r, &nxt_ws_err_ctrl_too_big, + nxt_websocket_frame_payload_len(wsh)); + return; + } + + if (nxt_slow_path(wsh->opcode == NXT_WEBSOCKET_OP_CLOSE + && wsh->payload_len == 1)) + { + hxt_h1p_send_ws_error(task, r, &nxt_ws_err_invalid_close_len); + return; + } + + } else { + if (h1p->websocket_cont_expected) { + if (nxt_slow_path(wsh->opcode != NXT_WEBSOCKET_OP_CONT)) { + hxt_h1p_send_ws_error(task, r, &nxt_ws_err_cont_expected, + wsh->opcode); + return; + } + + } else { + if (nxt_slow_path(wsh->opcode != NXT_WEBSOCKET_OP_BINARY + && wsh->opcode != NXT_WEBSOCKET_OP_TEXT)) + { + hxt_h1p_send_ws_error(task, r, &nxt_ws_err_invalid_opcode, + wsh->opcode); + return; + } + } + + h1p->websocket_cont_expected = !wsh->fin; + } + + max_frame_size = joint->socket_conf->websocket_conf.max_frame_size; + + payload_len = nxt_websocket_frame_payload_len(wsh); + + if (nxt_slow_path(hsize > max_frame_size + || payload_len > (max_frame_size - hsize))) + { + hxt_h1p_send_ws_error(task, r, &nxt_ws_err_too_big, payload_len); + return; + } + + c->read_state = &nxt_h1p_read_ws_frame_payload_state; + + frame_size = payload_len + hsize; + + nxt_debug(task, "h1p conn ws frame header read: %z, %z", size, frame_size); + + if (frame_size <= size) { + nxt_h1p_conn_ws_frame_process(task, c, h1p, wsh); + + return; + } + + if (frame_size < (size_t) nxt_buf_mem_size(&c->read->mem)) { + c->read->mem.end = c->read->mem.start + frame_size; + + } else { + nxt_buf_t *b = nxt_buf_mem_alloc(c->mem_pool, frame_size - size, 0); + + c->read->next = b; + c->read = b; + } + + nxt_conn_read(engine, c); + nxt_h1p_conn_ws_keepalive_enable(task, h1p); +} + + +static void +nxt_h1p_conn_ws_keepalive_disable(nxt_task_t *task, nxt_h1proto_t *h1p) +{ + nxt_timer_t *timer; + + if (h1p->websocket_timer == NULL) { + return; + } + + timer = &h1p->websocket_timer->timer; + + if (nxt_slow_path(timer->handler != nxt_h1p_conn_ws_keepalive)) { + nxt_debug(task, "h1p ws keepalive disable: scheduled ws shutdown"); + return; + } + + nxt_timer_disable(task->thread->engine, timer); +} + + +static void +nxt_h1p_conn_ws_keepalive_enable(nxt_task_t *task, nxt_h1proto_t *h1p) +{ + nxt_timer_t *timer; + + if (h1p->websocket_timer == NULL) { + return; + } + + timer = &h1p->websocket_timer->timer; + + if (nxt_slow_path(timer->handler != nxt_h1p_conn_ws_keepalive)) { + nxt_debug(task, "h1p ws keepalive enable: scheduled ws shutdown"); + return; + } + + nxt_timer_add(task->thread->engine, timer, + h1p->websocket_timer->keepalive_interval); +} + + +static void +nxt_h1p_conn_ws_frame_process(nxt_task_t *task, nxt_conn_t *c, + nxt_h1proto_t *h1p, nxt_websocket_header_t *wsh) +{ + size_t hsize; + uint8_t *p, *mask; + uint16_t code; + nxt_http_request_t *r; + nxt_event_engine_t *engine; + + engine = task->thread->engine; + r = h1p->request; + + c->read = NULL; + + if (nxt_slow_path(wsh->opcode == NXT_WEBSOCKET_OP_PING)) { + nxt_work_queue_add(&engine->fast_work_queue, nxt_h1p_conn_ws_pong, + task, r, NULL); + return; + } + + if (nxt_slow_path(wsh->opcode == NXT_WEBSOCKET_OP_CLOSE)) { + if (wsh->payload_len >= 2) { + hsize = nxt_websocket_frame_header_size(wsh); + mask = nxt_pointer_to(wsh, hsize - 4); + p = nxt_pointer_to(wsh, hsize); + + code = ((p[0] ^ mask[0]) << 8) + (p[1] ^ mask[1]); + + if (nxt_slow_path(code < 1000 || code >= 5000 + || (code > 1003 && code < 1007) + || (code > 1014 && code < 3000))) + { + hxt_h1p_send_ws_error(task, r, &nxt_ws_err_invalid_close_code, + code); + return; + } + } + + h1p->websocket_closed = 1; + } + + nxt_work_queue_add(&engine->fast_work_queue, r->state->ready_handler, + task, r, NULL); +} + + +static void +nxt_h1p_conn_ws_error(nxt_task_t *task, void *obj, void *data) +{ + nxt_h1proto_t *h1p; + nxt_http_request_t *r; + + h1p = data; + + nxt_debug(task, "h1p conn ws error"); + + r = h1p->request; + + h1p->keepalive = 0; + + if (nxt_fast_path(r != NULL)) { + r->state->error_handler(task, r, h1p); + } +} + + +static ssize_t +nxt_h1p_ws_io_read_handler(nxt_conn_t *c) +{ + size_t size; + ssize_t n; + nxt_buf_t *b; + + b = c->read; + + if (b == NULL) { + /* Enough for control frame. */ + size = 10 + 125; + + b = nxt_buf_mem_alloc(c->mem_pool, size, 0); + if (nxt_slow_path(b == NULL)) { + c->socket.error = NXT_ENOMEM; + return NXT_ERROR; + } + } + + n = c->io->recvbuf(c, b); + + if (n > 0) { + c->read = b; + + } else { + c->read = NULL; + nxt_mp_free(c->mem_pool, b); + } + + return n; +} + + +static void +nxt_h1p_conn_ws_timeout(nxt_task_t *task, void *obj, void *data) +{ + nxt_conn_t *c; + nxt_timer_t *timer; + nxt_h1proto_t *h1p; + nxt_http_request_t *r; + + timer = obj; + + nxt_debug(task, "h1p conn ws timeout"); + + c = nxt_read_timer_conn(timer); + c->block_read = 1; + /* + * Disable SO_LINGER off during socket closing + * to send "408 Request Timeout" error response. + */ + c->socket.timedout = 0; + + h1p = c->socket.data; + h1p->keepalive = 0; + + r = h1p->request; + if (nxt_slow_path(r == NULL)) { + return; + } + + hxt_h1p_send_ws_error(task, r, &nxt_ws_err_going_away); +} + + +static const nxt_conn_state_t nxt_h1p_read_ws_frame_payload_state + nxt_aligned(64) = +{ + .ready_handler = nxt_h1p_conn_ws_frame_payload_read, + .close_handler = nxt_h1p_conn_ws_error, + .error_handler = nxt_h1p_conn_ws_error, + + .timer_handler = nxt_h1p_conn_ws_timeout, + .timer_value = nxt_h1p_conn_request_timer_value, + .timer_data = offsetof(nxt_socket_conf_t, websocket_conf.read_timeout), + .timer_autoreset = 1, +}; + + +static void +nxt_h1p_conn_ws_frame_payload_read(nxt_task_t *task, void *obj, void *data) +{ + nxt_conn_t *c; + nxt_h1proto_t *h1p; + nxt_http_request_t *r; + nxt_event_engine_t *engine; + nxt_websocket_header_t *wsh; + + c = obj; + h1p = data; + + nxt_h1p_conn_ws_keepalive_disable(task, h1p); + + nxt_debug(task, "h1p conn ws frame read"); + + if (nxt_buf_mem_free_size(&c->read->mem) == 0) { + r = h1p->request; + if (nxt_slow_path(r == NULL)) { + return; + } + + wsh = (nxt_websocket_header_t *) r->ws_frame->mem.pos; + + nxt_h1p_conn_ws_frame_process(task, c, h1p, wsh); + + return; + } + + engine = task->thread->engine; + + nxt_conn_read(engine, c); + nxt_h1p_conn_ws_keepalive_enable(task, h1p); +} + + +static void +hxt_h1p_send_ws_error(nxt_task_t *task, nxt_http_request_t *r, + const nxt_ws_error_t *err, ...) +{ + u_char *p; + va_list args; + nxt_buf_t *out; + nxt_str_t desc; + nxt_websocket_header_t *wsh; + u_char buf[125]; + + if (nxt_slow_path(err->args)) { + va_start(args, err); + p = nxt_vsprintf(buf, buf + sizeof(buf), (char *) err->desc.start, + args); + va_end(args); + + desc.start = buf; + desc.length = p - buf; + + } else { + desc = err->desc; + } + + nxt_log(task, NXT_LOG_INFO, "websocket error %d: %V", err->code, &desc); + + out = nxt_http_buf_mem(task, r, 2 + sizeof(err->code) + desc.length); + if (nxt_slow_path(out == NULL)) { + nxt_http_request_error_handler(task, r, r->proto.any); + return; + } + + out->mem.start[0] = 0; + out->mem.start[1] = 0; + + wsh = (nxt_websocket_header_t *) out->mem.start; + p = nxt_websocket_frame_init(wsh, sizeof(err->code) + desc.length); + + wsh->fin = 1; + wsh->opcode = NXT_WEBSOCKET_OP_CLOSE; + + *p++ = (err->code >> 8) & 0xFF; + *p++ = err->code & 0xFF; + + out->mem.free = nxt_cpymem(p, desc.start, desc.length); + out->next = nxt_http_buf_last(r); + + if (out->next != NULL) { + out->next->completion_handler = nxt_h1p_conn_ws_error_sent; + } + + nxt_http_request_send(task, r, out); +} + + +static void +nxt_h1p_conn_ws_error_sent(nxt_task_t *task, void *obj, void *data) +{ + nxt_http_request_t *r; + + r = data; + + nxt_debug(task, "h1p conn ws error sent"); + + r->state->error_handler(task, r, r->proto.any); +} + + +static void +nxt_h1p_conn_ws_pong(nxt_task_t *task, void *obj, void *data) +{ + uint8_t payload_len, i; + nxt_buf_t *b, *out, *next; + nxt_http_request_t *r; + nxt_websocket_header_t *wsh; + uint8_t mask[4]; + + nxt_debug(task, "h1p conn ws pong"); + + r = obj; + b = r->ws_frame; + + wsh = (nxt_websocket_header_t *) b->mem.pos; + payload_len = wsh->payload_len; + + b->mem.pos += 2; + + nxt_memcpy(mask, b->mem.pos, 4); + + b->mem.pos += 4; + + out = nxt_http_buf_mem(task, r, 2 + payload_len); + if (nxt_slow_path(out == NULL)) { + nxt_http_request_error_handler(task, r, r->proto.any); + return; + } + + out->mem.start[0] = 0; + out->mem.start[1] = 0; + + wsh = (nxt_websocket_header_t *) out->mem.start; + out->mem.free = nxt_websocket_frame_init(wsh, payload_len); + + wsh->fin = 1; + wsh->opcode = NXT_WEBSOCKET_OP_PONG; + + for (i = 0; i < payload_len; i++) { + while (nxt_buf_mem_used_size(&b->mem) == 0) { + next = b->next; + + nxt_work_queue_add(&task->thread->engine->fast_work_queue, + b->completion_handler, task, b, b->parent); + + b = next; + } + + *out->mem.free++ = *b->mem.pos++ ^ mask[i % 4]; + } + + r->ws_frame = b; + + nxt_http_request_send(task, r, out); + + nxt_http_request_ws_frame_start(task, r, r->ws_frame); +} diff --git a/src/nxt_http.h b/src/nxt_http.h index c1a230ec..ac1eedcf 100644 --- a/src/nxt_http.h +++ b/src/nxt_http.h @@ -11,6 +11,9 @@ typedef enum { NXT_HTTP_INVALID = 0, + NXT_HTTP_CONTINUE = 100, + NXT_HTTP_SWITCHING_PROTOCOLS = 101, + NXT_HTTP_OK = 200, NXT_HTTP_NO_CONTENT = 204, @@ -26,6 +29,7 @@ typedef enum { NXT_HTTP_LENGTH_REQUIRED = 411, NXT_HTTP_PAYLOAD_TOO_LARGE = 413, NXT_HTTP_URI_TOO_LONG = 414, + NXT_HTTP_UPGRADE_REQUIRED = 426, NXT_HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431, NXT_HTTP_TO_HTTPS = 497, @@ -61,6 +65,13 @@ typedef struct { typedef struct nxt_h1proto_s nxt_h1proto_t; +struct nxt_h1p_websocket_timer_s { + nxt_timer_t timer; + nxt_h1proto_t *h1p; + nxt_msec_t keepalive_interval; +}; + + typedef union { void *any; nxt_h1proto_t *h1; @@ -99,6 +110,7 @@ struct nxt_http_request_s { nxt_mp_t *mem_pool; nxt_buf_t *body; + nxt_buf_t *ws_frame; nxt_buf_t *out; const nxt_http_request_state_t *state; @@ -127,6 +139,8 @@ struct nxt_http_request_s { nxt_timer_t timer; void *timer_data; + void *req_rpc_data; + nxt_buf_t *last; nxt_http_response_t resp; @@ -138,6 +152,7 @@ struct nxt_http_request_s { uint8_t logged; /* 1 bit */ uint8_t header_sent; /* 1 bit */ uint8_t error; /* 1 bit */ + uint8_t websocket_handshake; /* 1 bit */ }; @@ -166,6 +181,8 @@ typedef struct { void (*discard)(nxt_task_t *task, nxt_http_request_t *r, nxt_buf_t *last); void (*close)(nxt_task_t *task, nxt_http_proto_t proto, nxt_socket_conf_joint_t *joint); + void (*ws_frame_start)(nxt_task_t *task, nxt_http_request_t *r, + nxt_buf_t *ws_frame); } nxt_http_proto_table_t; @@ -179,12 +196,15 @@ void nxt_http_request_error(nxt_task_t *task, nxt_http_request_t *r, nxt_http_status_t status); void nxt_http_request_read_body(nxt_task_t *task, nxt_http_request_t *r); void nxt_http_request_header_send(nxt_task_t *task, nxt_http_request_t *r); +void nxt_http_request_ws_frame_start(nxt_task_t *task, nxt_http_request_t *r, + nxt_buf_t *ws_frame); void nxt_http_request_send(nxt_task_t *task, nxt_http_request_t *r, nxt_buf_t *out); nxt_buf_t *nxt_http_buf_mem(nxt_task_t *task, nxt_http_request_t *r, size_t size); nxt_buf_t *nxt_http_buf_last(nxt_http_request_t *r); void nxt_http_request_error_handler(nxt_task_t *task, void *obj, void *data); +void nxt_http_request_close_handler(nxt_task_t *task, void *obj, void *data); nxt_int_t nxt_http_request_host(void *ctx, nxt_http_field_t *field, uintptr_t data); @@ -212,5 +232,13 @@ extern nxt_lvlhsh_t nxt_response_fields_hash; extern const nxt_http_proto_table_t nxt_http_proto[]; +void nxt_h1p_websocket_first_frame_start(nxt_task_t *task, + nxt_http_request_t *r, nxt_buf_t *ws_frame); +void nxt_h1p_websocket_frame_start(nxt_task_t *task, nxt_http_request_t *r, + nxt_buf_t *ws_frame); +void nxt_h1p_complete_buffers(nxt_task_t *task, nxt_h1proto_t *h1p); +nxt_msec_t nxt_h1p_conn_request_timer_value(nxt_conn_t *c, uintptr_t data); + +extern const nxt_conn_state_t nxt_h1p_idle_close_state; #endif /* _NXT_HTTP_H_INCLUDED_ */ diff --git a/src/nxt_http_request.c b/src/nxt_http_request.c index 1ab22223..916004d2 100644 --- a/src/nxt_http_request.c +++ b/src/nxt_http_request.c @@ -16,8 +16,6 @@ static void nxt_http_request_proto_info(nxt_task_t *task, static void nxt_http_request_mem_buf_completion(nxt_task_t *task, void *obj, void *data); static void nxt_http_request_done(nxt_task_t *task, void *obj, void *data); -static void nxt_http_request_close_handler(nxt_task_t *task, void *obj, - void *data); static u_char *nxt_http_date(u_char *buf, nxt_realtime_t *now, struct tm *tm, size_t size, const char *format); @@ -443,6 +441,16 @@ fail: } +void +nxt_http_request_ws_frame_start(nxt_task_t *task, nxt_http_request_t *r, + nxt_buf_t *ws_frame) +{ + if (r->proto.any != NULL) { + nxt_http_proto[r->protocol].ws_frame_start(task, r, ws_frame); + } +} + + void nxt_http_request_send(nxt_task_t *task, nxt_http_request_t *r, nxt_buf_t *out) { @@ -530,7 +538,7 @@ nxt_http_request_error_handler(nxt_task_t *task, void *obj, void *data) } -static void +void nxt_http_request_close_handler(nxt_task_t *task, void *obj, void *data) { nxt_http_proto_t proto; @@ -556,12 +564,13 @@ nxt_http_request_close_handler(nxt_task_t *task, void *obj, void *data) } } - protocol = r->protocol; - r->proto.any = NULL; - nxt_mp_release(r->mem_pool); if (nxt_fast_path(proto.any != NULL)) { + protocol = r->protocol; + + nxt_mp_release(r->mem_pool); + nxt_http_proto[protocol].close(task, proto, conf); } } diff --git a/src/nxt_http_response.c b/src/nxt_http_response.c index 755182db..00ecff00 100644 --- a/src/nxt_http_response.c +++ b/src/nxt_http_response.c @@ -28,6 +28,8 @@ static nxt_http_field_proc_t nxt_response_fields[] = { offsetof(nxt_http_request_t, resp.content_type) }, { nxt_string("Content-Length"), &nxt_http_response_field, offsetof(nxt_http_request_t, resp.content_length) }, + { nxt_string("Upgrade"), &nxt_http_response_skip, 0 }, + { nxt_string("Sec-WebSocket-Accept"), &nxt_http_response_skip, 0 }, }; diff --git a/src/nxt_http_websocket.c b/src/nxt_http_websocket.c new file mode 100644 index 00000000..d58d615c --- /dev/null +++ b/src/nxt_http_websocket.c @@ -0,0 +1,161 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include + + +static void nxt_http_websocket_client(nxt_task_t *task, void *obj, void *data); +static void nxt_http_websocket_error_handler(nxt_task_t *task, void *obj, + void *data); + + +const nxt_http_request_state_t nxt_http_websocket + nxt_aligned(64) = +{ + .ready_handler = nxt_http_websocket_client, + .error_handler = nxt_http_websocket_error_handler, +}; + + +static void +nxt_http_websocket_client(nxt_task_t *task, void *obj, void *data) +{ + size_t frame_size, used_size, copy_size, buf_free_size; + size_t chunk_copy_size; + nxt_buf_t *out, *buf, **out_tail, *b, *next; + nxt_int_t res; + nxt_http_request_t *r; + nxt_request_app_link_t *req_app_link; + nxt_request_rpc_data_t *req_rpc_data; + nxt_websocket_header_t *wsh; + + r = obj; + + if (nxt_slow_path((req_rpc_data = r->req_rpc_data) == NULL + || (req_app_link = req_rpc_data->req_app_link) == NULL)) + { + nxt_debug(task, "websocket client frame for destroyed request"); + + return; + } + + nxt_debug(task, "http websocket client frame"); + + wsh = (nxt_websocket_header_t *) r->ws_frame->mem.pos; + + frame_size = nxt_websocket_frame_header_size(wsh) + + nxt_websocket_frame_payload_len(wsh); + + buf = NULL; + buf_free_size = 0; + out = NULL; + out_tail = &out; + + b = r->ws_frame; + + while (b != NULL && frame_size > 0) { + used_size = nxt_buf_mem_used_size(&b->mem); + copy_size = nxt_min(used_size, frame_size); + + while (copy_size > 0) { + if (buf == NULL || buf_free_size == 0) { + buf_free_size = nxt_min(frame_size, PORT_MMAP_DATA_SIZE); + + buf = nxt_port_mmap_get_buf(task, req_app_link->app_port, + buf_free_size); + + *out_tail = buf; + out_tail = &buf->next; + } + + chunk_copy_size = nxt_min(buf_free_size, copy_size); + + buf->mem.free = nxt_cpymem(buf->mem.free, b->mem.pos, + chunk_copy_size); + + copy_size -= chunk_copy_size; + b->mem.pos += chunk_copy_size; + buf_free_size -= chunk_copy_size; + } + + frame_size -= copy_size; + next = b->next; + + if (nxt_buf_mem_used_size(&b->mem) == 0) { + nxt_work_queue_add(&task->thread->engine->fast_work_queue, + b->completion_handler, task, b, b->parent); + + r->ws_frame = next; + } + + b = next; + } + + res = nxt_port_socket_twrite(task, req_app_link->app_port, + NXT_PORT_MSG_WEBSOCKET, -1, + req_app_link->stream, + req_app_link->reply_port->id, out, NULL); + if (nxt_slow_path(res != NXT_OK)) { + // TODO: handle + } + + b = r->ws_frame; + + if (b != NULL) { + used_size = nxt_buf_mem_used_size(&b->mem); + + if (used_size > 0) { + nxt_memmove(b->mem.start, b->mem.pos, used_size); + + b->mem.pos = b->mem.start; + b->mem.free = b->mem.start + used_size; + } + } + + nxt_http_request_ws_frame_start(task, r, r->ws_frame); +} + + +static void +nxt_http_websocket_error_handler(nxt_task_t *task, void *obj, void *data) +{ + nxt_http_request_t *r; + nxt_request_app_link_t *req_app_link; + nxt_request_rpc_data_t *req_rpc_data; + + nxt_debug(task, "http websocket error handler"); + + r = obj; + + if ((req_rpc_data = r->req_rpc_data) == NULL) { + nxt_debug(task, " req_rpc_data is NULL"); + goto close_handler; + } + + if ((req_app_link = req_rpc_data->req_app_link) == NULL) { + nxt_debug(task, " req_app_link is NULL"); + goto close_handler; + } + + if (req_app_link->app_port == NULL) { + nxt_debug(task, " app_port is NULL"); + goto close_handler; + } + + (void) nxt_port_socket_twrite(task, req_app_link->app_port, + NXT_PORT_MSG_WEBSOCKET_LAST, + -1, req_app_link->stream, + req_app_link->reply_port->id, NULL, NULL); + +close_handler: + + nxt_http_request_close_handler(task, obj, data); +} diff --git a/src/nxt_port.c b/src/nxt_port.c index aff46666..cef65cab 100644 --- a/src/nxt_port.c +++ b/src/nxt_port.c @@ -68,6 +68,7 @@ nxt_port_new(nxt_task_t *task, nxt_port_id_t id, nxt_pid_t pid, nxt_queue_init(&port->messages); nxt_thread_mutex_create(&port->write_mutex); nxt_queue_init(&port->pending_requests); + nxt_queue_init(&port->active_websockets); } else { nxt_mp_destroy(mp); diff --git a/src/nxt_port.h b/src/nxt_port.h index e4e76693..eeb6caa5 100644 --- a/src/nxt_port.h +++ b/src/nxt_port.h @@ -36,6 +36,12 @@ struct nxt_port_handlers_s { /* Stop process command. */ nxt_port_handler_t quit; + /* Request headers. */ + nxt_port_handler_t req_headers; + + /* Websocket frame. */ + nxt_port_handler_t websocket_frame; + /* Various data. */ nxt_port_handler_t data; }; @@ -71,6 +77,9 @@ typedef enum { _NXT_PORT_MSG_REMOVE_PID = nxt_port_handler_idx(remove_pid), _NXT_PORT_MSG_QUIT = nxt_port_handler_idx(quit), + _NXT_PORT_MSG_REQ_HEADERS = nxt_port_handler_idx(req_headers), + _NXT_PORT_MSG_WEBSOCKET = nxt_port_handler_idx(websocket_frame), + _NXT_PORT_MSG_DATA = nxt_port_handler_idx(data), NXT_PORT_MSG_MAX = sizeof(nxt_port_handlers_t) @@ -99,6 +108,10 @@ typedef enum { NXT_PORT_MSG_QUIT = _NXT_PORT_MSG_QUIT | NXT_PORT_MSG_LAST, NXT_PORT_MSG_REMOVE_PID = _NXT_PORT_MSG_REMOVE_PID | NXT_PORT_MSG_LAST, + NXT_PORT_MSG_REQ_HEADERS = _NXT_PORT_MSG_REQ_HEADERS, + NXT_PORT_MSG_WEBSOCKET = _NXT_PORT_MSG_WEBSOCKET, + NXT_PORT_MSG_WEBSOCKET_LAST = _NXT_PORT_MSG_WEBSOCKET | NXT_PORT_MSG_LAST, + NXT_PORT_MSG_DATA = _NXT_PORT_MSG_DATA, NXT_PORT_MSG_DATA_LAST = _NXT_PORT_MSG_DATA | NXT_PORT_MSG_LAST, } nxt_port_msg_type_t; @@ -181,6 +194,8 @@ struct nxt_port_s { uint32_t app_responses; nxt_queue_t pending_requests; + nxt_queue_t active_websockets; + nxt_port_handler_t handler; nxt_port_handler_t *data; diff --git a/src/nxt_router.c b/src/nxt_router.c index 566e0c65..b87f588f 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -14,7 +14,7 @@ #include #include #include - +#include typedef struct { nxt_str_t type; @@ -48,64 +48,6 @@ typedef struct { #endif -typedef struct nxt_msg_info_s { - nxt_buf_t *buf; - nxt_port_mmap_tracking_t tracking; - nxt_work_handler_t completion_handler; -} nxt_msg_info_t; - - -typedef struct nxt_request_app_link_s nxt_request_app_link_t; - - -typedef enum { - NXT_APR_NEW_PORT, - NXT_APR_REQUEST_FAILED, - NXT_APR_GOT_RESPONSE, - NXT_APR_CLOSE, -} nxt_apr_action_t; - - -typedef struct { - uint32_t stream; - nxt_app_t *app; - - nxt_port_t *app_port; - nxt_apr_action_t apr_action; - - nxt_http_request_t *request; - nxt_msg_info_t msg_info; - nxt_request_app_link_t *req_app_link; -} nxt_request_rpc_data_t; - - -struct nxt_request_app_link_s { - uint32_t stream; - nxt_atomic_t use_count; - - nxt_port_t *app_port; - nxt_apr_action_t apr_action; - - nxt_port_t *reply_port; - nxt_http_request_t *request; - nxt_msg_info_t msg_info; - nxt_request_rpc_data_t *req_rpc_data; - - nxt_nsec_t res_time; - - nxt_queue_link_t link_app_requests; /* for nxt_app_t.requests */ - /* for nxt_port_t.pending_requests */ - nxt_queue_link_t link_port_pending; - nxt_queue_link_t link_app_pending; /* for nxt_app_t.pending */ - - nxt_mp_t *mem_pool; - nxt_work_t work; - - int err_code; - const char *err_str; -}; - - typedef struct { nxt_socket_conf_t *socket_conf; nxt_router_temp_conf_t *temp_conf; @@ -305,6 +247,8 @@ static nxt_int_t nxt_router_http_request_done(nxt_task_t *task, static void nxt_router_http_request_release(nxt_task_t *task, void *obj, void *data); +const nxt_http_request_state_t nxt_http_websocket; + static nxt_router_t *nxt_router; static const nxt_str_t http_prefix = nxt_string("HTTP_"); @@ -663,6 +607,7 @@ nxt_request_app_link_release(nxt_task_t *task, nxt_request_app_link_t *req_app_link) { nxt_mp_t *mp; + nxt_http_request_t *r; nxt_request_rpc_data_t *req_rpc_data; nxt_assert(task->thread->engine == req_app_link->work.data); @@ -683,10 +628,11 @@ nxt_request_app_link_release(nxt_task_t *task, req_rpc_data->msg_info = req_app_link->msg_info; if (req_rpc_data->app->timeout != 0) { - req_rpc_data->request->timer.handler = nxt_router_app_timeout; - req_rpc_data->request->timer_data = req_rpc_data; - nxt_timer_add(task->thread->engine, - &req_rpc_data->request->timer, + r = req_rpc_data->request; + + r->timer.handler = nxt_router_app_timeout; + r->timer_data = req_rpc_data; + nxt_timer_add(task->thread->engine, &r->timer, req_rpc_data->app->timeout); } @@ -833,14 +779,16 @@ nxt_request_rpc_data_unlink(nxt_task_t *task, if (req_app_link->link_app_requests.next == NULL && req_app_link->link_port_pending.next == NULL - && req_app_link->link_app_pending.next == NULL) + && req_app_link->link_app_pending.next == NULL + && req_app_link->link_port_websockets.next == NULL) { req_app_link = NULL; } else { ra_use_delta -= nxt_queue_chk_remove(&req_app_link->link_app_requests) - + nxt_queue_chk_remove(&req_app_link->link_port_pending); + + nxt_queue_chk_remove(&req_app_link->link_port_pending) + + nxt_queue_chk_remove(&req_app_link->link_port_websockets); nxt_queue_chk_remove(&req_app_link->link_app_pending); } @@ -863,6 +811,7 @@ nxt_request_rpc_data_unlink(nxt_task_t *task, nxt_router_http_request_done(task, req_rpc_data->request); + req_rpc_data->request->req_rpc_data = NULL; req_rpc_data->request = NULL; } } @@ -1412,6 +1361,28 @@ static nxt_conf_map_t nxt_router_http_conf[] = { }; +static nxt_conf_map_t nxt_router_websocket_conf[] = { + { + nxt_string("max_frame_size"), + NXT_CONF_MAP_SIZE, + offsetof(nxt_websocket_conf_t, max_frame_size), + }, + + { + nxt_string("read_timeout"), + NXT_CONF_MAP_MSEC, + offsetof(nxt_websocket_conf_t, read_timeout), + }, + + { + nxt_string("keepalive_interval"), + NXT_CONF_MAP_MSEC, + offsetof(nxt_websocket_conf_t, keepalive_interval), + }, + +}; + + static nxt_int_t nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, u_char *start, u_char *end) @@ -1425,7 +1396,7 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_app_t *app, *prev; nxt_router_t *router; nxt_app_joint_t *app_joint; - nxt_conf_value_t *conf, *http, *value; + nxt_conf_value_t *conf, *http, *value, *websocket; nxt_conf_value_t *applications, *application; nxt_conf_value_t *listeners, *listener; nxt_conf_value_t *routes_conf; @@ -1448,6 +1419,7 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, #if (NXT_TLS) static nxt_str_t certificate_path = nxt_string("/tls/certificate"); #endif + static nxt_str_t websocket_path = nxt_string("/settings/http/websocket"); conf = nxt_conf_json_parse(tmcf->mem_pool, start, end, NULL); if (conf == NULL) { @@ -1658,6 +1630,8 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, } #endif + websocket = nxt_conf_get_path(conf, &websocket_path); + listeners = nxt_conf_get_path(conf, &listeners_path); if (listeners != NULL) { @@ -1697,6 +1671,10 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, skcf->body_read_timeout = 30 * 1000; skcf->send_timeout = 30 * 1000; + skcf->websocket_conf.max_frame_size = 1024 * 1024; + skcf->websocket_conf.read_timeout = 60 * 1000; + skcf->websocket_conf.keepalive_interval = 30 * 1000; + if (http != NULL) { ret = nxt_conf_map_object(mp, http, nxt_router_http_conf, nxt_nitems(nxt_router_http_conf), @@ -1707,6 +1685,17 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, } } + if (websocket != NULL) { + ret = nxt_conf_map_object(mp, websocket, + nxt_router_websocket_conf, + nxt_nitems(nxt_router_websocket_conf), + &skcf->websocket_conf); + if (ret != NXT_OK) { + nxt_alert(task, "websocket map error"); + goto fail; + } + } + #if (NXT_TLS) value = nxt_conf_get_path(listener, &certificate_path); @@ -3418,10 +3407,12 @@ nxt_router_response_ready_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, { nxt_int_t ret; nxt_buf_t *b; + nxt_port_t *app_port; nxt_unit_field_t *f; nxt_http_field_t *field; nxt_http_request_t *r; nxt_unit_response_t *resp; + nxt_request_app_link_t *req_app_link; nxt_request_rpc_data_t *req_rpc_data; b = msg->buf; @@ -3542,7 +3533,48 @@ nxt_router_response_ready_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, nxt_http_request_header_send(task, r); - r->state = &nxt_http_request_send_state; + if (r->websocket_handshake + && r->status == NXT_HTTP_SWITCHING_PROTOCOLS) + { + req_app_link = nxt_request_app_link_alloc(task, + req_rpc_data->req_app_link, + req_rpc_data); + if (nxt_slow_path(req_app_link == NULL)) { + goto fail; + } + + app_port = req_app_link->app_port; + + if (app_port == NULL && req_rpc_data->app_port != NULL) { + req_app_link->app_port = req_rpc_data->app_port; + app_port = req_app_link->app_port; + req_app_link->apr_action = req_rpc_data->apr_action; + + req_rpc_data->app_port = NULL; + } + + if (nxt_slow_path(app_port == NULL)) { + goto fail; + } + + nxt_thread_mutex_lock(&req_rpc_data->app->mutex); + + nxt_queue_insert_tail(&app_port->active_websockets, + &req_app_link->link_port_websockets); + + nxt_thread_mutex_unlock(&req_rpc_data->app->mutex); + + nxt_router_app_port_release(task, app_port, NXT_APR_UPGRADE); + req_app_link->apr_action = NXT_APR_CLOSE; + + nxt_debug(task, "req_app_link stream #%uD upgrade", + req_app_link->stream); + + r->state = &nxt_http_websocket; + + } else { + r->state = &nxt_http_request_send_state; + } if (r->out) { nxt_work_queue_add(&task->thread->engine->fast_work_queue, @@ -3924,6 +3956,10 @@ nxt_router_app_port_release(nxt_task_t *task, nxt_port_t *port, got_response = 1; inc_use = -1; break; + case NXT_APR_UPGRADE: + dec_pending = 1; + got_response = 1; + break; case NXT_APR_CLOSE: inc_use = -1; break; @@ -4046,9 +4082,10 @@ re_ra_cancelled: adjust_idle_timer = 0; - if (port->pair[1] != -1 && !send_quit && port->app_pending_responses == 0) { - nxt_assert(port->idle_link.next == NULL); - + if (port->pair[1] != -1 && !send_quit && port->app_pending_responses == 0 + && nxt_queue_is_empty(&port->active_websockets) + && port->idle_link.next == NULL) + { if (app->idle_processes == app->spare_processes && app->adjust_idle_work.data == NULL) { @@ -4545,6 +4582,7 @@ nxt_router_process_http_request(nxt_task_t *task, nxt_http_request_t *r, nxt_router_app_use(task, app, 1); req_rpc_data->request = r; + r->req_rpc_data = req_rpc_data; req_app_link = &ra_local; nxt_request_app_link_init(task, req_app_link, req_rpc_data); @@ -4635,7 +4673,7 @@ nxt_router_app_prepare_request(nxt_task_t *task, goto release_port; } - res = nxt_port_socket_twrite(task, port, NXT_PORT_MSG_DATA, + res = nxt_port_socket_twrite(task, port, NXT_PORT_MSG_REQ_HEADERS, -1, req_app_link->stream, reply_port->id, buf, &req_app_link->msg_info.tracking); @@ -4785,6 +4823,7 @@ nxt_router_prepare_msg(nxt_task_t *task, nxt_http_request_t *r, *p++ = '\0'; req->tls = (r->tls != NULL); + req->websocket_handshake = r->websocket_handshake; req->server_name_length = r->server_name.length; nxt_unit_sptr_set(&req->server_name, p); diff --git a/src/nxt_router.h b/src/nxt_router.h index ff791e3d..b55a4de3 100644 --- a/src/nxt_router.h +++ b/src/nxt_router.h @@ -138,6 +138,13 @@ struct nxt_app_s { }; +typedef struct { + size_t max_frame_size; + nxt_msec_t read_timeout; + nxt_msec_t keepalive_interval; +} nxt_websocket_conf_t; + + typedef struct { uint32_t count; nxt_queue_link_t link; @@ -164,6 +171,8 @@ typedef struct { nxt_msec_t body_read_timeout; nxt_msec_t send_timeout; + nxt_websocket_conf_t websocket_conf; + #if (NXT_TLS) nxt_tls_conf_t *tls; #endif diff --git a/src/nxt_router_request.h b/src/nxt_router_request.h new file mode 100644 index 00000000..c3d5767e --- /dev/null +++ b/src/nxt_router_request.h @@ -0,0 +1,71 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NXT_ROUTER_REQUEST_H_INCLUDED_ +#define _NXT_ROUTER_REQUEST_H_INCLUDED_ + + +typedef struct nxt_msg_info_s { + nxt_buf_t *buf; + nxt_port_mmap_tracking_t tracking; + nxt_work_handler_t completion_handler; +} nxt_msg_info_t; + + +typedef struct nxt_request_app_link_s nxt_request_app_link_t; + + +typedef enum { + NXT_APR_NEW_PORT, + NXT_APR_REQUEST_FAILED, + NXT_APR_GOT_RESPONSE, + NXT_APR_UPGRADE, + NXT_APR_CLOSE, +} nxt_apr_action_t; + + +typedef struct { + uint32_t stream; + nxt_app_t *app; + + nxt_port_t *app_port; + nxt_apr_action_t apr_action; + + nxt_http_request_t *request; + nxt_msg_info_t msg_info; + nxt_request_app_link_t *req_app_link; +} nxt_request_rpc_data_t; + + +struct nxt_request_app_link_s { + uint32_t stream; + nxt_atomic_t use_count; + + nxt_port_t *app_port; + nxt_apr_action_t apr_action; + + nxt_port_t *reply_port; + nxt_http_request_t *request; + nxt_msg_info_t msg_info; + nxt_request_rpc_data_t *req_rpc_data; + + nxt_nsec_t res_time; + + nxt_queue_link_t link_app_requests; /* for nxt_app_t.requests */ + /* for nxt_port_t.pending_requests */ + nxt_queue_link_t link_port_pending; + nxt_queue_link_t link_app_pending; /* for nxt_app_t.pending */ + /* for nxt_port_t.active_websockets */ + nxt_queue_link_t link_port_websockets; + + nxt_mp_t *mem_pool; + nxt_work_t work; + + int err_code; + const char *err_str; +}; + + +#endif /* _NXT_ROUTER_REQUEST_H_INCLUDED_ */ diff --git a/src/nxt_sha1.c b/src/nxt_sha1.c new file mode 100644 index 00000000..407c5933 --- /dev/null +++ b/src/nxt_sha1.c @@ -0,0 +1,295 @@ + +/* + * Copyright (C) Maxim Dounin + * Copyright (C) NGINX, Inc. + * + * An internal SHA1 implementation. + */ + + +#include +#include + + +static const u_char *nxt_sha1_body(nxt_sha1_t *ctx, const u_char *data, + size_t size); + + +void +nxt_sha1_init(nxt_sha1_t *ctx) +{ + ctx->a = 0x67452301; + ctx->b = 0xefcdab89; + ctx->c = 0x98badcfe; + ctx->d = 0x10325476; + ctx->e = 0xc3d2e1f0; + + ctx->bytes = 0; +} + + +void +nxt_sha1_update(nxt_sha1_t *ctx, const void *data, size_t size) +{ + size_t used, free; + + used = (size_t) (ctx->bytes & 0x3f); + ctx->bytes += size; + + if (used) { + free = 64 - used; + + if (size < free) { + memcpy(&ctx->buffer[used], data, size); + return; + } + + memcpy(&ctx->buffer[used], data, free); + data = (u_char *) data + free; + size -= free; + (void) nxt_sha1_body(ctx, ctx->buffer, 64); + } + + if (size >= 64) { + data = nxt_sha1_body(ctx, data, size & ~(size_t) 0x3f); + size &= 0x3f; + } + + memcpy(ctx->buffer, data, size); +} + + +void +nxt_sha1_final(u_char result[20], nxt_sha1_t *ctx) +{ + size_t used, free; + + used = (size_t) (ctx->bytes & 0x3f); + + ctx->buffer[used++] = 0x80; + + free = 64 - used; + + if (free < 8) { + nxt_memzero(&ctx->buffer[used], free); + (void) nxt_sha1_body(ctx, ctx->buffer, 64); + used = 0; + free = 64; + } + + nxt_memzero(&ctx->buffer[used], free - 8); + + ctx->bytes <<= 3; + ctx->buffer[56] = (u_char) (ctx->bytes >> 56); + ctx->buffer[57] = (u_char) (ctx->bytes >> 48); + ctx->buffer[58] = (u_char) (ctx->bytes >> 40); + ctx->buffer[59] = (u_char) (ctx->bytes >> 32); + ctx->buffer[60] = (u_char) (ctx->bytes >> 24); + ctx->buffer[61] = (u_char) (ctx->bytes >> 16); + ctx->buffer[62] = (u_char) (ctx->bytes >> 8); + ctx->buffer[63] = (u_char) ctx->bytes; + + (void) nxt_sha1_body(ctx, ctx->buffer, 64); + + result[0] = (u_char) (ctx->a >> 24); + result[1] = (u_char) (ctx->a >> 16); + result[2] = (u_char) (ctx->a >> 8); + result[3] = (u_char) ctx->a; + result[4] = (u_char) (ctx->b >> 24); + result[5] = (u_char) (ctx->b >> 16); + result[6] = (u_char) (ctx->b >> 8); + result[7] = (u_char) ctx->b; + result[8] = (u_char) (ctx->c >> 24); + result[9] = (u_char) (ctx->c >> 16); + result[10] = (u_char) (ctx->c >> 8); + result[11] = (u_char) ctx->c; + result[12] = (u_char) (ctx->d >> 24); + result[13] = (u_char) (ctx->d >> 16); + result[14] = (u_char) (ctx->d >> 8); + result[15] = (u_char) ctx->d; + result[16] = (u_char) (ctx->e >> 24); + result[17] = (u_char) (ctx->e >> 16); + result[18] = (u_char) (ctx->e >> 8); + result[19] = (u_char) ctx->e; + + nxt_memzero(ctx, sizeof(*ctx)); +} + + +/* + * Helper functions. + */ + +#define ROTATE(bits, word) (((word) << (bits)) | ((word) >> (32 - (bits)))) + +#define F1(b, c, d) (((b) & (c)) | ((~(b)) & (d))) +#define F2(b, c, d) ((b) ^ (c) ^ (d)) +#define F3(b, c, d) (((b) & (c)) | ((b) & (d)) | ((c) & (d))) + +#define STEP(f, a, b, c, d, e, w, t) \ + temp = ROTATE(5, (a)) + f((b), (c), (d)) + (e) + (w) + (t); \ + (e) = (d); \ + (d) = (c); \ + (c) = ROTATE(30, (b)); \ + (b) = (a); \ + (a) = temp; + + +/* + * GET() reads 4 input bytes in big-endian byte order and returns + * them as uint32_t. + */ + +#define GET(n) \ + ( ((uint32_t) p[n * 4 + 3]) \ + | ((uint32_t) p[n * 4 + 2] << 8) \ + | ((uint32_t) p[n * 4 + 1] << 16) \ + | ((uint32_t) p[n * 4] << 24)) + + +/* + * This processes one or more 64-byte data blocks, but does not update + * the bit counters. There are no alignment requirements. + */ + +static const u_char * +nxt_sha1_body(nxt_sha1_t *ctx, const u_char *data, size_t size) +{ + uint32_t a, b, c, d, e, temp; + uint32_t saved_a, saved_b, saved_c, saved_d, saved_e; + uint32_t words[80]; + nxt_uint_t i; + const u_char *p; + + p = data; + + a = ctx->a; + b = ctx->b; + c = ctx->c; + d = ctx->d; + e = ctx->e; + + do { + saved_a = a; + saved_b = b; + saved_c = c; + saved_d = d; + saved_e = e; + + /* Load data block into the words array */ + + for (i = 0; i < 16; i++) { + words[i] = GET(i); + } + + for (i = 16; i < 80; i++) { + words[i] = ROTATE(1, words[i - 3] + ^ words[i - 8] + ^ words[i - 14] + ^ words[i - 16]); + } + + /* Transformations */ + + STEP(F1, a, b, c, d, e, words[0], 0x5a827999); + STEP(F1, a, b, c, d, e, words[1], 0x5a827999); + STEP(F1, a, b, c, d, e, words[2], 0x5a827999); + STEP(F1, a, b, c, d, e, words[3], 0x5a827999); + STEP(F1, a, b, c, d, e, words[4], 0x5a827999); + STEP(F1, a, b, c, d, e, words[5], 0x5a827999); + STEP(F1, a, b, c, d, e, words[6], 0x5a827999); + STEP(F1, a, b, c, d, e, words[7], 0x5a827999); + STEP(F1, a, b, c, d, e, words[8], 0x5a827999); + STEP(F1, a, b, c, d, e, words[9], 0x5a827999); + STEP(F1, a, b, c, d, e, words[10], 0x5a827999); + STEP(F1, a, b, c, d, e, words[11], 0x5a827999); + STEP(F1, a, b, c, d, e, words[12], 0x5a827999); + STEP(F1, a, b, c, d, e, words[13], 0x5a827999); + STEP(F1, a, b, c, d, e, words[14], 0x5a827999); + STEP(F1, a, b, c, d, e, words[15], 0x5a827999); + STEP(F1, a, b, c, d, e, words[16], 0x5a827999); + STEP(F1, a, b, c, d, e, words[17], 0x5a827999); + STEP(F1, a, b, c, d, e, words[18], 0x5a827999); + STEP(F1, a, b, c, d, e, words[19], 0x5a827999); + + STEP(F2, a, b, c, d, e, words[20], 0x6ed9eba1); + STEP(F2, a, b, c, d, e, words[21], 0x6ed9eba1); + STEP(F2, a, b, c, d, e, words[22], 0x6ed9eba1); + STEP(F2, a, b, c, d, e, words[23], 0x6ed9eba1); + STEP(F2, a, b, c, d, e, words[24], 0x6ed9eba1); + STEP(F2, a, b, c, d, e, words[25], 0x6ed9eba1); + STEP(F2, a, b, c, d, e, words[26], 0x6ed9eba1); + STEP(F2, a, b, c, d, e, words[27], 0x6ed9eba1); + STEP(F2, a, b, c, d, e, words[28], 0x6ed9eba1); + STEP(F2, a, b, c, d, e, words[29], 0x6ed9eba1); + STEP(F2, a, b, c, d, e, words[30], 0x6ed9eba1); + STEP(F2, a, b, c, d, e, words[31], 0x6ed9eba1); + STEP(F2, a, b, c, d, e, words[32], 0x6ed9eba1); + STEP(F2, a, b, c, d, e, words[33], 0x6ed9eba1); + STEP(F2, a, b, c, d, e, words[34], 0x6ed9eba1); + STEP(F2, a, b, c, d, e, words[35], 0x6ed9eba1); + STEP(F2, a, b, c, d, e, words[36], 0x6ed9eba1); + STEP(F2, a, b, c, d, e, words[37], 0x6ed9eba1); + STEP(F2, a, b, c, d, e, words[38], 0x6ed9eba1); + STEP(F2, a, b, c, d, e, words[39], 0x6ed9eba1); + + STEP(F3, a, b, c, d, e, words[40], 0x8f1bbcdc); + STEP(F3, a, b, c, d, e, words[41], 0x8f1bbcdc); + STEP(F3, a, b, c, d, e, words[42], 0x8f1bbcdc); + STEP(F3, a, b, c, d, e, words[43], 0x8f1bbcdc); + STEP(F3, a, b, c, d, e, words[44], 0x8f1bbcdc); + STEP(F3, a, b, c, d, e, words[45], 0x8f1bbcdc); + STEP(F3, a, b, c, d, e, words[46], 0x8f1bbcdc); + STEP(F3, a, b, c, d, e, words[47], 0x8f1bbcdc); + STEP(F3, a, b, c, d, e, words[48], 0x8f1bbcdc); + STEP(F3, a, b, c, d, e, words[49], 0x8f1bbcdc); + STEP(F3, a, b, c, d, e, words[50], 0x8f1bbcdc); + STEP(F3, a, b, c, d, e, words[51], 0x8f1bbcdc); + STEP(F3, a, b, c, d, e, words[52], 0x8f1bbcdc); + STEP(F3, a, b, c, d, e, words[53], 0x8f1bbcdc); + STEP(F3, a, b, c, d, e, words[54], 0x8f1bbcdc); + STEP(F3, a, b, c, d, e, words[55], 0x8f1bbcdc); + STEP(F3, a, b, c, d, e, words[56], 0x8f1bbcdc); + STEP(F3, a, b, c, d, e, words[57], 0x8f1bbcdc); + STEP(F3, a, b, c, d, e, words[58], 0x8f1bbcdc); + STEP(F3, a, b, c, d, e, words[59], 0x8f1bbcdc); + + STEP(F2, a, b, c, d, e, words[60], 0xca62c1d6); + STEP(F2, a, b, c, d, e, words[61], 0xca62c1d6); + STEP(F2, a, b, c, d, e, words[62], 0xca62c1d6); + STEP(F2, a, b, c, d, e, words[63], 0xca62c1d6); + STEP(F2, a, b, c, d, e, words[64], 0xca62c1d6); + STEP(F2, a, b, c, d, e, words[65], 0xca62c1d6); + STEP(F2, a, b, c, d, e, words[66], 0xca62c1d6); + STEP(F2, a, b, c, d, e, words[67], 0xca62c1d6); + STEP(F2, a, b, c, d, e, words[68], 0xca62c1d6); + STEP(F2, a, b, c, d, e, words[69], 0xca62c1d6); + STEP(F2, a, b, c, d, e, words[70], 0xca62c1d6); + STEP(F2, a, b, c, d, e, words[71], 0xca62c1d6); + STEP(F2, a, b, c, d, e, words[72], 0xca62c1d6); + STEP(F2, a, b, c, d, e, words[73], 0xca62c1d6); + STEP(F2, a, b, c, d, e, words[74], 0xca62c1d6); + STEP(F2, a, b, c, d, e, words[75], 0xca62c1d6); + STEP(F2, a, b, c, d, e, words[76], 0xca62c1d6); + STEP(F2, a, b, c, d, e, words[77], 0xca62c1d6); + STEP(F2, a, b, c, d, e, words[78], 0xca62c1d6); + STEP(F2, a, b, c, d, e, words[79], 0xca62c1d6); + + a += saved_a; + b += saved_b; + c += saved_c; + d += saved_d; + e += saved_e; + + p += 64; + + } while (size -= 64); + + ctx->a = a; + ctx->b = b; + ctx->c = c; + ctx->d = d; + ctx->e = e; + + return p; +} diff --git a/src/nxt_sha1.h b/src/nxt_sha1.h new file mode 100644 index 00000000..2816982b --- /dev/null +++ b/src/nxt_sha1.h @@ -0,0 +1,24 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + + +#ifndef _NXT_SHA1_H_INCLUDED_ +#define _NXT_SHA1_H_INCLUDED_ + + +typedef struct { + uint64_t bytes; + uint32_t a, b, c, d, e; + u_char buffer[64]; +} nxt_sha1_t; + + +NXT_EXPORT void nxt_sha1_init(nxt_sha1_t *ctx); +NXT_EXPORT void nxt_sha1_update(nxt_sha1_t *ctx, const void *data, size_t size); +NXT_EXPORT void nxt_sha1_final(u_char result[20], nxt_sha1_t *ctx); + + +#endif /* _NXT_SHA1_H_INCLUDED_ */ diff --git a/src/nxt_unit.c b/src/nxt_unit.c index 88c7fa6a..28a0de20 100644 --- a/src/nxt_unit.c +++ b/src/nxt_unit.c @@ -11,38 +11,60 @@ #include "nxt_unit.h" #include "nxt_unit_request.h" #include "nxt_unit_response.h" +#include "nxt_unit_websocket.h" + +#include "nxt_websocket.h" #if (NXT_HAVE_MEMFD_CREATE) #include #endif -typedef struct nxt_unit_impl_s nxt_unit_impl_t; -typedef struct nxt_unit_mmap_s nxt_unit_mmap_t; -typedef struct nxt_unit_mmaps_s nxt_unit_mmaps_t; -typedef struct nxt_unit_process_s nxt_unit_process_t; -typedef struct nxt_unit_mmap_buf_s nxt_unit_mmap_buf_t; -typedef struct nxt_unit_recv_msg_s nxt_unit_recv_msg_t; -typedef struct nxt_unit_ctx_impl_s nxt_unit_ctx_impl_t; -typedef struct nxt_unit_port_impl_s nxt_unit_port_impl_t; -typedef struct nxt_unit_request_info_impl_s nxt_unit_request_info_impl_t; +typedef struct nxt_unit_impl_s nxt_unit_impl_t; +typedef struct nxt_unit_mmap_s nxt_unit_mmap_t; +typedef struct nxt_unit_mmaps_s nxt_unit_mmaps_t; +typedef struct nxt_unit_process_s nxt_unit_process_t; +typedef struct nxt_unit_mmap_buf_s nxt_unit_mmap_buf_t; +typedef struct nxt_unit_recv_msg_s nxt_unit_recv_msg_t; +typedef struct nxt_unit_ctx_impl_s nxt_unit_ctx_impl_t; +typedef struct nxt_unit_port_impl_s nxt_unit_port_impl_t; +typedef struct nxt_unit_request_info_impl_s nxt_unit_request_info_impl_t; +typedef struct nxt_unit_websocket_frame_impl_s nxt_unit_websocket_frame_impl_t; static nxt_unit_impl_t *nxt_unit_create(nxt_unit_init_t *init); static void nxt_unit_ctx_init(nxt_unit_impl_t *lib, nxt_unit_ctx_impl_t *ctx_impl, void *data); +nxt_inline void nxt_unit_mmap_buf_insert(nxt_unit_mmap_buf_t **head, + nxt_unit_mmap_buf_t *mmap_buf); +nxt_inline void nxt_unit_mmap_buf_insert_tail(nxt_unit_mmap_buf_t **prev, + nxt_unit_mmap_buf_t *mmap_buf); +nxt_inline void nxt_unit_mmap_buf_remove(nxt_unit_mmap_buf_t *mmap_buf); static int nxt_unit_read_env(nxt_unit_port_t *ready_port, nxt_unit_port_t *read_port, int *log_fd, uint32_t *stream); static int nxt_unit_ready(nxt_unit_ctx_t *ctx, nxt_unit_port_id_t *port_id, uint32_t stream); +static int nxt_unit_process_new_port(nxt_unit_ctx_t *ctx, + nxt_unit_recv_msg_t *recv_msg); +static int nxt_unit_process_req_headers(nxt_unit_ctx_t *ctx, + nxt_unit_recv_msg_t *recv_msg); +static int nxt_unit_process_websocket(nxt_unit_ctx_t *ctx, + nxt_unit_recv_msg_t *recv_msg); static nxt_unit_request_info_impl_t *nxt_unit_request_info_get( nxt_unit_ctx_t *ctx); static void nxt_unit_request_info_release(nxt_unit_request_info_t *req); static void nxt_unit_request_info_free(nxt_unit_request_info_impl_t *req); +static nxt_unit_websocket_frame_impl_t *nxt_unit_websocket_frame_get( + nxt_unit_ctx_t *ctx); +static void nxt_unit_websocket_frame_release(nxt_unit_websocket_frame_t *ws); +static void nxt_unit_websocket_frame_free(nxt_unit_websocket_frame_impl_t *ws); static nxt_unit_process_t *nxt_unit_msg_get_process(nxt_unit_ctx_t *ctx, nxt_unit_recv_msg_t *recv_msg); static nxt_unit_mmap_buf_t *nxt_unit_mmap_buf_get(nxt_unit_ctx_t *ctx); static void nxt_unit_mmap_buf_release(nxt_unit_mmap_buf_t *mmap_buf); static int nxt_unit_mmap_buf_send(nxt_unit_ctx_t *ctx, uint32_t stream, nxt_unit_mmap_buf_t *mmap_buf, int last); +static void nxt_unit_mmap_buf_free(nxt_unit_mmap_buf_t *mmap_buf); +static ssize_t nxt_unit_buf_read(nxt_unit_buf_t **b, uint64_t *len, void *dst, + size_t size); static nxt_port_mmap_header_t *nxt_unit_mmap_get(nxt_unit_ctx_t *ctx, nxt_unit_process_t *process, nxt_unit_port_id_t *port_id, nxt_chunk_id_t *c, int n); @@ -65,7 +87,7 @@ static nxt_port_mmap_header_t *nxt_unit_get_incoming_mmap(nxt_unit_ctx_t *ctx, static int nxt_unit_tracking_read(nxt_unit_ctx_t *ctx, nxt_unit_recv_msg_t *recv_msg); static int nxt_unit_mmap_read(nxt_unit_ctx_t *ctx, - nxt_unit_recv_msg_t *recv_msg, nxt_queue_t *incoming_buf); + nxt_unit_recv_msg_t *recv_msg); static int nxt_unit_mmap_release(nxt_port_mmap_header_t *hdr, void *start, uint32_t size); @@ -98,14 +120,22 @@ static int nxt_unit_port_hash_add(nxt_lvlhsh_t *port_hash, static nxt_unit_port_impl_t *nxt_unit_port_hash_find(nxt_lvlhsh_t *port_hash, nxt_unit_port_id_t *port_id, int remove); +static int nxt_unit_request_hash_add(nxt_lvlhsh_t *request_hash, + nxt_unit_request_info_impl_t *req_impl); +static nxt_unit_request_info_impl_t *nxt_unit_request_hash_find( + nxt_lvlhsh_t *request_hash, uint32_t stream, int remove); + static char * nxt_unit_snprint_prefix(char *p, char *end, pid_t pid, int level); struct nxt_unit_mmap_buf_s { nxt_unit_buf_t buf; + nxt_unit_mmap_buf_t *next; + nxt_unit_mmap_buf_t **prev; + nxt_port_mmap_header_t *hdr; - nxt_queue_link_t link; +// nxt_queue_link_t link; nxt_unit_port_id_t port_id; nxt_unit_request_info_t *req; nxt_unit_ctx_impl_t *ctx_impl; @@ -113,12 +143,20 @@ struct nxt_unit_mmap_buf_s { struct nxt_unit_recv_msg_s { - nxt_port_msg_t port_msg; + uint32_t stream; + nxt_pid_t pid; + nxt_port_id_t reply_port; + + uint8_t last; /* 1 bit */ + uint8_t mmap; /* 1 bit */ void *start; uint32_t size; + int fd; nxt_unit_process_t *process; + + nxt_unit_mmap_buf_t *incoming_buf; }; @@ -127,18 +165,22 @@ typedef enum { NXT_UNIT_RS_RESPONSE_INIT, NXT_UNIT_RS_RESPONSE_HAS_CONTENT, NXT_UNIT_RS_RESPONSE_SENT, - NXT_UNIT_RS_DONE, + NXT_UNIT_RS_RELEASED, } nxt_unit_req_state_t; struct nxt_unit_request_info_impl_s { nxt_unit_request_info_t req; - nxt_unit_recv_msg_t recv_msg; - nxt_queue_t outgoing_buf; /* of nxt_unit_mmap_buf_t */ - nxt_queue_t incoming_buf; /* of nxt_unit_mmap_buf_t */ + uint32_t stream; + + nxt_unit_process_t *process; + + nxt_unit_mmap_buf_t *outgoing_buf; + nxt_unit_mmap_buf_t *incoming_buf; nxt_unit_req_state_t state; + uint8_t websocket; nxt_queue_link_t link; @@ -146,6 +188,19 @@ struct nxt_unit_request_info_impl_s { }; +struct nxt_unit_websocket_frame_impl_s { + nxt_unit_websocket_frame_t ws; + + nxt_unit_mmap_buf_t *buf; + + nxt_queue_link_t link; + + nxt_unit_ctx_impl_t *ctx_impl; + + void *retain_buf; +}; + + struct nxt_unit_ctx_impl_s { nxt_unit_ctx_t ctx; @@ -154,14 +209,20 @@ struct nxt_unit_ctx_impl_s { nxt_queue_link_t link; - nxt_queue_t free_buf; /* of nxt_unit_mmap_buf_t */ + nxt_unit_mmap_buf_t *free_buf; /* of nxt_unit_request_info_impl_t */ nxt_queue_t free_req; + /* of nxt_unit_websocket_frame_impl_t */ + nxt_queue_t free_ws; + /* of nxt_unit_request_info_impl_t */ nxt_queue_t active_req; + /* of nxt_unit_request_info_impl_t */ + nxt_lvlhsh_t requests; + nxt_unit_mmap_buf_t ctx_buf[2]; nxt_unit_request_info_impl_t req; @@ -394,18 +455,65 @@ nxt_unit_ctx_init(nxt_unit_impl_t *lib, nxt_unit_ctx_impl_t *ctx_impl, nxt_queue_insert_tail(&lib->contexts, &ctx_impl->link); - nxt_queue_init(&ctx_impl->free_buf); nxt_queue_init(&ctx_impl->free_req); + nxt_queue_init(&ctx_impl->free_ws); nxt_queue_init(&ctx_impl->active_req); - nxt_queue_insert_tail(&ctx_impl->free_buf, &ctx_impl->ctx_buf[0].link); - nxt_queue_insert_tail(&ctx_impl->free_buf, &ctx_impl->ctx_buf[1].link); + ctx_impl->free_buf = NULL; + nxt_unit_mmap_buf_insert(&ctx_impl->free_buf, &ctx_impl->ctx_buf[1]); + nxt_unit_mmap_buf_insert(&ctx_impl->free_buf, &ctx_impl->ctx_buf[0]); + nxt_queue_insert_tail(&ctx_impl->free_req, &ctx_impl->req.link); ctx_impl->req.req.ctx = &ctx_impl->ctx; ctx_impl->req.req.unit = &lib->unit; ctx_impl->read_port_fd = -1; + ctx_impl->requests.slot = 0; +} + + +nxt_inline void +nxt_unit_mmap_buf_insert(nxt_unit_mmap_buf_t **head, + nxt_unit_mmap_buf_t *mmap_buf) +{ + mmap_buf->next = *head; + + if (mmap_buf->next != NULL) { + mmap_buf->next->prev = &mmap_buf->next; + } + + *head = mmap_buf; + mmap_buf->prev = head; +} + + +nxt_inline void +nxt_unit_mmap_buf_insert_tail(nxt_unit_mmap_buf_t **prev, + nxt_unit_mmap_buf_t *mmap_buf) +{ + while (*prev != NULL) { + prev = &(*prev)->next; + } + + nxt_unit_mmap_buf_insert(prev, mmap_buf); +} + + +nxt_inline void +nxt_unit_mmap_buf_remove(nxt_unit_mmap_buf_t *mmap_buf) +{ + nxt_unit_mmap_buf_t **prev; + + prev = mmap_buf->prev; + + if (mmap_buf->next != NULL) { + mmap_buf->next->prev = prev; + } + + if (prev != NULL) { + *prev = mmap_buf->next; + } } @@ -509,26 +617,18 @@ int nxt_unit_process_msg(nxt_unit_ctx_t *ctx, nxt_unit_port_id_t *port_id, void *buf, size_t buf_size, void *oob, size_t oob_size) { - int fd, rc, nb; - pid_t pid; - nxt_queue_t incoming_buf; - struct cmsghdr *cm; - nxt_port_msg_t *port_msg; - nxt_unit_impl_t *lib; - nxt_unit_port_t new_port; - nxt_queue_link_t *lnk; - nxt_unit_request_t *r; - nxt_unit_mmap_buf_t *b; - nxt_unit_recv_msg_t recv_msg; - nxt_unit_callbacks_t *cb; - nxt_port_msg_new_port_t *new_port_msg; - nxt_unit_request_info_t *req; - nxt_unit_request_info_impl_t *req_impl; + int rc; + pid_t pid; + struct cmsghdr *cm; + nxt_port_msg_t *port_msg; + nxt_unit_impl_t *lib; + nxt_unit_recv_msg_t recv_msg; + nxt_unit_callbacks_t *cb; lib = nxt_container_of(ctx->unit, nxt_unit_impl_t, unit); rc = NXT_UNIT_ERROR; - fd = -1; + recv_msg.fd = -1; recv_msg.process = NULL; port_msg = buf; cm = oob; @@ -538,17 +638,22 @@ nxt_unit_process_msg(nxt_unit_ctx_t *ctx, nxt_unit_port_id_t *port_id, && cm->cmsg_level == SOL_SOCKET && cm->cmsg_type == SCM_RIGHTS) { - memcpy(&fd, CMSG_DATA(cm), sizeof(int)); + memcpy(&recv_msg.fd, CMSG_DATA(cm), sizeof(int)); } - nxt_queue_init(&incoming_buf); + recv_msg.incoming_buf = NULL; if (nxt_slow_path(buf_size < sizeof(nxt_port_msg_t))) { nxt_unit_warn(ctx, "message too small (%d bytes)", (int) buf_size); goto fail; } - recv_msg.port_msg = *port_msg; + recv_msg.stream = port_msg->stream; + recv_msg.pid = port_msg->pid; + recv_msg.reply_port = port_msg->reply_port; + recv_msg.last = port_msg->last; + recv_msg.mmap = port_msg->mmap; + recv_msg.start = port_msg + 1; recv_msg.size = buf_size - sizeof(nxt_port_msg_t); @@ -572,7 +677,7 @@ nxt_unit_process_msg(nxt_unit_ctx_t *ctx, nxt_unit_port_id_t *port_id, } if (port_msg->mmap) { - if (nxt_unit_mmap_read(ctx, &recv_msg, &incoming_buf) != NXT_UNIT_OK) { + if (nxt_unit_mmap_read(ctx, &recv_msg) != NXT_UNIT_OK) { goto fail; } } @@ -589,187 +694,326 @@ nxt_unit_process_msg(nxt_unit_ctx_t *ctx, nxt_unit_port_id_t *port_id, break; case _NXT_PORT_MSG_NEW_PORT: - if (nxt_slow_path(recv_msg.size != sizeof(nxt_port_msg_new_port_t))) { - nxt_unit_warn(ctx, "#%"PRIu32": new_port: " - "invalid message size (%d)", - port_msg->stream, (int) recv_msg.size); + rc = nxt_unit_process_new_port(ctx, &recv_msg); + break; - goto fail; - } + case _NXT_PORT_MSG_CHANGE_FILE: + nxt_unit_debug(ctx, "#%"PRIu32": change_file: fd %d", + port_msg->stream, recv_msg.fd); + break; - if (nxt_slow_path(fd < 0)) { - nxt_unit_alert(ctx, "#%"PRIu32": invalid fd %d for new port", - port_msg->stream, fd); + case _NXT_PORT_MSG_MMAP: + if (nxt_slow_path(recv_msg.fd < 0)) { + nxt_unit_alert(ctx, "#%"PRIu32": invalid fd %d for mmap", + port_msg->stream, recv_msg.fd); goto fail; } - new_port_msg = recv_msg.start; + rc = nxt_unit_incoming_mmap(ctx, port_msg->pid, recv_msg.fd); + break; - nxt_unit_debug(ctx, "#%"PRIu32": new_port: %d,%d fd %d", - port_msg->stream, (int) new_port_msg->pid, - (int) new_port_msg->id, fd); + case _NXT_PORT_MSG_REQ_HEADERS: + rc = nxt_unit_process_req_headers(ctx, &recv_msg); + break; - nb = 0; + case _NXT_PORT_MSG_WEBSOCKET: + rc = nxt_unit_process_websocket(ctx, &recv_msg); + break; - if (nxt_slow_path(ioctl(fd, FIONBIO, &nb) == -1)) { - nxt_unit_alert(ctx, "#%"PRIu32": new_port: ioctl(%d, FIONBIO, 0) " - "failed: %s (%d)", fd, strerror(errno), errno); + case _NXT_PORT_MSG_REMOVE_PID: + if (nxt_slow_path(recv_msg.size != sizeof(pid))) { + nxt_unit_warn(ctx, "#%"PRIu32": remove_pid: invalid message size " + "(%d != %d)", port_msg->stream, (int) recv_msg.size, + (int) sizeof(pid)); goto fail; } - nxt_unit_port_id_init(&new_port.id, new_port_msg->pid, - new_port_msg->id); + memcpy(&pid, recv_msg.start, sizeof(pid)); - new_port.in_fd = -1; - new_port.out_fd = fd; - new_port.data = NULL; + nxt_unit_debug(ctx, "#%"PRIu32": remove_pid: %d", + port_msg->stream, (int) pid); - fd = -1; + cb->remove_pid(ctx, pid); - rc = cb->add_port(ctx, &new_port); + rc = NXT_UNIT_OK; break; - case _NXT_PORT_MSG_CHANGE_FILE: - nxt_unit_debug(ctx, "#%"PRIu32": change_file: fd %d", - port_msg->stream, fd); - break; + default: + nxt_unit_debug(ctx, "#%"PRIu32": ignore message type: %d", + port_msg->stream, (int) port_msg->type); - case _NXT_PORT_MSG_MMAP: - if (nxt_slow_path(fd < 0)) { - nxt_unit_alert(ctx, "#%"PRIu32": invalid fd %d for mmap", - port_msg->stream, fd); + goto fail; + } - goto fail; - } +fail: - rc = nxt_unit_incoming_mmap(ctx, port_msg->pid, fd); - break; + if (recv_msg.fd != -1) { + close(recv_msg.fd); + } - case _NXT_PORT_MSG_DATA: - if (nxt_slow_path(port_msg->mmap == 0)) { - nxt_unit_warn(ctx, "#%"PRIu32": data is not in shared memory", - port_msg->stream); + while (recv_msg.incoming_buf != NULL) { + nxt_unit_mmap_buf_free(recv_msg.incoming_buf); + } - goto fail; - } + if (recv_msg.process != NULL) { + nxt_unit_process_use(ctx, recv_msg.process, -1); + } - if (nxt_slow_path(recv_msg.size < sizeof(nxt_unit_request_t))) { - nxt_unit_warn(ctx, "#%"PRIu32": data too short: %d while at least " - "%d expected", port_msg->stream, (int) recv_msg.size, - (int) sizeof(nxt_unit_request_t)); + return rc; +} - goto fail; - } - req_impl = nxt_unit_request_info_get(ctx); - if (nxt_slow_path(req_impl == NULL)) { - nxt_unit_warn(ctx, "#%"PRIu32": request info allocation failed", - port_msg->stream); +static int +nxt_unit_process_new_port(nxt_unit_ctx_t *ctx, nxt_unit_recv_msg_t *recv_msg) +{ + int nb; + nxt_unit_impl_t *lib; + nxt_unit_port_t new_port; + nxt_port_msg_new_port_t *new_port_msg; - goto fail; - } + if (nxt_slow_path(recv_msg->size != sizeof(nxt_port_msg_new_port_t))) { + nxt_unit_warn(ctx, "#%"PRIu32": new_port: " + "invalid message size (%d)", + recv_msg->stream, (int) recv_msg->size); - req = &req_impl->req; + return NXT_UNIT_ERROR; + } - req->request_port = *port_id; + if (nxt_slow_path(recv_msg->fd < 0)) { + nxt_unit_alert(ctx, "#%"PRIu32": invalid fd %d for new port", + recv_msg->stream, recv_msg->fd); - nxt_unit_port_id_init(&req->response_port, port_msg->pid, - port_msg->reply_port); + return NXT_UNIT_ERROR; + } - req->request = recv_msg.start; + new_port_msg = recv_msg->start; - lnk = nxt_queue_first(&incoming_buf); - b = nxt_container_of(lnk, nxt_unit_mmap_buf_t, link); + nxt_unit_debug(ctx, "#%"PRIu32": new_port: %d,%d fd %d", + recv_msg->stream, (int) new_port_msg->pid, + (int) new_port_msg->id, recv_msg->fd); - req->request_buf = &b->buf; - req->response = NULL; - req->response_buf = NULL; + nb = 0; - r = req->request; + if (nxt_slow_path(ioctl(recv_msg->fd, FIONBIO, &nb) == -1)) { + nxt_unit_alert(ctx, "#%"PRIu32": new_port: ioctl(%d, FIONBIO, 0) " + "failed: %s (%d)", recv_msg->fd, strerror(errno), errno); - req->content_length = r->content_length; + return NXT_UNIT_ERROR; + } - req->content_buf = req->request_buf; - req->content_buf->free = nxt_unit_sptr_get(&r->preread_content); + nxt_unit_port_id_init(&new_port.id, new_port_msg->pid, + new_port_msg->id); - /* Move process to req_impl. */ - req_impl->recv_msg = recv_msg; + new_port.in_fd = -1; + new_port.out_fd = recv_msg->fd; + new_port.data = NULL; - recv_msg.process = NULL; + recv_msg->fd = -1; - nxt_queue_init(&req_impl->outgoing_buf); - nxt_queue_init(&req_impl->incoming_buf); + lib = nxt_container_of(ctx->unit, nxt_unit_impl_t, unit); - nxt_queue_each(b, &incoming_buf, nxt_unit_mmap_buf_t, link) - { - b->req = req; - } nxt_queue_loop; + return lib->callbacks.add_port(ctx, &new_port); +} - nxt_queue_add(&req_impl->incoming_buf, &incoming_buf); - nxt_queue_init(&incoming_buf); - req->response_max_fields = 0; - req_impl->state = NXT_UNIT_RS_START; +static int +nxt_unit_process_req_headers(nxt_unit_ctx_t *ctx, nxt_unit_recv_msg_t *recv_msg) +{ + nxt_unit_impl_t *lib; + nxt_unit_request_t *r; + nxt_unit_mmap_buf_t *b; + nxt_unit_request_info_t *req; + nxt_unit_request_info_impl_t *req_impl; - nxt_unit_debug(ctx, "#%"PRIu32": %.*s %.*s (%d)", port_msg->stream, - (int) r->method_length, nxt_unit_sptr_get(&r->method), - (int) r->target_length, nxt_unit_sptr_get(&r->target), - (int) r->content_length); + if (nxt_slow_path(recv_msg->mmap == 0)) { + nxt_unit_warn(ctx, "#%"PRIu32": data is not in shared memory", + recv_msg->stream); - cb->request_handler(req); + return NXT_UNIT_ERROR; + } - rc = NXT_UNIT_OK; - break; + if (nxt_slow_path(recv_msg->size < sizeof(nxt_unit_request_t))) { + nxt_unit_warn(ctx, "#%"PRIu32": data too short: %d while at least " + "%d expected", recv_msg->stream, (int) recv_msg->size, + (int) sizeof(nxt_unit_request_t)); - case _NXT_PORT_MSG_REMOVE_PID: - if (nxt_slow_path(recv_msg.size != sizeof(pid))) { - nxt_unit_warn(ctx, "#%"PRIu32": remove_pid: invalid message size " - "(%d != %d)", port_msg->stream, (int) recv_msg.size, - (int) sizeof(pid)); + return NXT_UNIT_ERROR; + } - goto fail; - } + req_impl = nxt_unit_request_info_get(ctx); + if (nxt_slow_path(req_impl == NULL)) { + nxt_unit_warn(ctx, "#%"PRIu32": request info allocation failed", + recv_msg->stream); - memcpy(&pid, recv_msg.start, sizeof(pid)); + return NXT_UNIT_ERROR; + } - nxt_unit_debug(ctx, "#%"PRIu32": remove_pid: %d", - port_msg->stream, (int) pid); + req = &req_impl->req; - cb->remove_pid(ctx, pid); + nxt_unit_port_id_init(&req->response_port, recv_msg->pid, + recv_msg->reply_port); - rc = NXT_UNIT_OK; - break; + req->request = recv_msg->start; - default: - nxt_unit_debug(ctx, "#%"PRIu32": ignore message type: %d", - port_msg->stream, (int) port_msg->type); + b = recv_msg->incoming_buf; - goto fail; + req->request_buf = &b->buf; + req->response = NULL; + req->response_buf = NULL; + + r = req->request; + + req->content_length = r->content_length; + + req->content_buf = req->request_buf; + req->content_buf->free = nxt_unit_sptr_get(&r->preread_content); + + /* "Move" process reference to req_impl. */ + req_impl->process = nxt_unit_msg_get_process(ctx, recv_msg); + if (nxt_slow_path(req_impl->process == NULL)) { + return NXT_UNIT_ERROR; } -fail: + recv_msg->process = NULL; - if (fd != -1) { - close(fd); + req_impl->stream = recv_msg->stream; + + req_impl->outgoing_buf = NULL; + + for (b = recv_msg->incoming_buf; b != NULL; b = b->next) { + b->req = req; } - if (port_msg->mmap) { - nxt_queue_each(b, &incoming_buf, nxt_unit_mmap_buf_t, link) - { - nxt_unit_mmap_release(b->hdr, b->buf.start, - b->buf.end - b->buf.start); + /* "Move" incoming buffer list to req_impl. */ + req_impl->incoming_buf = recv_msg->incoming_buf; + req_impl->incoming_buf->prev = &req_impl->incoming_buf; + recv_msg->incoming_buf = NULL; + + req->response_max_fields = 0; + req_impl->state = NXT_UNIT_RS_START; + req_impl->websocket = 0; + + nxt_unit_debug(ctx, "#%"PRIu32": %.*s %.*s (%d)", recv_msg->stream, + (int) r->method_length, nxt_unit_sptr_get(&r->method), + (int) r->target_length, nxt_unit_sptr_get(&r->target), + (int) r->content_length); - nxt_unit_mmap_buf_release(b); - } nxt_queue_loop; + lib = nxt_container_of(ctx->unit, nxt_unit_impl_t, unit); + + lib->callbacks.request_handler(req); + + return NXT_UNIT_OK; +} + + +static int +nxt_unit_process_websocket(nxt_unit_ctx_t *ctx, nxt_unit_recv_msg_t *recv_msg) +{ + size_t hsize; + nxt_unit_impl_t *lib; + nxt_unit_mmap_buf_t *b; + nxt_unit_ctx_impl_t *ctx_impl; + nxt_unit_callbacks_t *cb; + nxt_unit_request_info_t *req; + nxt_unit_request_info_impl_t *req_impl; + nxt_unit_websocket_frame_impl_t *ws_impl; + + ctx_impl = nxt_container_of(ctx, nxt_unit_ctx_impl_t, ctx); + + req_impl = nxt_unit_request_hash_find(&ctx_impl->requests, recv_msg->stream, + recv_msg->last); + if (req_impl == NULL) { + return NXT_UNIT_OK; } - if (recv_msg.process != NULL) { - nxt_unit_process_use(ctx, recv_msg.process, -1); + req = &req_impl->req; + + lib = nxt_container_of(ctx->unit, nxt_unit_impl_t, unit); + cb = &lib->callbacks; + + if (cb->websocket_handler && recv_msg->size >= 2) { + ws_impl = nxt_unit_websocket_frame_get(ctx); + if (nxt_slow_path(ws_impl == NULL)) { + nxt_unit_warn(ctx, "#%"PRIu32": websocket frame allocation failed", + req_impl->stream); + + return NXT_UNIT_ERROR; + } + + ws_impl->ws.req = req; + + ws_impl->buf = NULL; + ws_impl->retain_buf = NULL; + + if (recv_msg->mmap) { + for (b = recv_msg->incoming_buf; b != NULL; b = b->next) { + b->req = req; + } + + /* "Move" incoming buffer list to ws_impl. */ + ws_impl->buf = recv_msg->incoming_buf; + ws_impl->buf->prev = &ws_impl->buf; + recv_msg->incoming_buf = NULL; + + b = ws_impl->buf; + + } else { + b = nxt_unit_mmap_buf_get(ctx); + if (nxt_slow_path(b == NULL)) { + return NXT_UNIT_ERROR; + } + + b->hdr = NULL; + b->req = req; + b->buf.start = recv_msg->start; + b->buf.free = b->buf.start; + b->buf.end = b->buf.start + recv_msg->size; + + nxt_unit_mmap_buf_insert(&ws_impl->buf, b); + } + + ws_impl->ws.header = (void *) b->buf.start; + ws_impl->ws.payload_len = nxt_websocket_frame_payload_len( + ws_impl->ws.header); + + hsize = nxt_websocket_frame_header_size(ws_impl->ws.header); + + if (ws_impl->ws.header->mask) { + ws_impl->ws.mask = (uint8_t *) b->buf.start + hsize - 4; + + } else { + ws_impl->ws.mask = NULL; + } + + b->buf.free += hsize; + + ws_impl->ws.content_buf = &b->buf; + ws_impl->ws.content_length = ws_impl->ws.payload_len; + + nxt_unit_req_debug(req, "websocket_handler: opcode=%d, " + "payload_len=%"PRIu64, + ws_impl->ws.header->opcode, + ws_impl->ws.payload_len); + + cb->websocket_handler(&ws_impl->ws); } - return rc; + if (recv_msg->last) { + req_impl->websocket = 0; + + if (cb->close_handler) { + nxt_unit_req_debug(req, "close_handler"); + + cb->close_handler(req); + + } else { + nxt_unit_request_done(req, NXT_UNIT_ERROR); + } + } + + return NXT_UNIT_OK; } @@ -815,9 +1059,7 @@ nxt_unit_request_info_get(nxt_unit_ctx_t *ctx) static void nxt_unit_request_info_release(nxt_unit_request_info_t *req) { - nxt_unit_mmap_buf_t *b; nxt_unit_ctx_impl_t *ctx_impl; - nxt_unit_recv_msg_t *recv_msg; nxt_unit_request_info_impl_t *req_impl; ctx_impl = nxt_container_of(req->ctx, nxt_unit_ctx_impl_t, ctx); @@ -826,30 +1068,31 @@ nxt_unit_request_info_release(nxt_unit_request_info_t *req) req->response = NULL; req->response_buf = NULL; - recv_msg = &req_impl->recv_msg; - - if (recv_msg->process != NULL) { - nxt_unit_process_use(req->ctx, recv_msg->process, -1); + if (req_impl->process != NULL) { + nxt_unit_process_use(req->ctx, req_impl->process, -1); - recv_msg->process = NULL; + req_impl->process = NULL; } - nxt_queue_each(b, &req_impl->outgoing_buf, nxt_unit_mmap_buf_t, link) { + if (req_impl->websocket) { + nxt_unit_request_hash_find(&ctx_impl->requests, req_impl->stream, 1); - nxt_unit_buf_free(&b->buf); - - } nxt_queue_loop; - - nxt_queue_each(b, &req_impl->incoming_buf, nxt_unit_mmap_buf_t, link) { + req_impl->websocket = 0; + } - nxt_unit_mmap_release(b->hdr, b->buf.start, b->buf.end - b->buf.start); - nxt_unit_mmap_buf_release(b); + while (req_impl->outgoing_buf != NULL) { + nxt_unit_mmap_buf_free(req_impl->outgoing_buf); + } - } nxt_queue_loop; + while (req_impl->incoming_buf != NULL) { + nxt_unit_mmap_buf_free(req_impl->incoming_buf); + } nxt_queue_remove(&req_impl->link); nxt_queue_insert_tail(&ctx_impl->free_req, &req_impl->link); + + req_impl->state = NXT_UNIT_RS_RELEASED; } @@ -858,13 +1101,75 @@ nxt_unit_request_info_free(nxt_unit_request_info_impl_t *req_impl) { nxt_unit_ctx_impl_t *ctx_impl; - ctx_impl = nxt_container_of(req_impl->req.ctx, nxt_unit_ctx_impl_t, ctx); + ctx_impl = nxt_container_of(req_impl->req.ctx, nxt_unit_ctx_impl_t, ctx); + + nxt_queue_remove(&req_impl->link); + + if (req_impl != &ctx_impl->req) { + free(req_impl); + } +} + + +static nxt_unit_websocket_frame_impl_t * +nxt_unit_websocket_frame_get(nxt_unit_ctx_t *ctx) +{ + nxt_queue_link_t *lnk; + nxt_unit_ctx_impl_t *ctx_impl; + nxt_unit_websocket_frame_impl_t *ws_impl; + + ctx_impl = nxt_container_of(ctx, nxt_unit_ctx_impl_t, ctx); + + if (nxt_queue_is_empty(&ctx_impl->free_ws)) { + ws_impl = malloc(sizeof(nxt_unit_websocket_frame_impl_t)); + if (nxt_slow_path(ws_impl == NULL)) { + nxt_unit_warn(ctx, "websocket frame allocation failed"); + + return NULL; + } + + } else { + lnk = nxt_queue_first(&ctx_impl->free_ws); + nxt_queue_remove(lnk); + + ws_impl = nxt_container_of(lnk, nxt_unit_websocket_frame_impl_t, link); + } + + ws_impl->ctx_impl = ctx_impl; + + return ws_impl; +} + + +static void +nxt_unit_websocket_frame_release(nxt_unit_websocket_frame_t *ws) +{ + nxt_unit_websocket_frame_impl_t *ws_impl; + + ws_impl = nxt_container_of(ws, nxt_unit_websocket_frame_impl_t, ws); + + while (ws_impl->buf != NULL) { + nxt_unit_mmap_buf_free(ws_impl->buf); + } + + ws->req = NULL; + + if (ws_impl->retain_buf != NULL) { + free(ws_impl->retain_buf); + + ws_impl->retain_buf = NULL; + } + + nxt_queue_insert_tail(&ws_impl->ctx_impl->free_ws, &ws_impl->link); +} + - nxt_queue_remove(&req_impl->link); +static void +nxt_unit_websocket_frame_free(nxt_unit_websocket_frame_impl_t *ws_impl) +{ + nxt_queue_remove(&ws_impl->link); - if (req_impl != &ctx_impl->req) { - free(req_impl); - } + free(ws_impl); } @@ -1275,6 +1580,10 @@ nxt_unit_response_send(nxt_unit_request_info_t *req) return NXT_UNIT_ERROR; } + if (req->request->websocket_handshake && req->response->status == 101) { + nxt_unit_response_upgrade(req); + } + nxt_unit_req_debug(req, "send: %"PRIu32" fields, %d bytes", req->response->fields_count, (int) (req->response_buf->free @@ -1282,9 +1591,7 @@ nxt_unit_response_send(nxt_unit_request_info_t *req) mmap_buf = nxt_container_of(req->response_buf, nxt_unit_mmap_buf_t, buf); - rc = nxt_unit_mmap_buf_send(req->ctx, - req_impl->recv_msg.port_msg.stream, - mmap_buf, 0); + rc = nxt_unit_mmap_buf_send(req->ctx, req_impl->stream, mmap_buf, 0); if (nxt_fast_path(rc == NXT_UNIT_OK)) { req->response = NULL; req->response_buf = NULL; @@ -1312,7 +1619,6 @@ nxt_unit_buf_t * nxt_unit_response_buf_alloc(nxt_unit_request_info_t *req, uint32_t size) { int rc; - nxt_unit_process_t *process; nxt_unit_mmap_buf_t *mmap_buf; nxt_unit_request_info_impl_t *req_impl; @@ -1327,11 +1633,6 @@ nxt_unit_response_buf_alloc(nxt_unit_request_info_t *req, uint32_t size) req_impl = nxt_container_of(req, nxt_unit_request_info_impl_t, req); - process = nxt_unit_msg_get_process(req->ctx, &req_impl->recv_msg); - if (nxt_slow_path(process == NULL)) { - return NULL; - } - mmap_buf = nxt_unit_mmap_buf_get(req->ctx); if (nxt_slow_path(mmap_buf == NULL)) { return NULL; @@ -1339,10 +1640,10 @@ nxt_unit_response_buf_alloc(nxt_unit_request_info_t *req, uint32_t size) mmap_buf->req = req; - nxt_queue_insert_tail(&req_impl->outgoing_buf, &mmap_buf->link); + nxt_unit_mmap_buf_insert_tail(&req_impl->outgoing_buf, mmap_buf); - rc = nxt_unit_get_outgoing_buf(req->ctx, process, &req->response_port, - size, mmap_buf); + rc = nxt_unit_get_outgoing_buf(req->ctx, req_impl->process, + &req->response_port, size, mmap_buf); if (nxt_slow_path(rc != NXT_UNIT_OK)) { nxt_unit_mmap_buf_release(mmap_buf); @@ -1366,13 +1667,13 @@ nxt_unit_msg_get_process(nxt_unit_ctx_t *ctx, nxt_unit_recv_msg_t *recv_msg) pthread_mutex_lock(&lib->mutex); - recv_msg->process = nxt_unit_process_find(ctx, recv_msg->port_msg.pid, 0); + recv_msg->process = nxt_unit_process_find(ctx, recv_msg->pid, 0); pthread_mutex_unlock(&lib->mutex); if (recv_msg->process == NULL) { nxt_unit_warn(ctx, "#%"PRIu32": process %d not found", - recv_msg->port_msg.stream, (int) recv_msg->port_msg.pid); + recv_msg->stream, (int) recv_msg->pid); } return recv_msg->process; @@ -1382,23 +1683,21 @@ nxt_unit_msg_get_process(nxt_unit_ctx_t *ctx, nxt_unit_recv_msg_t *recv_msg) static nxt_unit_mmap_buf_t * nxt_unit_mmap_buf_get(nxt_unit_ctx_t *ctx) { - nxt_queue_link_t *lnk; nxt_unit_mmap_buf_t *mmap_buf; nxt_unit_ctx_impl_t *ctx_impl; ctx_impl = nxt_container_of(ctx, nxt_unit_ctx_impl_t, ctx); - if (nxt_queue_is_empty(&ctx_impl->free_buf)) { + if (ctx_impl->free_buf == NULL) { mmap_buf = malloc(sizeof(nxt_unit_mmap_buf_t)); if (nxt_slow_path(mmap_buf == NULL)) { nxt_unit_warn(ctx, "failed to allocate buf"); } } else { - lnk = nxt_queue_first(&ctx_impl->free_buf); - nxt_queue_remove(lnk); + mmap_buf = ctx_impl->free_buf; - mmap_buf = nxt_container_of(lnk, nxt_unit_mmap_buf_t, link); + nxt_unit_mmap_buf_remove(mmap_buf); } mmap_buf->ctx_impl = ctx_impl; @@ -1410,9 +1709,91 @@ nxt_unit_mmap_buf_get(nxt_unit_ctx_t *ctx) static void nxt_unit_mmap_buf_release(nxt_unit_mmap_buf_t *mmap_buf) { - nxt_queue_remove(&mmap_buf->link); + nxt_unit_mmap_buf_remove(mmap_buf); + + nxt_unit_mmap_buf_insert(&mmap_buf->ctx_impl->free_buf, mmap_buf); +} + + +typedef struct { + size_t len; + const char *str; +} nxt_unit_str_t; + + +#define nxt_unit_str(str) { nxt_length(str), str } + + +int +nxt_unit_request_is_websocket_handshake(nxt_unit_request_info_t *req) +{ + return req->request->websocket_handshake; +} + + +int +nxt_unit_response_upgrade(nxt_unit_request_info_t *req) +{ + int rc; + nxt_unit_ctx_impl_t *ctx_impl; + nxt_unit_request_info_impl_t *req_impl; + + req_impl = nxt_container_of(req, nxt_unit_request_info_impl_t, req); + + if (nxt_slow_path(req_impl->websocket != 0)) { + nxt_unit_req_debug(req, "upgrade: already upgraded"); + + return NXT_UNIT_OK; + } + + if (nxt_slow_path(req_impl->state < NXT_UNIT_RS_RESPONSE_INIT)) { + nxt_unit_req_warn(req, "upgrade: response is not initialized yet"); + + return NXT_UNIT_ERROR; + } + + if (nxt_slow_path(req_impl->state >= NXT_UNIT_RS_RESPONSE_SENT)) { + nxt_unit_req_warn(req, "upgrade: response already sent"); + + return NXT_UNIT_ERROR; + } + + ctx_impl = nxt_container_of(req->ctx, nxt_unit_ctx_impl_t, ctx); + + rc = nxt_unit_request_hash_add(&ctx_impl->requests, req_impl); + if (nxt_slow_path(rc != NXT_UNIT_OK)) { + nxt_unit_req_warn(req, "upgrade: failed to add request to hash"); + + return NXT_UNIT_ERROR; + } + + req_impl->websocket = 1; + + req->response->status = 101; + + return NXT_UNIT_OK; +} + + +int +nxt_unit_response_is_websocket(nxt_unit_request_info_t *req) +{ + nxt_unit_request_info_impl_t *req_impl; + + req_impl = nxt_container_of(req, nxt_unit_request_info_impl_t, req); + + return req_impl->websocket; +} + + +nxt_unit_request_info_t * +nxt_unit_get_request_info_from_data(void *data) +{ + nxt_unit_request_info_impl_t *req_impl; + + req_impl = nxt_container_of(data, nxt_unit_request_info_impl_t, extra_data); - nxt_queue_insert_tail(&mmap_buf->ctx_impl->free_buf, &mmap_buf->link); + return &req_impl->req; } @@ -1445,9 +1826,7 @@ nxt_unit_buf_send(nxt_unit_buf_t *buf) } if (nxt_fast_path(buf->free > buf->start)) { - rc = nxt_unit_mmap_buf_send(req->ctx, - req_impl->recv_msg.port_msg.stream, - mmap_buf, 0); + rc = nxt_unit_mmap_buf_send(req->ctx, req_impl->stream, mmap_buf, 0); if (nxt_slow_path(rc != NXT_UNIT_OK)) { return rc; } @@ -1472,10 +1851,7 @@ nxt_unit_buf_send_done(nxt_unit_buf_t *buf) req = mmap_buf->req; req_impl = nxt_container_of(req, nxt_unit_request_info_impl_t, req); - rc = nxt_unit_mmap_buf_send(req->ctx, - req_impl->recv_msg.port_msg.stream, - mmap_buf, 1); - + rc = nxt_unit_mmap_buf_send(req->ctx, req_impl->stream, mmap_buf, 1); if (nxt_slow_path(rc == NXT_UNIT_OK)) { nxt_unit_mmap_buf_release(mmap_buf); @@ -1506,6 +1882,7 @@ nxt_unit_mmap_buf_send(nxt_unit_ctx_t *ctx, uint32_t stream, lib = nxt_container_of(ctx->unit, nxt_unit_impl_t, unit); buf = &mmap_buf->buf; + hdr = mmap_buf->hdr; m.mmap_msg.size = buf->free - buf->start; @@ -1514,15 +1891,15 @@ nxt_unit_mmap_buf_send(nxt_unit_ctx_t *ctx, uint32_t stream, m.msg.reply_port = 0; m.msg.type = _NXT_PORT_MSG_DATA; m.msg.last = last != 0; - m.msg.mmap = m.mmap_msg.size > 0; + m.msg.mmap = hdr != NULL && m.mmap_msg.size > 0; m.msg.nf = 0; m.msg.mf = 0; m.msg.tracking = 0; - hdr = mmap_buf->hdr; - - m.mmap_msg.mmap_id = hdr->id; - m.mmap_msg.chunk_id = nxt_port_mmap_chunk_id(hdr, (u_char *) buf->start); + if (hdr != NULL) { + m.mmap_msg.mmap_id = hdr->id; + m.mmap_msg.chunk_id = nxt_port_mmap_chunk_id(hdr, (u_char *) buf->start); + } nxt_unit_debug(ctx, "#%"PRIu32": send mmap: (%d,%d,%d)", stream, @@ -1531,14 +1908,13 @@ nxt_unit_mmap_buf_send(nxt_unit_ctx_t *ctx, uint32_t stream, (int) m.mmap_msg.size); res = lib->callbacks.port_send(ctx, &mmap_buf->port_id, &m, - m.mmap_msg.size > 0 ? sizeof(m) - : sizeof(m.msg), + m.msg.mmap ? sizeof(m) : sizeof(m.msg), NULL, 0); if (nxt_slow_path(res != sizeof(m))) { return NXT_UNIT_ERROR; } - if (buf->end - buf->free >= PORT_MMAP_CHUNK_SIZE) { + if (buf->end - buf->free >= PORT_MMAP_CHUNK_SIZE && hdr != NULL) { last_used = (u_char *) buf->free - 1; first_free_chunk = nxt_port_mmap_chunk_id(hdr, last_used) + 1; @@ -1557,11 +1933,17 @@ nxt_unit_mmap_buf_send(nxt_unit_ctx_t *ctx, uint32_t stream, void nxt_unit_buf_free(nxt_unit_buf_t *buf) { - nxt_unit_mmap_buf_t *mmap_buf; + nxt_unit_mmap_buf_free(nxt_container_of(buf, nxt_unit_mmap_buf_t, buf)); +} - mmap_buf = nxt_container_of(buf, nxt_unit_mmap_buf_t, buf); - nxt_unit_mmap_release(mmap_buf->hdr, buf->start, buf->end - buf->start); +static void +nxt_unit_mmap_buf_free(nxt_unit_mmap_buf_t *mmap_buf) +{ + if (nxt_fast_path(mmap_buf->hdr != NULL)) { + nxt_unit_mmap_release(mmap_buf->hdr, mmap_buf->buf.start, + mmap_buf->buf.end - mmap_buf->buf.start); + } nxt_unit_mmap_buf_release(mmap_buf); } @@ -1570,26 +1952,15 @@ nxt_unit_buf_free(nxt_unit_buf_t *buf) nxt_unit_buf_t * nxt_unit_buf_next(nxt_unit_buf_t *buf) { - nxt_queue_link_t *lnk; - nxt_unit_mmap_buf_t *mmap_buf; - nxt_unit_request_info_impl_t *req_impl; + nxt_unit_mmap_buf_t *mmap_buf; mmap_buf = nxt_container_of(buf, nxt_unit_mmap_buf_t, buf); - req_impl = nxt_container_of(mmap_buf->req, nxt_unit_request_info_impl_t, - req); - - lnk = &mmap_buf->link; - if (lnk == nxt_queue_last(&req_impl->incoming_buf) - || lnk == nxt_queue_last(&req_impl->outgoing_buf)) - { + if (mmap_buf->next == NULL) { return NULL; } - lnk = nxt_queue_next(lnk); - mmap_buf = nxt_container_of(lnk, nxt_unit_mmap_buf_t, link); - - return &mmap_buf->buf; + return &mmap_buf->next->buf; } @@ -1614,7 +1985,6 @@ nxt_unit_response_write(nxt_unit_request_info_t *req, const void *start, int rc; uint32_t part_size; const char *part_start; - nxt_unit_process_t *process; nxt_unit_mmap_buf_t mmap_buf; nxt_unit_request_info_impl_t *req_impl; @@ -1641,16 +2011,12 @@ nxt_unit_response_write(nxt_unit_request_info_t *req, const void *start, part_start += part_size; } - process = nxt_unit_msg_get_process(req->ctx, &req_impl->recv_msg); - if (nxt_slow_path(process == NULL)) { - return NXT_UNIT_ERROR; - } - while (size > 0) { part_size = nxt_min(size, PORT_MMAP_DATA_SIZE); - rc = nxt_unit_get_outgoing_buf(req->ctx, process, &req->response_port, - part_size, &mmap_buf); + rc = nxt_unit_get_outgoing_buf(req->ctx, req_impl->process, + &req->response_port, part_size, + &mmap_buf); if (nxt_slow_path(rc != NXT_UNIT_OK)) { return rc; } @@ -1658,9 +2024,7 @@ nxt_unit_response_write(nxt_unit_request_info_t *req, const void *start, mmap_buf.buf.free = nxt_cpymem(mmap_buf.buf.free, part_start, part_size); - rc = nxt_unit_mmap_buf_send(req->ctx, - req_impl->recv_msg.port_msg.stream, - &mmap_buf, 0); + rc = nxt_unit_mmap_buf_send(req->ctx, req_impl->stream, &mmap_buf, 0); if (nxt_slow_path(rc != NXT_UNIT_OK)) { nxt_unit_mmap_release(mmap_buf.hdr, mmap_buf.buf.start, mmap_buf.buf.end - mmap_buf.buf.start); @@ -1765,6 +2129,14 @@ nxt_unit_response_write_cb(nxt_unit_request_info_t *req, ssize_t nxt_unit_request_read(nxt_unit_request_info_t *req, void *dst, size_t size) +{ + return nxt_unit_buf_read(&req->content_buf, &req->content_length, + dst, size); +} + + +static ssize_t +nxt_unit_buf_read(nxt_unit_buf_t **b, uint64_t *len, void *dst, size_t size) { u_char *p; size_t rest, copy, read; @@ -1773,7 +2145,7 @@ nxt_unit_request_read(nxt_unit_request_info_t *req, void *dst, size_t size) p = dst; rest = size; - buf = req->content_buf; + buf = *b; while (buf != NULL) { copy = buf->end - buf->free; @@ -1795,11 +2167,11 @@ nxt_unit_request_read(nxt_unit_request_info_t *req, void *dst, size_t size) buf = nxt_unit_buf_next(buf); } - req->content_buf = buf; + *b = buf; read = size - rest; - req->content_length -= read; + *len -= read; return read; } @@ -1852,7 +2224,7 @@ skip_response_send: lib = nxt_container_of(req->unit, nxt_unit_impl_t, unit); - msg.stream = req_impl->recv_msg.port_msg.stream; + msg.stream = req_impl->stream; msg.pid = lib->pid; msg.reply_port = 0; msg.type = (rc == NXT_UNIT_OK) ? _NXT_PORT_MSG_DATA @@ -1874,6 +2246,162 @@ skip_response_send: } +int +nxt_unit_websocket_send(nxt_unit_request_info_t *req, uint8_t opcode, + uint8_t last, const void *start, size_t size) +{ + const struct iovec iov = { (void *) start, size }; + + return nxt_unit_websocket_sendv(req, opcode, last, &iov, 1); +} + + +int +nxt_unit_websocket_sendv(nxt_unit_request_info_t *req, uint8_t opcode, + uint8_t last, const struct iovec *iov, int iovcnt) +{ + int i, rc; + size_t l, copy; + uint32_t payload_len, buf_size; + const uint8_t *b; + nxt_unit_buf_t *buf; + nxt_websocket_header_t *wh; + + payload_len = 0; + + for (i = 0; i < iovcnt; i++) { + payload_len += iov[i].iov_len; + } + + buf_size = 10 + payload_len; + + buf = nxt_unit_response_buf_alloc(req, nxt_min(buf_size, + PORT_MMAP_DATA_SIZE)); + if (nxt_slow_path(buf == NULL)) { + nxt_unit_req_error(req, "Failed to allocate buf for content"); + + return NXT_UNIT_ERROR; + } + + buf->start[0] = 0; + buf->start[1] = 0; + + wh = (void *) buf->free; + + buf->free = nxt_websocket_frame_init(wh, payload_len); + wh->fin = last; + wh->opcode = opcode; + + for (i = 0; i < iovcnt; i++) { + b = iov[i].iov_base; + l = iov[i].iov_len; + + while (l > 0) { + copy = buf->end - buf->free; + copy = nxt_min(l, copy); + + buf->free = nxt_cpymem(buf->free, b, copy); + b += copy; + l -= copy; + + if (l > 0) { + buf_size -= buf->end - buf->start; + + rc = nxt_unit_buf_send(buf); + if (nxt_slow_path(rc != NXT_UNIT_OK)) { + nxt_unit_req_error(req, "Failed to send content"); + + return NXT_UNIT_ERROR; + } + + buf = nxt_unit_response_buf_alloc(req, nxt_min(buf_size, + PORT_MMAP_DATA_SIZE)); + if (nxt_slow_path(buf == NULL)) { + nxt_unit_req_error(req, + "Failed to allocate buf for content"); + + return NXT_UNIT_ERROR; + } + } + } + } + + if (buf->free > buf->start) { + rc = nxt_unit_buf_send(buf); + if (nxt_slow_path(rc != NXT_UNIT_OK)) { + nxt_unit_req_error(req, "Failed to send content"); + } + } + + return rc; +} + + +ssize_t +nxt_unit_websocket_read(nxt_unit_websocket_frame_t *ws, void *dst, + size_t size) +{ + ssize_t res; + uint8_t *b; + uint64_t i, d; + + res = nxt_unit_buf_read(&ws->content_buf, &ws->content_length, + dst, size); + + if (ws->mask == NULL) { + return res; + } + + b = dst; + d = (ws->payload_len - ws->content_length - res) % 4; + + for (i = 0; i < (uint64_t) res; i++) { + b[i] ^= ws->mask[ (i + d) % 4 ]; + } + + return res; +} + + +int +nxt_unit_websocket_retain(nxt_unit_websocket_frame_t *ws) +{ + char *b; + size_t size; + nxt_unit_websocket_frame_impl_t *ws_impl; + + ws_impl = nxt_container_of(ws, nxt_unit_websocket_frame_impl_t, ws); + + if (ws_impl->retain_buf != NULL || ws_impl->buf->hdr != NULL) { + return NXT_UNIT_OK; + } + + size = ws_impl->buf->buf.end - ws_impl->buf->buf.start; + + b = malloc(size); + if (nxt_slow_path(b == NULL)) { + return NXT_UNIT_ERROR; + } + + memcpy(b, ws_impl->buf->buf.start, size); + + ws_impl->buf->buf.start = b; + ws_impl->buf->buf.free = b; + ws_impl->buf->buf.end = b + size; + + ws_impl->retain_buf = b; + + return NXT_UNIT_OK; +} + + +void +nxt_unit_websocket_done(nxt_unit_websocket_frame_t *ws) +{ + nxt_unit_websocket_frame_release(ws); +} + + static nxt_port_mmap_header_t * nxt_unit_mmap_get(nxt_unit_ctx_t *ctx, nxt_unit_process_t *process, nxt_unit_port_id_t *port_id, nxt_chunk_id_t *c, int n) @@ -2355,7 +2883,7 @@ nxt_unit_tracking_read(nxt_unit_ctx_t *ctx, nxt_unit_recv_msg_t *recv_msg) if (recv_msg->size < (int) sizeof(nxt_port_mmap_tracking_msg_t)) { nxt_unit_warn(ctx, "#%"PRIu32": tracking_read: too small message (%d)", - recv_msg->port_msg.stream, (int) recv_msg->size); + recv_msg->stream, (int) recv_msg->size); return 0; } @@ -2378,18 +2906,18 @@ nxt_unit_tracking_read(nxt_unit_ctx_t *ctx, nxt_unit_recv_msg_t *recv_msg) nxt_unit_warn(ctx, "#%"PRIu32": tracking_read: " "invalid mmap id %d,%"PRIu32, - recv_msg->port_msg.stream, - (int) process->pid, tracking_msg->mmap_id); + recv_msg->stream, (int) process->pid, + tracking_msg->mmap_id); return 0; } c = tracking_msg->tracking_id; - rc = nxt_atomic_cmp_set(hdr->tracking + c, recv_msg->port_msg.stream, 0); + rc = nxt_atomic_cmp_set(hdr->tracking + c, recv_msg->stream, 0); if (rc == 0) { nxt_unit_debug(ctx, "#%"PRIu32": tracking cancelled", - recv_msg->port_msg.stream); + recv_msg->stream); nxt_port_mmap_set_chunk_free(hdr->free_tracking_map, c); } @@ -2401,19 +2929,18 @@ nxt_unit_tracking_read(nxt_unit_ctx_t *ctx, nxt_unit_recv_msg_t *recv_msg) static int -nxt_unit_mmap_read(nxt_unit_ctx_t *ctx, nxt_unit_recv_msg_t *recv_msg, - nxt_queue_t *incoming_buf) +nxt_unit_mmap_read(nxt_unit_ctx_t *ctx, nxt_unit_recv_msg_t *recv_msg) { void *start; uint32_t size; nxt_unit_process_t *process; - nxt_unit_mmap_buf_t *b; + nxt_unit_mmap_buf_t *b, **incoming_tail; nxt_port_mmap_msg_t *mmap_msg, *end; nxt_port_mmap_header_t *hdr; if (nxt_slow_path(recv_msg->size < sizeof(nxt_port_mmap_msg_t))) { nxt_unit_warn(ctx, "#%"PRIu32": mmap_read: too small message (%d)", - recv_msg->port_msg.stream, (int) recv_msg->size); + recv_msg->stream, (int) recv_msg->size); return NXT_UNIT_ERROR; } @@ -2426,6 +2953,8 @@ nxt_unit_mmap_read(nxt_unit_ctx_t *ctx, nxt_unit_recv_msg_t *recv_msg, mmap_msg = recv_msg->start; end = nxt_pointer_to(recv_msg->start, recv_msg->size); + incoming_tail = &recv_msg->incoming_buf; + pthread_mutex_lock(&process->incoming.mutex); for (; mmap_msg < end; mmap_msg++) { @@ -2435,8 +2964,8 @@ nxt_unit_mmap_read(nxt_unit_ctx_t *ctx, nxt_unit_recv_msg_t *recv_msg, nxt_unit_warn(ctx, "#%"PRIu32": mmap_read: " "invalid mmap id %d,%"PRIu32, - recv_msg->port_msg.stream, - (int) process->pid, mmap_msg->mmap_id); + recv_msg->stream, (int) process->pid, + mmap_msg->mmap_id); return NXT_UNIT_ERROR; } @@ -2453,16 +2982,16 @@ nxt_unit_mmap_read(nxt_unit_ctx_t *ctx, nxt_unit_recv_msg_t *recv_msg, if (nxt_slow_path(b == NULL)) { pthread_mutex_unlock(&process->incoming.mutex); - nxt_unit_warn(ctx, "#%"PRIu32": mmap_read: " - "failed to allocate buf", - recv_msg->port_msg.stream); + nxt_unit_warn(ctx, "#%"PRIu32": mmap_read: failed to allocate buf", + recv_msg->stream); nxt_unit_mmap_release(hdr, start, size); return NXT_UNIT_ERROR; } - nxt_queue_insert_tail(incoming_buf, &b->link); + nxt_unit_mmap_buf_insert(incoming_tail, b); + incoming_tail = &b->next; b->buf.start = start; b->buf.free = start; @@ -2470,7 +2999,7 @@ nxt_unit_mmap_read(nxt_unit_ctx_t *ctx, nxt_unit_recv_msg_t *recv_msg, b->hdr = hdr; nxt_unit_debug(ctx, "#%"PRIu32": mmap_read: [%p,%d] %d->%d,(%d,%d,%d)", - recv_msg->port_msg.stream, + recv_msg->stream, start, (int) size, (int) hdr->src_pid, (int) hdr->dst_pid, (int) hdr->id, (int) mmap_msg->chunk_id, @@ -2685,6 +3214,11 @@ nxt_unit_run_once(nxt_unit_ctx_t *ctx) if (nxt_fast_path(rsize > 0)) { rc = nxt_unit_process_msg(ctx, &ctx_impl->read_port_id, buf, rsize, oob, sizeof(oob)); + +#if (NXT_DEBUG) + memset(buf, 0xAC, rsize); +#endif + } else { rc = NXT_UNIT_ERROR; } @@ -2775,10 +3309,11 @@ nxt_unit_ctx_alloc(nxt_unit_ctx_t *ctx, void *data) void nxt_unit_ctx_free(nxt_unit_ctx_t *ctx) { - nxt_unit_impl_t *lib; - nxt_unit_ctx_impl_t *ctx_impl; - nxt_unit_mmap_buf_t *mmap_buf; - nxt_unit_request_info_impl_t *req_impl; + nxt_unit_impl_t *lib; + nxt_unit_ctx_impl_t *ctx_impl; + nxt_unit_mmap_buf_t *mmap_buf; + nxt_unit_request_info_impl_t *req_impl; + nxt_unit_websocket_frame_impl_t *ws_impl; ctx_impl = nxt_container_of(ctx, nxt_unit_ctx_impl_t, ctx); lib = nxt_container_of(ctx->unit, nxt_unit_impl_t, unit); @@ -2792,15 +3327,14 @@ nxt_unit_ctx_free(nxt_unit_ctx_t *ctx) } nxt_queue_loop; - nxt_queue_remove(&ctx_impl->ctx_buf[0].link); - nxt_queue_remove(&ctx_impl->ctx_buf[1].link); + nxt_unit_mmap_buf_remove(&ctx_impl->ctx_buf[0]); + nxt_unit_mmap_buf_remove(&ctx_impl->ctx_buf[1]); - nxt_queue_each(mmap_buf, &ctx_impl->free_buf, nxt_unit_mmap_buf_t, link) { - - nxt_queue_remove(&mmap_buf->link); + while (ctx_impl->free_buf != NULL) { + mmap_buf = ctx_impl->free_buf; + nxt_unit_mmap_buf_remove(mmap_buf); free(mmap_buf); - - } nxt_queue_loop; + } nxt_queue_each(req_impl, &ctx_impl->free_req, nxt_unit_request_info_impl_t, link) @@ -2809,6 +3343,13 @@ nxt_unit_ctx_free(nxt_unit_ctx_t *ctx) } nxt_queue_loop; + nxt_queue_each(ws_impl, &ctx_impl->free_ws, + nxt_unit_websocket_frame_impl_t, link) + { + nxt_unit_websocket_frame_free(ws_impl); + + } nxt_queue_loop; + nxt_queue_remove(&ctx_impl->link); if (ctx_impl != &lib->main_ctx) { @@ -3454,6 +3995,83 @@ nxt_unit_port_hash_find(nxt_lvlhsh_t *port_hash, nxt_unit_port_id_t *port_id, } +static nxt_int_t +nxt_unit_request_hash_test(nxt_lvlhsh_query_t *lhq, void *data) +{ + return NXT_OK; +} + + +static const nxt_lvlhsh_proto_t lvlhsh_requests_proto nxt_aligned(64) = { + NXT_LVLHSH_DEFAULT, + nxt_unit_request_hash_test, + nxt_lvlhsh_alloc, + nxt_lvlhsh_free, +}; + + +static int +nxt_unit_request_hash_add(nxt_lvlhsh_t *request_hash, + nxt_unit_request_info_impl_t *req_impl) +{ + uint32_t *stream; + nxt_int_t res; + nxt_lvlhsh_query_t lhq; + + stream = &req_impl->stream; + + lhq.key_hash = nxt_murmur_hash2(stream, sizeof(*stream)); + lhq.key.length = sizeof(*stream); + lhq.key.start = (u_char *) stream; + lhq.proto = &lvlhsh_requests_proto; + lhq.pool = NULL; + lhq.replace = 0; + lhq.value = req_impl; + + res = nxt_lvlhsh_insert(request_hash, &lhq); + + switch (res) { + + case NXT_OK: + return NXT_UNIT_OK; + + default: + return NXT_UNIT_ERROR; + } +} + + +static nxt_unit_request_info_impl_t * +nxt_unit_request_hash_find(nxt_lvlhsh_t *request_hash, uint32_t stream, + int remove) +{ + nxt_int_t res; + nxt_lvlhsh_query_t lhq; + + lhq.key_hash = nxt_murmur_hash2(&stream, sizeof(stream)); + lhq.key.length = sizeof(stream); + lhq.key.start = (u_char *) &stream; + lhq.proto = &lvlhsh_requests_proto; + lhq.pool = NULL; + + if (remove) { + res = nxt_lvlhsh_delete(request_hash, &lhq); + + } else { + res = nxt_lvlhsh_find(request_hash, &lhq); + } + + switch (res) { + + case NXT_OK: + return lhq.value; + + default: + return NULL; + } +} + + void nxt_unit_log(nxt_unit_ctx_t *ctx, int level, const char *fmt, ...) { @@ -3526,8 +4144,7 @@ nxt_unit_req_log(nxt_unit_request_info_t *req, int level, const char *fmt, ...) if (nxt_fast_path(req != NULL)) { req_impl = nxt_container_of(req, nxt_unit_request_info_impl_t, req); - p += snprintf(p, end - p, - "#%"PRIu32": ", req_impl->recv_msg.port_msg.stream); + p += snprintf(p, end - p, "#%"PRIu32": ", req_impl->stream); } va_start(ap, fmt); diff --git a/src/nxt_unit.h b/src/nxt_unit.h index 532de20d..3471a758 100644 --- a/src/nxt_unit.h +++ b/src/nxt_unit.h @@ -9,6 +9,7 @@ #include #include +#include #include #include "nxt_version.h" @@ -106,17 +107,24 @@ struct nxt_unit_request_info_s { void *data; }; + /* * Set of application-specific callbacks. Application may leave all optional * callbacks as NULL. */ struct nxt_unit_callbacks_s { /* - * Process request data. Unlike all other callback, this callback + * Process request. Unlike all other callback, this callback * need to be defined by application. */ void (*request_handler)(nxt_unit_request_info_t *req); + /* Process websocket frame. */ + void (*websocket_handler)(nxt_unit_websocket_frame_t *ws); + + /* Connection closed. */ + void (*close_handler)(nxt_unit_request_info_t *req); + /* Add new Unit port to communicate with process pid. Optional. */ int (*add_port)(nxt_unit_ctx_t *, nxt_unit_port_t *port); @@ -293,6 +301,14 @@ int nxt_unit_response_is_sent(nxt_unit_request_info_t *req); nxt_unit_buf_t *nxt_unit_response_buf_alloc(nxt_unit_request_info_t *req, uint32_t size); +int nxt_unit_request_is_websocket_handshake(nxt_unit_request_info_t *req); + +int nxt_unit_response_upgrade(nxt_unit_request_info_t *req); + +int nxt_unit_response_is_websocket(nxt_unit_request_info_t *req); + +nxt_unit_request_info_t *nxt_unit_get_request_info_from_data(void *data); + int nxt_unit_buf_send(nxt_unit_buf_t *buf); void nxt_unit_buf_free(nxt_unit_buf_t *buf); @@ -315,6 +331,20 @@ ssize_t nxt_unit_request_read(nxt_unit_request_info_t *req, void *dst, void nxt_unit_request_done(nxt_unit_request_info_t *req, int rc); +int nxt_unit_websocket_send(nxt_unit_request_info_t *req, uint8_t opcode, + uint8_t last, const void *start, size_t size); + +int nxt_unit_websocket_sendv(nxt_unit_request_info_t *req, uint8_t opcode, + uint8_t last, const struct iovec *iov, int iovcnt); + +ssize_t nxt_unit_websocket_read(nxt_unit_websocket_frame_t *ws, void *dst, + size_t size); + +int nxt_unit_websocket_retain(nxt_unit_websocket_frame_t *ws); + +void nxt_unit_websocket_done(nxt_unit_websocket_frame_t *ws); + + void nxt_unit_log(nxt_unit_ctx_t *ctx, int level, const char* fmt, ...); void nxt_unit_req_log(nxt_unit_request_info_t *req, int level, diff --git a/src/nxt_unit_request.h b/src/nxt_unit_request.h index 2207cefa..52017a42 100644 --- a/src/nxt_unit_request.h +++ b/src/nxt_unit_request.h @@ -20,6 +20,7 @@ struct nxt_unit_request_s { uint8_t remote_length; uint8_t local_length; uint8_t tls; + uint8_t websocket_handshake; uint32_t server_name_length; uint32_t target_length; uint32_t path_length; diff --git a/src/nxt_unit_typedefs.h b/src/nxt_unit_typedefs.h index 871ce25b..26e54f91 100644 --- a/src/nxt_unit_typedefs.h +++ b/src/nxt_unit_typedefs.h @@ -7,19 +7,20 @@ #define _NXT_UNIT_TYPEDEFS_H_INCLUDED_ -typedef struct nxt_unit_s nxt_unit_t; -typedef struct nxt_unit_ctx_s nxt_unit_ctx_t; -typedef struct nxt_unit_port_id_s nxt_unit_port_id_t; -typedef struct nxt_unit_port_s nxt_unit_port_t; -typedef struct nxt_unit_buf_s nxt_unit_buf_t; -typedef struct nxt_unit_request_info_s nxt_unit_request_info_t; -typedef struct nxt_unit_callbacks_s nxt_unit_callbacks_t; -typedef struct nxt_unit_init_s nxt_unit_init_t; -typedef union nxt_unit_sptr_u nxt_unit_sptr_t; -typedef struct nxt_unit_field_s nxt_unit_field_t; -typedef struct nxt_unit_request_s nxt_unit_request_t; -typedef struct nxt_unit_response_s nxt_unit_response_t; -typedef struct nxt_unit_read_info_s nxt_unit_read_info_t; +typedef struct nxt_unit_s nxt_unit_t; +typedef struct nxt_unit_ctx_s nxt_unit_ctx_t; +typedef struct nxt_unit_port_id_s nxt_unit_port_id_t; +typedef struct nxt_unit_port_s nxt_unit_port_t; +typedef struct nxt_unit_buf_s nxt_unit_buf_t; +typedef struct nxt_unit_request_info_s nxt_unit_request_info_t; +typedef struct nxt_unit_callbacks_s nxt_unit_callbacks_t; +typedef struct nxt_unit_init_s nxt_unit_init_t; +typedef union nxt_unit_sptr_u nxt_unit_sptr_t; +typedef struct nxt_unit_field_s nxt_unit_field_t; +typedef struct nxt_unit_request_s nxt_unit_request_t; +typedef struct nxt_unit_response_s nxt_unit_response_t; +typedef struct nxt_unit_read_info_s nxt_unit_read_info_t; +typedef struct nxt_unit_websocket_frame_s nxt_unit_websocket_frame_t; #endif /* _NXT_UNIT_TYPEDEFS_H_INCLUDED_ */ diff --git a/src/nxt_unit_websocket.h b/src/nxt_unit_websocket.h new file mode 100644 index 00000000..beb2536e --- /dev/null +++ b/src/nxt_unit_websocket.h @@ -0,0 +1,27 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NXT_UNIT_WEBSOCKET_H_INCLUDED_ +#define _NXT_UNIT_WEBSOCKET_H_INCLUDED_ + +#include + +#include "nxt_unit_typedefs.h" +#include "nxt_websocket_header.h" + + +struct nxt_unit_websocket_frame_s { + nxt_unit_request_info_t *req; + + uint64_t payload_len; + nxt_websocket_header_t *header; + uint8_t *mask; + + nxt_unit_buf_t *content_buf; + uint64_t content_length; +}; + + +#endif /* _NXT_UNIT_WEBSOCKET_H_INCLUDED_ */ diff --git a/src/nxt_websocket.c b/src/nxt_websocket.c new file mode 100644 index 00000000..9a099760 --- /dev/null +++ b/src/nxt_websocket.c @@ -0,0 +1,122 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include + + +nxt_inline uint16_t +nxt_ntoh16(const uint8_t *b) +{ + return ((uint16_t) b[0]) << 8 | ((uint16_t) b[1]); +} + + +nxt_inline void +nxt_hton16(uint8_t *b, uint16_t v) +{ + b[0] = (v >> 8); + b[1] = (v & 0xFFu); +} + + +nxt_inline uint64_t +nxt_ntoh64(const uint8_t *b) +{ + return ((uint64_t) b[0]) << 56 + | ((uint64_t) b[1]) << 48 + | ((uint64_t) b[2]) << 40 + | ((uint64_t) b[3]) << 32 + | ((uint64_t) b[4]) << 24 + | ((uint64_t) b[5]) << 16 + | ((uint64_t) b[6]) << 8 + | ((uint64_t) b[7]); +} + + +nxt_inline void +nxt_hton64(uint8_t *b, uint64_t v) +{ + b[0] = (v >> 56); + b[1] = (v >> 48) & 0xFFu; + b[2] = (v >> 40) & 0xFFu; + b[3] = (v >> 32) & 0xFFu; + b[4] = (v >> 24) & 0xFFu; + b[5] = (v >> 16) & 0xFFu; + b[6] = (v >> 8) & 0xFFu; + b[7] = v & 0xFFu; +} + + +size_t +nxt_websocket_frame_header_size(const void *data) +{ + size_t res; + uint64_t p; + const nxt_websocket_header_t *h; + + h = data; + p = h->payload_len; + + res = 2; + + if (p == 126) { + res += 2; + } else if (p == 127) { + res += 8; + } + + if (h->mask) { + res += 4; + } + + return res; +} + + +uint64_t +nxt_websocket_frame_payload_len(const void *data) +{ + uint64_t p; + const nxt_websocket_header_t *h; + + h = data; + p = h->payload_len; + + if (p == 126) { + p = nxt_ntoh16(h->payload_len_); + } else if (p == 127) { + p = nxt_ntoh64(h->payload_len_); + } + + return p; +} + + +void * +nxt_websocket_frame_init(void *data, uint64_t payload_len) +{ + uint8_t *p; + nxt_websocket_header_t *h; + + h = data; + p = data; + + if (payload_len < 126) { + h->payload_len = payload_len; + return p + 2; + } + + if (payload_len < 65536) { + h->payload_len = 126; + nxt_hton16(h->payload_len_, payload_len); + return p + 4; + } + + h->payload_len = 127; + nxt_hton64(h->payload_len_, payload_len); + return p + 10; +} diff --git a/src/nxt_websocket.h b/src/nxt_websocket.h new file mode 100644 index 00000000..499a3268 --- /dev/null +++ b/src/nxt_websocket.h @@ -0,0 +1,21 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NXT_WEBSOCKET_H_INCLUDED_ +#define _NXT_WEBSOCKET_H_INCLUDED_ + + +enum { + NXT_WEBSOCKET_ACCEPT_SIZE = 28, +}; + + +NXT_EXPORT size_t nxt_websocket_frame_header_size(const void *data); +NXT_EXPORT uint64_t nxt_websocket_frame_payload_len(const void *data); +NXT_EXPORT void *nxt_websocket_frame_init(void *data, uint64_t payload_len); +NXT_EXPORT void nxt_websocket_accept(u_char *accept, const void *key); + + +#endif /* _NXT_WEBSOCKET_H_INCLUDED_ */ diff --git a/src/nxt_websocket_accept.c b/src/nxt_websocket_accept.c new file mode 100644 index 00000000..05cbcb56 --- /dev/null +++ b/src/nxt_websocket_accept.c @@ -0,0 +1,68 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include + + +static void +nxt_websocket_base64_encode(u_char *d, const uint8_t *s, size_t len) +{ + u_char c0, c1, c2; + static u_char basis[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + while (len > 2) { + c0 = s[0]; + c1 = s[1]; + c2 = s[2]; + + *d++ = basis[c0 >> 2]; + *d++ = basis[((c0 & 0x03) << 4) | (c1 >> 4)]; + *d++ = basis[((c1 & 0x0f) << 2) | (c2 >> 6)]; + *d++ = basis[c2 & 0x3f]; + + s += 3; + len -= 3; + } + + if (len > 0) { + c0 = s[0]; + *d++ = basis[c0 >> 2]; + + if (len == 1) { + *d++ = basis[(c0 & 0x03) << 4]; + *d++ = '='; + *d++ = '='; + + } else { + c1 = s[1]; + + *d++ = basis[((c0 & 0x03) << 4) | (c1 >> 4)]; + *d++ = basis[(c1 & 0x0f) << 2]; + + *d++ = '='; + } + } +} + + +void +nxt_websocket_accept(u_char *accept, const void *key) +{ + u_char bin_accept[20]; + nxt_sha1_t ctx; + static const char accept_guid[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + + nxt_sha1_init(&ctx); + nxt_sha1_update(&ctx, key, 24); + nxt_sha1_update(&ctx, accept_guid, nxt_length(accept_guid)); + nxt_sha1_final(bin_accept, &ctx); + + nxt_websocket_base64_encode(accept, bin_accept, sizeof(bin_accept)); +} + + diff --git a/src/nxt_websocket_header.h b/src/nxt_websocket_header.h new file mode 100644 index 00000000..f75dfacd --- /dev/null +++ b/src/nxt_websocket_header.h @@ -0,0 +1,68 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NXT_WEBSOCKET_HEADER_H_INCLUDED_ +#define _NXT_WEBSOCKET_HEADER_H_INCLUDED_ + +#include + + +typedef struct { +#if (BYTE_ORDER == BIG_ENDIAN) + uint8_t fin:1; + uint8_t rsv1:1; + uint8_t rsv2:1; + uint8_t rsv3:1; + uint8_t opcode:4; + + uint8_t mask:1; + uint8_t payload_len:7; +#endif + +#if (BYTE_ORDER == LITTLE_ENDIAN) + uint8_t opcode:4; + uint8_t rsv3:1; + uint8_t rsv2:1; + uint8_t rsv1:1; + uint8_t fin:1; + + uint8_t payload_len:7; + uint8_t mask:1; +#endif + + uint8_t payload_len_[8]; +} nxt_websocket_header_t; + + +enum { + NXT_WEBSOCKET_OP_CONT = 0x00, + NXT_WEBSOCKET_OP_TEXT = 0x01, + NXT_WEBSOCKET_OP_BINARY = 0x02, + NXT_WEBSOCKET_OP_CLOSE = 0x08, + NXT_WEBSOCKET_OP_PING = 0x09, + NXT_WEBSOCKET_OP_PONG = 0x0A, + + NXT_WEBSOCKET_OP_CTRL = 0x08, +}; + + +enum { + NXT_WEBSOCKET_CR_NORMAL = 1000, + NXT_WEBSOCKET_CR_GOING_AWAY = 1001, + NXT_WEBSOCKET_CR_PROTOCOL_ERROR = 1002, + NXT_WEBSOCKET_CR_UNPROCESSABLE_INPUT = 1003, + NXT_WEBSOCKET_CR_RESERVED = 1004, + NXT_WEBSOCKET_CR_NOT_PROVIDED = 1005, + NXT_WEBSOCKET_CR_ABNORMAL = 1006, + NXT_WEBSOCKET_CR_INVALID_DATA = 1007, + NXT_WEBSOCKET_CR_POLICY_VIOLATION = 1008, + NXT_WEBSOCKET_CR_MESSAGE_TOO_BIG = 1009, + NXT_WEBSOCKET_CR_EXTENSION_REQUIRED = 1010, + NXT_WEBSOCKET_CR_INTERNAL_SERVER_ERROR = 1011, + NXT_WEBSOCKET_CR_TLS_HANDSHAKE_FAILED = 1015, +}; + + +#endif /* _NXT_WEBSOCKET_HEADER_H_INCLUDED_ */ diff --git a/src/test/nxt_unit_websocket_chat.c b/src/test/nxt_unit_websocket_chat.c new file mode 100644 index 00000000..ecc9a243 --- /dev/null +++ b/src/test/nxt_unit_websocket_chat.c @@ -0,0 +1,348 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + + +#define CONTENT_TYPE "Content-Type" +#define CONTENT_LENGTH "Content-Length" +#define TEXT_HTML "text/html" + +typedef struct { + nxt_queue_link_t link; + int id; +} ws_chat_request_data_t; + + +static int ws_chat_root(nxt_unit_request_info_t *req); +static void ws_chat_broadcast(const void *buf, size_t size); + + +static const char ws_chat_index_html[]; +static const int ws_chat_index_html_size; + +static char ws_chat_index_content_length[34]; +static int ws_chat_index_content_length_size; + +static nxt_queue_t ws_chat_sessions; +static int ws_chat_next_id = 0; + + +static void +ws_chat_request_handler(nxt_unit_request_info_t *req) +{ + static char buf[1024]; + int buf_size; + int rc = NXT_UNIT_OK; + nxt_unit_request_t *r; + ws_chat_request_data_t *data; + + r = req->request; + + const char* target = nxt_unit_sptr_get(&r->target); + + if (strcmp(target, "/") == 0) { + rc = ws_chat_root(req); + goto fail; + } + + if (strcmp(target, "/chat") == 0) { + if (!nxt_unit_request_is_websocket_handshake(req)) { + goto notfound; + } + + rc = nxt_unit_response_init(req, 101, 0, 0); + if (nxt_slow_path(rc != NXT_UNIT_OK)) { + goto fail; + } + + data = req->data; + nxt_queue_insert_tail(&ws_chat_sessions, &data->link); + + data->id = ws_chat_next_id++; + + nxt_unit_response_upgrade(req); + nxt_unit_response_send(req); + + + buf_size = snprintf(buf, sizeof(buf), "Guest #%d has joined.", data->id); + + ws_chat_broadcast(buf, buf_size); + + return; + } + +notfound: + + rc = nxt_unit_response_init(req, 404, 0, 0); + +fail: + + nxt_unit_request_done(req, rc); +} + + +static int +ws_chat_root(nxt_unit_request_info_t *req) +{ + int rc; + + rc = nxt_unit_response_init(req, 200 /* Status code. */, + 2 /* Number of response headers. */, + nxt_length(CONTENT_TYPE) + 1 + + nxt_length(TEXT_HTML) + 1 + + nxt_length(CONTENT_LENGTH) + 1 + + ws_chat_index_content_length_size + 1 + + ws_chat_index_html_size); + if (nxt_slow_path(rc != NXT_UNIT_OK)) { + return rc; + } + + rc = nxt_unit_response_add_field(req, + CONTENT_TYPE, nxt_length(CONTENT_TYPE), + TEXT_HTML, nxt_length(TEXT_HTML)); + if (nxt_slow_path(rc != NXT_UNIT_OK)) { + return rc; + } + + rc = nxt_unit_response_add_field(req, + CONTENT_LENGTH, nxt_length(CONTENT_LENGTH), + ws_chat_index_content_length, + ws_chat_index_content_length_size); + if (nxt_slow_path(rc != NXT_UNIT_OK)) { + return rc; + } + + rc = nxt_unit_response_add_content(req, ws_chat_index_html, + ws_chat_index_html_size); + if (nxt_slow_path(rc != NXT_UNIT_OK)) { + return rc; + } + + return nxt_unit_response_send(req); +} + + +static void +ws_chat_broadcast(const void *buf, size_t size) +{ + ws_chat_request_data_t *data; + nxt_unit_request_info_t *req; + + nxt_unit_debug(NULL, "broadcast: %s", buf); + + nxt_queue_each(data, &ws_chat_sessions, ws_chat_request_data_t, link) { + + req = nxt_unit_get_request_info_from_data(data); + + nxt_unit_req_debug(req, "broadcast: %s", buf); + + nxt_unit_websocket_send(req, NXT_WEBSOCKET_OP_TEXT, 1, buf, size); + } nxt_queue_loop; +} + + +static void +ws_chat_websocket_handler(nxt_unit_websocket_frame_t *ws) +{ + int buf_size; + static char buf[1024]; + ws_chat_request_data_t *data; + + if (ws->header->opcode != NXT_WEBSOCKET_OP_TEXT) { + return; + } + + data = ws->req->data; + + buf_size = snprintf(buf, sizeof(buf), "Guest #%d: ", data->id); + + buf_size += nxt_unit_websocket_read(ws, buf + buf_size, + nxt_min(sizeof(buf), + ws->content_length)); + + ws_chat_broadcast(buf, buf_size); + + nxt_unit_websocket_done(ws); +} + + +static void +ws_chat_close_handler(nxt_unit_request_info_t *req) +{ + int buf_size; + static char buf[1024]; + ws_chat_request_data_t *data; + + data = req->data; + buf_size = snprintf(buf, sizeof(buf), "Guest #%d has disconnected.", + data->id); + + nxt_queue_remove(&data->link); + nxt_unit_request_done(req, NXT_UNIT_OK); + + ws_chat_broadcast(buf, buf_size); +} + + +int +main() +{ + nxt_unit_ctx_t *ctx; + nxt_unit_init_t init; + + ws_chat_index_content_length_size = + snprintf(ws_chat_index_content_length, + sizeof(ws_chat_index_content_length), "%d", + ws_chat_index_html_size); + + nxt_queue_init(&ws_chat_sessions); + + memset(&init, 0, sizeof(nxt_unit_init_t)); + + init.callbacks.request_handler = ws_chat_request_handler; + init.callbacks.websocket_handler = ws_chat_websocket_handler; + init.callbacks.close_handler = ws_chat_close_handler; + + init.request_data_size = sizeof(ws_chat_request_data_t); + + ctx = nxt_unit_init(&init); + if (ctx == NULL) { + return 1; + } + + nxt_unit_run(ctx); + + nxt_unit_done(ctx); + + return 0; +} + + +static const char ws_chat_index_html[] = +"\n" +"\n" +" WebSocket Chat Examples\n" +" \n" +" \n" +"\n" +"\n" +"\n" +"
\n" +"

\n" +"
\n" +"
\n" +"
\n" +"
\n" +"\n" +"\n" +; + +static const int ws_chat_index_html_size = nxt_length(ws_chat_index_html); diff --git a/src/test/nxt_unit_websocket_echo.c b/src/test/nxt_unit_websocket_echo.c new file mode 100644 index 00000000..2a89cdc0 --- /dev/null +++ b/src/test/nxt_unit_websocket_echo.c @@ -0,0 +1,105 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#include +#include + +#include +#include +#include +#include +#include + + +static void +ws_echo_request_handler(nxt_unit_request_info_t *req) +{ + int rc; + const char *target; + + rc = NXT_UNIT_OK; + target = nxt_unit_sptr_get(&req->request->target); + + if (strcmp(target, "/") == 0) { + if (!nxt_unit_request_is_websocket_handshake(req)) { + goto notfound; + } + + rc = nxt_unit_response_init(req, 101, 0, 0); + if (nxt_slow_path(rc != NXT_UNIT_OK)) { + goto fail; + } + + nxt_unit_response_upgrade(req); + nxt_unit_response_send(req); + + return; + } + +notfound: + + rc = nxt_unit_response_init(req, 404, 0, 0); + +fail: + + nxt_unit_request_done(req, rc); +} + + +static void +ws_echo_websocket_handler(nxt_unit_websocket_frame_t *ws) +{ + uint8_t opcode; + ssize_t size; + nxt_unit_request_info_t *req; + + static size_t buf_size = 0; + static uint8_t *buf = NULL; + + if (buf_size < ws->content_length) { + buf = realloc(buf, ws->content_length); + buf_size = ws->content_length; + } + + req = ws->req; + opcode = ws->header->opcode; + + if (opcode == NXT_WEBSOCKET_OP_PONG) { + nxt_unit_websocket_done(ws); + return; + } + + size = nxt_unit_websocket_read(ws, buf, ws->content_length); + + nxt_unit_websocket_send(req, opcode, ws->header->fin, buf, size); + nxt_unit_websocket_done(ws); + + if (opcode == NXT_WEBSOCKET_OP_CLOSE) { + nxt_unit_request_done(req, NXT_UNIT_OK); + } +} + + +int +main() +{ + nxt_unit_ctx_t *ctx; + nxt_unit_init_t init; + + memset(&init, 0, sizeof(nxt_unit_init_t)); + + init.callbacks.request_handler = ws_echo_request_handler; + init.callbacks.websocket_handler = ws_echo_websocket_handler; + + ctx = nxt_unit_init(&init); + if (ctx == NULL) { + return 1; + } + + nxt_unit_run(ctx); + nxt_unit_done(ctx); + + return 0; +} -- cgit From e291841b3379f8787a10ad4f91e4aeae2ae323a4 Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Tue, 20 Aug 2019 16:32:05 +0300 Subject: Node.js: introducing websocket support. --- src/nodejs/unit-http/http.js | 0 src/nodejs/unit-http/http_server.js | 76 ++- src/nodejs/unit-http/nxt_napi.h | 195 ++++++- src/nodejs/unit-http/package.json | 9 +- src/nodejs/unit-http/socket.js | 8 +- src/nodejs/unit-http/unit.cpp | 517 ++++++++++++----- src/nodejs/unit-http/unit.h | 29 +- src/nodejs/unit-http/utils.js | 73 +++ src/nodejs/unit-http/websocket.js | 14 + src/nodejs/unit-http/websocket_connection.js | 683 +++++++++++++++++++++++ src/nodejs/unit-http/websocket_frame.js | 11 + src/nodejs/unit-http/websocket_request.js | 509 +++++++++++++++++ src/nodejs/unit-http/websocket_router.js | 157 ++++++ src/nodejs/unit-http/websocket_router_request.js | 54 ++ src/nodejs/unit-http/websocket_server.js | 213 +++++++ 15 files changed, 2373 insertions(+), 175 deletions(-) mode change 100755 => 100644 src/nodejs/unit-http/http.js mode change 100755 => 100644 src/nodejs/unit-http/http_server.js mode change 100755 => 100644 src/nodejs/unit-http/socket.js create mode 100644 src/nodejs/unit-http/utils.js create mode 100644 src/nodejs/unit-http/websocket.js create mode 100644 src/nodejs/unit-http/websocket_connection.js create mode 100644 src/nodejs/unit-http/websocket_frame.js create mode 100644 src/nodejs/unit-http/websocket_request.js create mode 100644 src/nodejs/unit-http/websocket_router.js create mode 100644 src/nodejs/unit-http/websocket_router_request.js create mode 100644 src/nodejs/unit-http/websocket_server.js diff --git a/src/nodejs/unit-http/http.js b/src/nodejs/unit-http/http.js old mode 100755 new mode 100644 diff --git a/src/nodejs/unit-http/http_server.js b/src/nodejs/unit-http/http_server.js old mode 100755 new mode 100644 index 0fe5dd34..c42149a5 --- a/src/nodejs/unit-http/http_server.js +++ b/src/nodejs/unit-http/http_server.js @@ -8,16 +8,21 @@ const EventEmitter = require('events'); const http = require('http'); const util = require('util'); -const unit_lib = require('unit-http/build/Release/unit-http.node'); -const unit_socket = require('unit-http/socket'); - -const { Socket } = unit_socket; +const unit_lib = require('./build/Release/unit-http'); +const Socket = require('./socket'); +const WebSocketFrame = require('./websocket_frame'); function ServerResponse(req) { EventEmitter.call(this); this.headers = {}; + + this.server = req.server; + this._request = req; + req._response = this; + this.socket = req.socket; + this.connection = req.connection; } util.inherits(ServerResponse, EventEmitter); @@ -207,15 +212,23 @@ ServerResponse.prototype._implicitHeader = function _implicitHeader() { this.writeHead(this.statusCode); }; -ServerResponse.prototype._writeBody = function(chunk, encoding, callback) { - var contentLength = 0; +ServerResponse.prototype._send_headers = unit_lib.response_send_headers; +ServerResponse.prototype._sendHeaders = function _sendHeaders() { if (!this.headersSent) { - unit_lib.unit_response_headers(this, this.statusCode, this.headers, - this.headers_count, this.headers_len); + this._send_headers(this.statusCode, this.headers, this.headers_count, + this.headers_len); this.headersSent = true; } +}; + +ServerResponse.prototype._write = unit_lib.response_write; + +ServerResponse.prototype._writeBody = function(chunk, encoding, callback) { + var contentLength = 0; + + this._sendHeaders(); if (typeof chunk === 'function') { callback = chunk; @@ -238,7 +251,7 @@ ServerResponse.prototype._writeBody = function(chunk, encoding, callback) { contentLength = chunk.length; } - unit_lib.unit_response_write(this, chunk, contentLength); + this._write(chunk, contentLength); } if (typeof callback === 'function') { @@ -268,11 +281,13 @@ ServerResponse.prototype.write = function write(chunk, encoding, callback) { return true; }; +ServerResponse.prototype._end = unit_lib.response_end; + ServerResponse.prototype.end = function end(chunk, encoding, callback) { if (!this.finished) { this._writeBody(chunk, encoding, callback); - unit_lib.unit_response_end(this); + this._end(); this.finished = true; } @@ -280,10 +295,12 @@ ServerResponse.prototype.end = function end(chunk, encoding, callback) { return this; }; -function ServerRequest(server) { +function ServerRequest(server, socket) { EventEmitter.call(this); this.server = server; + this.socket = socket; + this.connection = socket; } util.inherits(ServerRequest, EventEmitter); @@ -339,8 +356,8 @@ ServerRequest.prototype.on = function on(ev, fn) { if (ev === "data") { process.nextTick(function () { - if (this.server.buffer.length !== 0) { - this.emit("data", this.server.buffer); + if (this._data.length !== 0) { + this.emit("data", this._data); } }.bind(this)); @@ -357,14 +374,27 @@ function Server(requestListener) { this.unit.createServer(); - this.socket = Socket; - this.request = ServerRequest; - this.response = ServerResponse; + this.Socket = Socket; + this.ServerRequest = ServerRequest; + this.ServerResponse = ServerResponse; + this.WebSocketFrame = WebSocketFrame; if (requestListener) { this.on('request', requestListener); } + + this._upgradeListenerCount = 0; + this.on('newListener', function(ev) { + if (ev === 'upgrade'){ + this._upgradeListenerCount++; + } + }).on('removeListener', function(ev) { + if (ev === 'upgrade') { + this._upgradeListenerCount--; + } + }); } + util.inherits(Server, EventEmitter); Server.prototype.setTimeout = function setTimeout(msecs, callback) { @@ -381,15 +411,13 @@ Server.prototype.listen = function () { this.unit.listen(); }; -Server.prototype.emit_events = function (server, req, res) { - req.server = server; - res.server = server; - req.res = res; - res.req = req; - - server.buffer = server.unit._read(req.socket.req_pointer); +Server.prototype.emit_request = function (req, res) { + if (req._websocket_handshake && this._upgradeListenerCount > 0) { + this.emit('upgrade', req, req.socket); - server.emit("request", req, res); + } else { + this.emit("request", req, res); + } process.nextTick(() => { req.emit("finish"); diff --git a/src/nodejs/unit-http/nxt_napi.h b/src/nodejs/unit-http/nxt_napi.h index 9bcf3a21..d9721a40 100644 --- a/src/nodejs/unit-http/nxt_napi.h +++ b/src/nodejs/unit-http/nxt_napi.h @@ -188,6 +188,21 @@ struct nxt_napi { } + inline void * + get_buffer_info(napi_value val, size_t &size) + { + void *res; + napi_status status; + + status = napi_get_buffer_info(env_, val, &res, &size); + if (status != napi_ok) { + throw exception("Failed to get buffer info"); + } + + return res; + } + + inline napi_value get_cb_info(napi_callback_info info, size_t &argc, napi_value *argv) { @@ -218,6 +233,23 @@ struct nxt_napi { } + inline napi_value + get_cb_info(napi_callback_info info, napi_value &arg) + { + size_t argc; + napi_value res; + + argc = 1; + res = get_cb_info(info, argc, &arg); + + if (argc != 1) { + throw exception("Wrong args count. Expected 1"); + } + + return res; + } + + inline napi_value get_element(napi_value obj, uint32_t i) { @@ -311,15 +343,22 @@ struct nxt_napi { inline nxt_unit_request_info_t * get_request_info(napi_value obj) { - int64_t n; + return (nxt_unit_request_info_t *) unwrap(obj); + } + + + inline uint32_t + get_value_bool(napi_value obj) + { + bool res; napi_status status; - status = napi_get_value_int64(env_, obj, &n); + status = napi_get_value_bool(env_, obj, &res); if (status != napi_ok) { - throw exception("Failed to get request pointer"); + throw exception("Failed to get bool"); } - return (nxt_unit_request_info_t *) (intptr_t) n; + return res; } @@ -353,6 +392,21 @@ struct nxt_napi { } + inline size_t + get_value_string_utf8(napi_value val, char *buf, size_t bufsize) + { + size_t res; + napi_status status; + + status = napi_get_value_string_utf8(env_, val, buf, bufsize, &res); + if (status != napi_ok) { + throw exception("Failed to get string utf8"); + } + + return res; + } + + inline bool is_array(napi_value val) { @@ -368,6 +422,21 @@ struct nxt_napi { } + inline bool + is_buffer(napi_value val) + { + bool res; + napi_status status; + + status = napi_is_buffer(env_, val, &res); + if (status != napi_ok) { + throw exception("Failed to confirm value is buffer"); + } + + return res; + } + + inline napi_value make_callback(napi_async_context ctx, napi_value val, napi_value func, int argc, const napi_value *argv) @@ -397,6 +466,41 @@ struct nxt_napi { } + inline napi_value + make_callback(napi_async_context ctx, napi_value val, napi_value func) + { + return make_callback(ctx, val, func, 0, NULL); + } + + + inline napi_value + make_callback(napi_async_context ctx, napi_value val, napi_value func, + napi_value arg1) + { + return make_callback(ctx, val, func, 1, &arg1); + } + + + inline napi_value + make_callback(napi_async_context ctx, napi_value val, napi_value func, + napi_value arg1, napi_value arg2) + { + napi_value args[2] = { arg1, arg2 }; + + return make_callback(ctx, val, func, 2, args); + } + + + inline napi_value + make_callback(napi_async_context ctx, napi_value val, napi_value func, + napi_value arg1, napi_value arg2, napi_value arg3) + { + napi_value args[3] = { arg1, arg2, arg3 }; + + return make_callback(ctx, val, func, 3, args); + } + + inline napi_value new_instance(napi_value ctor) { @@ -427,6 +531,22 @@ struct nxt_napi { } + inline napi_value + new_instance(napi_value ctor, napi_value param1, napi_value param2) + { + napi_value res; + napi_status status; + napi_value param[2] = { param1, param2 }; + + status = napi_new_instance(env_, ctor, 2, param, &res); + if (status != napi_ok) { + throw exception("Failed to create instance"); + } + + return res; + } + + inline void set_element(napi_value obj, uint32_t i, napi_value val) { @@ -472,8 +592,46 @@ struct nxt_napi { } + template inline void - set_named_property(napi_value obj, const char *name, intptr_t val) + set_named_property(napi_value obj, const char *name, T val) + { + set_named_property(obj, name, create(val)); + } + + + inline napi_value + create(int32_t val) + { + napi_value ptr; + napi_status status; + + status = napi_create_int32(env_, val, &ptr); + if (status != napi_ok) { + throw exception("Failed to create int32"); + } + + return ptr; + } + + + inline napi_value + create(uint32_t val) + { + napi_value ptr; + napi_status status; + + status = napi_create_uint32(env_, val, &ptr); + if (status != napi_ok) { + throw exception("Failed to create uint32"); + } + + return ptr; + } + + + inline napi_value + create(int64_t val) { napi_value ptr; napi_status status; @@ -483,7 +641,32 @@ struct nxt_napi { throw exception("Failed to create int64"); } - set_named_property(obj, name, ptr); + return ptr; + } + + + inline void + remove_wrap(napi_ref& ref) + { + if (ref != nullptr) { + remove_wrap(get_reference_value(ref)); + ref = nullptr; + } + } + + + inline void * + remove_wrap(napi_value val) + { + void *res; + napi_status status; + + status = napi_remove_wrap(env_, val, &res); + if (status != napi_ok) { + throw exception("Failed to remove_wrap"); + } + + return res; } diff --git a/src/nodejs/unit-http/package.json b/src/nodejs/unit-http/package.json index 6a6c00b4..7ee01346 100644 --- a/src/nodejs/unit-http/package.json +++ b/src/nodejs/unit-http/package.json @@ -14,7 +14,14 @@ "package.json", "socket.js", "binding.gyp", - "README.md" + "README.md", + "websocket.js", + "websocket_connection.js", + "websocket_frame.js", + "websocket_request.js", + "websocket_router.js", + "websocket_router_request.js", + "websocket_server.js" ], "scripts": { "clean": "node-gyp clean", diff --git a/src/nodejs/unit-http/socket.js b/src/nodejs/unit-http/socket.js old mode 100755 new mode 100644 index 6e836949..b1a3abb8 --- a/src/nodejs/unit-http/socket.js +++ b/src/nodejs/unit-http/socket.js @@ -7,7 +7,7 @@ const EventEmitter = require('events'); const util = require('util'); -const unit_lib = require('unit-http/build/Release/unit-http.node'); +const unit_lib = require('./build/Release/unit-http'); function Socket(options) { EventEmitter.call(this); @@ -89,7 +89,7 @@ Socket.prototype.setTimeout = function setTimeout(timeout, callback) { this.timeout = timeout; - this.on('timeout', callback); + // this.on('timeout', callback); return this; }; @@ -101,6 +101,4 @@ Socket.prototype.write = function write(data, encoding, callback) { }; -module.exports = { - Socket -}; +module.exports = Socket; diff --git a/src/nodejs/unit-http/unit.cpp b/src/nodejs/unit-http/unit.cpp index 3f66189a..ac10024c 100644 --- a/src/nodejs/unit-http/unit.cpp +++ b/src/nodejs/unit-http/unit.cpp @@ -10,6 +10,8 @@ #include +#include + napi_ref Unit::constructor_; @@ -20,17 +22,27 @@ struct nxt_nodejs_ctx_t { }; +struct req_data_t { + napi_ref sock_ref; + napi_ref resp_ref; + napi_ref conn_ref; +}; + + Unit::Unit(napi_env env, napi_value jsthis): nxt_napi(env), wrapper_(wrap(jsthis, this, destroy)), unit_ctx_(nullptr) { + nxt_unit_debug(NULL, "Unit::Unit()"); } Unit::~Unit() { delete_reference(wrapper_); + + nxt_unit_debug(NULL, "Unit::~Unit()"); } @@ -38,23 +50,26 @@ napi_value Unit::init(napi_env env, napi_value exports) { nxt_napi napi(env); - napi_value cons; + napi_value ctor; - napi_property_descriptor properties[] = { + napi_property_descriptor unit_props[] = { { "createServer", 0, create_server, 0, 0, 0, napi_default, 0 }, { "listen", 0, listen, 0, 0, 0, napi_default, 0 }, - { "_read", 0, _read, 0, 0, 0, napi_default, 0 } }; try { - cons = napi.define_class("Unit", create, 3, properties); - constructor_ = napi.create_reference(cons); + ctor = napi.define_class("Unit", create, 2, unit_props); + constructor_ = napi.create_reference(ctor); - napi.set_named_property(exports, "Unit", cons); - napi.set_named_property(exports, "unit_response_headers", + napi.set_named_property(exports, "Unit", ctor); + napi.set_named_property(exports, "response_send_headers", response_send_headers); - napi.set_named_property(exports, "unit_response_write", response_write); - napi.set_named_property(exports, "unit_response_end", response_end); + napi.set_named_property(exports, "response_write", response_write); + napi.set_named_property(exports, "response_end", response_end); + napi.set_named_property(exports, "websocket_send_frame", + websocket_send_frame); + napi.set_named_property(exports, "websocket_set_sock", + websocket_set_sock); } catch (exception &e) { napi.throw_error(e); @@ -78,7 +93,7 @@ napi_value Unit::create(napi_env env, napi_callback_info info) { nxt_napi napi(env); - napi_value target, cons, instance, jsthis; + napi_value target, ctor, instance, jsthis; try { target = napi.get_new_target(info); @@ -94,8 +109,8 @@ Unit::create(napi_env env, napi_callback_info info) } /* Invoked as plain function `Unit(...)`, turn into construct call. */ - cons = napi.get_reference_value(constructor_); - instance = napi.new_instance(cons); + ctor = napi.get_reference_value(constructor_); + instance = napi.new_instance(ctor); napi.create_reference(instance); } catch (exception &e) { @@ -130,10 +145,14 @@ Unit::create_server(napi_env env, napi_callback_info info) memset(&unit_init, 0, sizeof(nxt_unit_init_t)); unit_init.data = obj; - unit_init.callbacks.request_handler = request_handler; - unit_init.callbacks.add_port = add_port; - unit_init.callbacks.remove_port = remove_port; - unit_init.callbacks.quit = quit; + unit_init.callbacks.request_handler = request_handler_cb; + unit_init.callbacks.websocket_handler = websocket_handler_cb; + unit_init.callbacks.close_handler = close_handler_cb; + unit_init.callbacks.add_port = add_port; + unit_init.callbacks.remove_port = remove_port; + unit_init.callbacks.quit = quit_cb; + + unit_init.request_data_size = sizeof(req_data_t); obj->unit_ctx_ = nxt_unit_init(&unit_init); if (obj->unit_ctx_ == NULL) { @@ -157,74 +176,139 @@ Unit::listen(napi_env env, napi_callback_info info) } -napi_value -Unit::_read(napi_env env, napi_callback_info info) +void +Unit::request_handler_cb(nxt_unit_request_info_t *req) { - void *data; - size_t argc; - nxt_napi napi(env); - napi_value buffer, argv; - nxt_unit_request_info_t *req; + Unit *obj; - argc = 1; + obj = reinterpret_cast(req->unit->data); + + obj->request_handler(req); +} + + +void +Unit::request_handler(nxt_unit_request_info_t *req) +{ + napi_value socket, request, response, server_obj, emit_request; + + memset(req->data, 0, sizeof(req_data_t)); try { - napi.get_cb_info(info, argc, &argv); + nxt_handle_scope scope(env()); + + server_obj = get_server_object(); + + socket = create_socket(server_obj, req); + request = create_request(server_obj, socket); + response = create_response(server_obj, request, req); + + create_headers(req, request); - req = napi.get_request_info(argv); - buffer = napi.create_buffer((size_t) req->content_length, &data); + emit_request = get_named_property(server_obj, "emit_request"); + + nxt_async_context async_context(env(), "request_handler"); + nxt_callback_scope async_scope(async_context); + + make_callback(async_context, server_obj, emit_request, request, + response); } catch (exception &e) { - napi.throw_error(e); - return nullptr; + nxt_unit_req_warn(req, "request_handler: %s", e.str); } +} - nxt_unit_request_read(req, data, req->content_length); - return buffer; +void +Unit::websocket_handler_cb(nxt_unit_websocket_frame_t *ws) +{ + Unit *obj; + + obj = reinterpret_cast(ws->req->unit->data); + + obj->websocket_handler(ws); } void -Unit::request_handler(nxt_unit_request_info_t *req) +Unit::websocket_handler(nxt_unit_websocket_frame_t *ws) { - Unit *obj; - napi_value socket, request, response, server_obj; - napi_value emit_events; - napi_value events_args[3]; + napi_value frame, server_obj, process_frame, conn; + req_data_t *req_data; - obj = reinterpret_cast(req->unit->data); + req_data = (req_data_t *) ws->req->data; try { - nxt_handle_scope scope(obj->env()); - - server_obj = obj->get_server_object(); + nxt_handle_scope scope(env()); - socket = obj->create_socket(server_obj, req); - request = obj->create_request(server_obj, socket); - response = obj->create_response(server_obj, socket, request, req); + server_obj = get_server_object(); - obj->create_headers(req, request); + frame = create_websocket_frame(server_obj, ws); - emit_events = obj->get_named_property(server_obj, "emit_events"); + conn = get_reference_value(req_data->conn_ref); - events_args[0] = server_obj; - events_args[1] = request; - events_args[2] = response; + process_frame = get_named_property(conn, "processFrame"); - nxt_async_context async_context(obj->env(), "unit_request_handler"); + nxt_async_context async_context(env(), "websocket_handler"); nxt_callback_scope async_scope(async_context); - obj->make_callback(async_context, server_obj, emit_events, - 3, events_args); + make_callback(async_context, conn, process_frame, frame); } catch (exception &e) { - obj->throw_error(e); + nxt_unit_req_warn(ws->req, "websocket_handler: %s", e.str); } + + nxt_unit_websocket_done(ws); +} + + +void +Unit::close_handler_cb(nxt_unit_request_info_t *req) +{ + Unit *obj; + + obj = reinterpret_cast(req->unit->data); + + obj->close_handler(req); } void +Unit::close_handler(nxt_unit_request_info_t *req) +{ + napi_value conn_handle_close, conn; + req_data_t *req_data; + + req_data = (req_data_t *) req->data; + + try { + nxt_handle_scope scope(env()); + + conn = get_reference_value(req_data->conn_ref); + + conn_handle_close = get_named_property(conn, "handleSocketClose"); + + nxt_async_context async_context(env(), "close_handler"); + nxt_callback_scope async_scope(async_context); + + make_callback(async_context, conn, conn_handle_close, + nxt_napi::create(0)); + + remove_wrap(req_data->sock_ref); + remove_wrap(req_data->resp_ref); + remove_wrap(req_data->conn_ref); + + } catch (exception &e) { + nxt_unit_req_warn(req, "close_handler: %s", e.str); + + return; + } + + nxt_unit_request_done(req, NXT_UNIT_OK); +} + + +static void nxt_uv_read_callback(uv_poll_t *handle, int status, int events) { nxt_unit_run_once((nxt_unit_ctx_t *) handle->data); @@ -244,14 +328,14 @@ Unit::add_port(nxt_unit_ctx_t *ctx, nxt_unit_port_t *port) obj = reinterpret_cast(ctx->unit->data); if (fcntl(port->in_fd, F_SETFL, O_NONBLOCK) == -1) { - obj->throw_error("Failed to upgrade read" - " file descriptor to O_NONBLOCK"); + nxt_unit_warn(ctx, "fcntl(%d, O_NONBLOCK) failed: %s (%d)", + port->in_fd, strerror(errno), errno); return -1; } status = napi_get_uv_event_loop(obj->env(), &loop); if (status != napi_ok) { - obj->throw_error("Failed to get uv.loop"); + nxt_unit_warn(ctx, "Failed to get uv.loop"); return NXT_UNIT_ERROR; } @@ -259,13 +343,13 @@ Unit::add_port(nxt_unit_ctx_t *ctx, nxt_unit_port_t *port) err = uv_poll_init(loop, &node_ctx->poll, port->in_fd); if (err < 0) { - obj->throw_error("Failed to init uv.poll"); + nxt_unit_warn(ctx, "Failed to init uv.poll"); return NXT_UNIT_ERROR; } err = uv_poll_start(&node_ctx->poll, UV_READABLE, nxt_uv_read_callback); if (err < 0) { - obj->throw_error("Failed to start uv.poll"); + nxt_unit_warn(ctx, "Failed to start uv.poll"); return NXT_UNIT_ERROR; } @@ -308,27 +392,35 @@ Unit::remove_port(nxt_unit_ctx_t *ctx, nxt_unit_port_id_t *port_id) void -Unit::quit(nxt_unit_ctx_t *ctx) +Unit::quit_cb(nxt_unit_ctx_t *ctx) { - Unit *obj; - napi_value server_obj, emit_close; + Unit *obj; obj = reinterpret_cast(ctx->unit->data); + obj->quit(ctx); +} + + +void +Unit::quit(nxt_unit_ctx_t *ctx) +{ + napi_value server_obj, emit_close; + try { - nxt_handle_scope scope(obj->env()); + nxt_handle_scope scope(env()); - server_obj = obj->get_server_object(); + server_obj = get_server_object(); - emit_close = obj->get_named_property(server_obj, "emit_close"); + emit_close = get_named_property(server_obj, "emit_close"); - nxt_async_context async_context(obj->env(), "unit_quit"); + nxt_async_context async_context(env(), "unit_quit"); nxt_callback_scope async_scope(async_context); - obj->make_callback(async_context, server_obj, emit_close, 0, NULL); + make_callback(async_context, server_obj, emit_close); } catch (exception &e) { - obj->throw_error(e); + nxt_unit_debug(ctx, "quit: %s", e.str); } nxt_unit_done(ctx); @@ -349,8 +441,9 @@ Unit::get_server_object() void Unit::create_headers(nxt_unit_request_info_t *req, napi_value request) { + void *data; uint32_t i; - napi_value headers, raw_headers; + napi_value headers, raw_headers, buffer; napi_status status; nxt_unit_request_t *r; @@ -373,6 +466,13 @@ Unit::create_headers(nxt_unit_request_info_t *req, napi_value request) set_named_property(request, "httpVersion", r->version, r->version_length); set_named_property(request, "method", r->method, r->method_length); set_named_property(request, "url", r->target, r->target_length); + + set_named_property(request, "_websocket_handshake", r->websocket_handshake); + + buffer = create_buffer((size_t) req->content_length, &data); + nxt_unit_request_read(req, data, req->content_length); + + set_named_property(request, "_data", buffer); } @@ -410,15 +510,18 @@ napi_value Unit::create_socket(napi_value server_obj, nxt_unit_request_info_t *req) { napi_value constructor, res; + req_data_t *req_data; nxt_unit_request_t *r; r = req->request; - constructor = get_named_property(server_obj, "socket"); + constructor = get_named_property(server_obj, "Socket"); res = new_instance(constructor); - set_named_property(res, "req_pointer", (intptr_t) req); + req_data = (req_data_t *) req->data; + req_data->sock_ref = wrap(res, req, sock_destroy); + set_named_property(res, "remoteAddress", r->remote, r->remote_length); set_named_property(res, "localAddress", r->local, r->local_length); @@ -429,34 +532,66 @@ Unit::create_socket(napi_value server_obj, nxt_unit_request_info_t *req) napi_value Unit::create_request(napi_value server_obj, napi_value socket) { - napi_value constructor, return_val; + napi_value constructor; - constructor = get_named_property(server_obj, "request"); + constructor = get_named_property(server_obj, "ServerRequest"); - return_val = new_instance(constructor, server_obj); + return new_instance(constructor, server_obj, socket); +} - set_named_property(return_val, "socket", socket); - set_named_property(return_val, "connection", socket); - return return_val; +napi_value +Unit::create_response(napi_value server_obj, napi_value request, + nxt_unit_request_info_t *req) +{ + napi_value constructor, res; + req_data_t *req_data; + + constructor = get_named_property(server_obj, "ServerResponse"); + + res = new_instance(constructor, request); + + req_data = (req_data_t *) req->data; + req_data->resp_ref = wrap(res, req, resp_destroy); + + return res; } napi_value -Unit::create_response(napi_value server_obj, napi_value socket, - napi_value request, nxt_unit_request_info_t *req) +Unit::create_websocket_frame(napi_value server_obj, + nxt_unit_websocket_frame_t *ws) { - napi_value constructor, return_val; + void *data; + napi_value constructor, res, buffer; + uint8_t sc[2]; + + constructor = get_named_property(server_obj, "WebSocketFrame"); - constructor = get_named_property(server_obj, "response"); + res = new_instance(constructor); - return_val = new_instance(constructor, request); + set_named_property(res, "fin", (bool) ws->header->fin); + set_named_property(res, "opcode", ws->header->opcode); + set_named_property(res, "length", (int64_t) ws->payload_len); - set_named_property(return_val, "socket", socket); - set_named_property(return_val, "connection", socket); - set_named_property(return_val, "_req_point", (intptr_t) req); + if (ws->header->opcode == NXT_WEBSOCKET_OP_CLOSE) { + if (ws->payload_len >= 2) { + nxt_unit_websocket_read(ws, sc, 2); - return return_val; + set_named_property(res, "closeStatus", + (((uint16_t) sc[0]) << 8) | sc[1]); + + } else { + set_named_property(res, "closeStatus", -1); + } + } + + buffer = create_buffer((size_t) ws->content_length, &data); + nxt_unit_websocket_read(ws, data, ws->content_length); + + set_named_property(res, "binaryPayload", buffer); + + return res; } @@ -472,35 +607,32 @@ Unit::response_send_headers(napi_env env, napi_callback_info info) uint16_t hash; nxt_napi napi(env); napi_value this_arg, headers, keys, name, value, array_val; - napi_value req_num, array_entry; + napi_value array_entry; napi_valuetype val_type; nxt_unit_field_t *f; nxt_unit_request_info_t *req; - napi_value argv[5]; + napi_value argv[4]; - argc = 5; + argc = 4; try { this_arg = napi.get_cb_info(info, argc, argv); - if (argc != 5) { + if (argc != 4) { napi.throw_error("Wrong args count. Expected: " "statusCode, headers, headers count, " "headers length"); return nullptr; } - req_num = napi.get_named_property(argv[0], "_req_point"); - - req = napi.get_request_info(req_num); - - status_code = napi.get_value_uint32(argv[1]); - keys_count = napi.get_value_uint32(argv[3]); - header_len = napi.get_value_uint32(argv[4]); + req = napi.get_request_info(this_arg); + status_code = napi.get_value_uint32(argv[0]); + keys_count = napi.get_value_uint32(argv[2]); + header_len = napi.get_value_uint32(argv[3]); /* Need to reserve extra byte for C-string 0-termination. */ header_len++; - headers = argv[2]; + headers = argv[1]; ret = nxt_unit_response_init(req, status_code, keys_count, header_len); if (ret != NXT_UNIT_OK) { @@ -611,65 +743,151 @@ napi_value Unit::response_write(napi_env env, napi_callback_info info) { int ret; - char *ptr; + void *ptr; size_t argc, have_buf_len; uint32_t buf_len; nxt_napi napi(env); - napi_value this_arg, req_num; - napi_status status; + napi_value this_arg; nxt_unit_buf_t *buf; napi_valuetype buf_type; nxt_unit_request_info_t *req; - napi_value argv[3]; + napi_value argv[2]; - argc = 3; + argc = 2; try { this_arg = napi.get_cb_info(info, argc, argv); - if (argc != 3) { + if (argc != 2) { throw exception("Wrong args count. Expected: " "chunk, chunk length"); } - req_num = napi.get_named_property(argv[0], "_req_point"); - req = napi.get_request_info(req_num); + req = napi.get_request_info(this_arg); + buf_type = napi.type_of(argv[0]); + buf_len = napi.get_value_uint32(argv[1]) + 1; + + buf = nxt_unit_response_buf_alloc(req, buf_len); + if (buf == NULL) { + throw exception("Failed to allocate response buffer"); + } + + if (buf_type == napi_string) { + /* TODO: will work only for utf8 content-type */ + + have_buf_len = napi.get_value_string_utf8(argv[0], buf->free, + buf_len); - buf_len = napi.get_value_uint32(argv[2]); + } else { + ptr = napi.get_buffer_info(argv[0], have_buf_len); - buf_type = napi.type_of(argv[1]); + memcpy(buf->free, ptr, have_buf_len); + } + buf->free += have_buf_len; + + ret = nxt_unit_buf_send(buf); + if (ret != NXT_UNIT_OK) { + throw exception("Failed to send body buf"); + } } catch (exception &e) { napi.throw_error(e); return nullptr; } - buf_len++; + return this_arg; +} - buf = nxt_unit_response_buf_alloc(req, buf_len); - if (buf == NULL) { - goto failed; - } - if (buf_type == napi_string) { - /* TODO: will work only for utf8 content-type */ +napi_value +Unit::response_end(napi_env env, napi_callback_info info) +{ + nxt_napi napi(env); + napi_value this_arg; + req_data_t *req_data; + nxt_unit_request_info_t *req; + + try { + this_arg = napi.get_cb_info(info); + + req = napi.get_request_info(this_arg); - status = napi_get_value_string_utf8(env, argv[1], buf->free, - buf_len, &have_buf_len); + req_data = (req_data_t *) req->data; - } else { - status = napi_get_buffer_info(env, argv[1], (void **) &ptr, - &have_buf_len); + napi.remove_wrap(req_data->sock_ref); + napi.remove_wrap(req_data->resp_ref); + napi.remove_wrap(req_data->conn_ref); - memcpy(buf->free, ptr, have_buf_len); + } catch (exception &e) { + napi.throw_error(e); + return nullptr; } - if (status != napi_ok) { - goto failed; + nxt_unit_request_done(req, NXT_UNIT_OK); + + return this_arg; +} + + +napi_value +Unit::websocket_send_frame(napi_env env, napi_callback_info info) +{ + int ret, iovec_len; + bool fin; + size_t buf_len; + uint32_t opcode, sc; + nxt_napi napi(env); + napi_value this_arg, frame, payload; + nxt_unit_request_info_t *req; + char status_code[2]; + struct iovec iov[2]; + + iovec_len = 0; + + try { + this_arg = napi.get_cb_info(info, frame); + + req = napi.get_request_info(this_arg); + + opcode = napi.get_value_uint32(napi.get_named_property(frame, + "opcode")); + if (opcode == NXT_WEBSOCKET_OP_CLOSE) { + sc = napi.get_value_uint32(napi.get_named_property(frame, + "closeStatus")); + status_code[0] = (sc >> 8) & 0xFF; + status_code[1] = sc & 0xFF; + + iov[iovec_len].iov_base = status_code; + iov[iovec_len].iov_len = 2; + iovec_len++; + } + + try { + fin = napi.get_value_bool(napi.get_named_property(frame, "fin")); + + } catch (exception &e) { + fin = true; + } + + payload = napi.get_named_property(frame, "binaryPayload"); + + if (napi.is_buffer(payload)) { + iov[iovec_len].iov_base = napi.get_buffer_info(payload, buf_len); + + } else { + buf_len = 0; + } + + } catch (exception &e) { + napi.throw_error(e); + return nullptr; } - buf->free += have_buf_len; + if (buf_len > 0) { + iov[iovec_len].iov_len = buf_len; + iovec_len++; + } - ret = nxt_unit_buf_send(buf); + ret = nxt_unit_websocket_sendv(req, opcode, fin ? 1 : 0, iov, iovec_len); if (ret != NXT_UNIT_OK) { goto failed; } @@ -678,34 +896,65 @@ Unit::response_write(napi_env env, napi_callback_info info) failed: - napi.throw_error("Failed to write body"); + napi.throw_error("Failed to send frame"); return nullptr; } napi_value -Unit::response_end(napi_env env, napi_callback_info info) +Unit::websocket_set_sock(napi_env env, napi_callback_info info) { - size_t argc; nxt_napi napi(env); - napi_value resp, this_arg, req_num; + napi_value this_arg, sock; + req_data_t *req_data; nxt_unit_request_info_t *req; - argc = 1; - try { - this_arg = napi.get_cb_info(info, argc, &resp); + this_arg = napi.get_cb_info(info, sock); - req_num = napi.get_named_property(resp, "_req_point"); - req = napi.get_request_info(req_num); + req = napi.get_request_info(sock); + + req_data = (req_data_t *) req->data; + req_data->conn_ref = napi.wrap(this_arg, req, conn_destroy); } catch (exception &e) { napi.throw_error(e); return nullptr; } - nxt_unit_request_done(req, NXT_UNIT_OK); - return this_arg; } + + +void +Unit::conn_destroy(napi_env env, void *nativeObject, void *finalize_hint) +{ + nxt_unit_request_info_t *req; + + req = (nxt_unit_request_info_t *) nativeObject; + + nxt_unit_warn(NULL, "conn_destroy: %p", req); +} + + +void +Unit::sock_destroy(napi_env env, void *nativeObject, void *finalize_hint) +{ + nxt_unit_request_info_t *req; + + req = (nxt_unit_request_info_t *) nativeObject; + + nxt_unit_warn(NULL, "sock_destroy: %p", req); +} + + +void +Unit::resp_destroy(napi_env env, void *nativeObject, void *finalize_hint) +{ + nxt_unit_request_info_t *req; + + req = (nxt_unit_request_info_t *) nativeObject; + + nxt_unit_warn(NULL, "resp_destroy: %p", req); +} diff --git a/src/nodejs/unit-http/unit.h b/src/nodejs/unit-http/unit.h index e76d805a..f5eaf9fd 100644 --- a/src/nodejs/unit-http/unit.h +++ b/src/nodejs/unit-http/unit.h @@ -19,14 +19,28 @@ private: static napi_value create(napi_env env, napi_callback_info info); static void destroy(napi_env env, void *nativeObject, void *finalize_hint); + static void conn_destroy(napi_env env, void *nativeObject, void *finalize_hint); + static void sock_destroy(napi_env env, void *nativeObject, void *finalize_hint); + static void resp_destroy(napi_env env, void *nativeObject, void *finalize_hint); static napi_value create_server(napi_env env, napi_callback_info info); static napi_value listen(napi_env env, napi_callback_info info); static napi_value _read(napi_env env, napi_callback_info info); - static void request_handler(nxt_unit_request_info_t *req); + + static void request_handler_cb(nxt_unit_request_info_t *req); + void request_handler(nxt_unit_request_info_t *req); + + static void websocket_handler_cb(nxt_unit_websocket_frame_t *ws); + void websocket_handler(nxt_unit_websocket_frame_t *ws); + + static void close_handler_cb(nxt_unit_request_info_t *req); + void close_handler(nxt_unit_request_info_t *req); + static int add_port(nxt_unit_ctx_t *ctx, nxt_unit_port_t *port); static void remove_port(nxt_unit_ctx_t *ctx, nxt_unit_port_id_t *port_id); - static void quit(nxt_unit_ctx_t *ctx); + + static void quit_cb(nxt_unit_ctx_t *ctx); + void quit(nxt_unit_ctx_t *ctx); napi_value get_server_object(); @@ -35,20 +49,25 @@ private: napi_value create_request(napi_value server_obj, napi_value socket); - napi_value create_response(napi_value server_obj, napi_value socket, - napi_value request, + napi_value create_response(napi_value server_obj, napi_value request, nxt_unit_request_info_t *req); + napi_value create_websocket_frame(napi_value server_obj, + nxt_unit_websocket_frame_t *ws); + static napi_value response_send_headers(napi_env env, napi_callback_info info); static napi_value response_write(napi_env env, napi_callback_info info); static napi_value response_end(napi_env env, napi_callback_info info); + static napi_value websocket_send_frame(napi_env env, + napi_callback_info info); + static napi_value websocket_set_sock(napi_env env, napi_callback_info info); void create_headers(nxt_unit_request_info_t *req, napi_value request); void append_header(nxt_unit_field_t *f, napi_value headers, - napi_value raw_headers, uint32_t idx); + napi_value raw_headers, uint32_t idx); static napi_ref constructor_; diff --git a/src/nodejs/unit-http/utils.js b/src/nodejs/unit-http/utils.js new file mode 100644 index 00000000..e1e51b0e --- /dev/null +++ b/src/nodejs/unit-http/utils.js @@ -0,0 +1,73 @@ +var noop = exports.noop = function(){}; + +exports.extend = function extend(dest, source) { + for (var prop in source) { + dest[prop] = source[prop]; + } +}; + +exports.eventEmitterListenerCount = + require('events').EventEmitter.listenerCount || + function(emitter, type) { return emitter.listeners(type).length; }; + +exports.bufferAllocUnsafe = Buffer.allocUnsafe ? + Buffer.allocUnsafe : + function oldBufferAllocUnsafe(size) { return new Buffer(size); }; + +exports.bufferFromString = Buffer.from ? + Buffer.from : + function oldBufferFromString(string, encoding) { + return new Buffer(string, encoding); + }; + +exports.BufferingLogger = function createBufferingLogger(identifier, uniqueID) { + try { + var logFunction = require('debug')(identifier); + } + catch(e) { + logFunction = noop; + logFunction.enabled = false; + } + + if (logFunction.enabled) { + var logger = new BufferingLogger(identifier, uniqueID, logFunction); + var debug = logger.log.bind(logger); + debug.printOutput = logger.printOutput.bind(logger); + debug.enabled = logFunction.enabled; + return debug; + } + logFunction.printOutput = noop; + return logFunction; +}; + +function BufferingLogger(identifier, uniqueID, logFunction) { + this.logFunction = logFunction; + this.identifier = identifier; + this.uniqueID = uniqueID; + this.buffer = []; +} + +BufferingLogger.prototype.log = function() { + this.buffer.push([ new Date(), Array.prototype.slice.call(arguments) ]); + return this; +}; + +BufferingLogger.prototype.clear = function() { + this.buffer = []; + return this; +}; + +BufferingLogger.prototype.printOutput = function(logFunction) { + if (!logFunction) { logFunction = this.logFunction; } + var uniqueID = this.uniqueID; + this.buffer.forEach(function(entry) { + var date = entry[0].toLocaleString(); + var args = entry[1].slice(); + var formatString = args[0]; + if (formatString !== (void 0) && formatString !== null) { + formatString = '%s - %s - ' + formatString.toString(); + args.splice(0, 1, formatString, date, uniqueID); + logFunction.apply(global, args); + } + }); +}; diff --git a/src/nodejs/unit-http/websocket.js b/src/nodejs/unit-http/websocket.js new file mode 100644 index 00000000..36d0e07a --- /dev/null +++ b/src/nodejs/unit-http/websocket.js @@ -0,0 +1,14 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +'use strict'; + +module.exports = { + 'server' : require('./websocket_server'), + 'router' : require('./websocket_router'), + 'frame' : require('./websocket_frame'), + 'request' : require('./websocket_request'), + 'connection' : require('./websocket_connection'), +}; diff --git a/src/nodejs/unit-http/websocket_connection.js b/src/nodejs/unit-http/websocket_connection.js new file mode 100644 index 00000000..4eccf6bf --- /dev/null +++ b/src/nodejs/unit-http/websocket_connection.js @@ -0,0 +1,683 @@ +/************************************************************************ + * Copyright 2010-2015 Brian McKelvey. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***********************************************************************/ + +var util = require('util'); +var utils = require('./utils'); +var unit_lib = require('./build/Release/unit-http'); +var EventEmitter = require('events').EventEmitter; +var WebSocketFrame = require('./websocket_frame'); +var bufferAllocUnsafe = utils.bufferAllocUnsafe; +var bufferFromString = utils.bufferFromString; + +// Connected, fully-open, ready to send and receive frames +const STATE_OPEN = 'open'; +// Received a close frame from the remote peer +const STATE_PEER_REQUESTED_CLOSE = 'peer_requested_close'; +// Sent close frame to remote peer. No further data can be sent. +const STATE_ENDING = 'ending'; +// Connection is fully closed. No further data can be sent or received. +const STATE_CLOSED = 'closed'; + +var idCounter = 0; + +function WebSocketConnection(socket, extensions, protocol, maskOutgoingPackets, config) { + this._debug = utils.BufferingLogger('websocket:connection', ++idCounter); + this._debug('constructor'); + + if (this._debug.enabled) { + instrumentSocketForDebugging(this, socket); + } + + // Superclass Constructor + EventEmitter.call(this); + + this._pingListenerCount = 0; + this.on('newListener', function(ev) { + if (ev === 'ping'){ + this._pingListenerCount++; + } + }).on('removeListener', function(ev) { + if (ev === 'ping') { + this._pingListenerCount--; + } + }); + + this.config = config; + this.socket = socket; + this.protocol = protocol; + this.extensions = extensions; + this.remoteAddress = socket.remoteAddress; + this.closeReasonCode = -1; + this.closeDescription = null; + this.closeEventEmitted = false; + + // We have to mask outgoing packets if we're acting as a WebSocket client. + this.maskOutgoingPackets = maskOutgoingPackets; + + this.fragmentationSize = 0; // data received so far... + this.frameQueue = []; + + // Various bits of connection state + this.connected = true; + this.state = STATE_OPEN; + this.waitingForCloseResponse = false; + // Received TCP FIN, socket's readable stream is finished. + this.receivedEnd = false; + + this.closeTimeout = this.config.closeTimeout; + this.assembleFragments = this.config.assembleFragments; + this.maxReceivedMessageSize = this.config.maxReceivedMessageSize; + + this.outputBufferFull = false; + this.inputPaused = false; + this._closeTimerHandler = this.handleCloseTimer.bind(this); + + // Disable nagle algorithm? + this.socket.setNoDelay(this.config.disableNagleAlgorithm); + + // Make sure there is no socket inactivity timeout + this.socket.setTimeout(0); + + // The HTTP Client seems to subscribe to socket error events + // and re-dispatch them in such a way that doesn't make sense + // for users of our client, so we want to make sure nobody + // else is listening for error events on the socket besides us. + this.socket.removeAllListeners('error'); + + this._set_sock(this.socket); +} + +WebSocketConnection.prototype._set_sock = unit_lib.websocket_set_sock; +WebSocketConnection.prototype._end = unit_lib.response_end; + +WebSocketConnection.CLOSE_REASON_NORMAL = 1000; +WebSocketConnection.CLOSE_REASON_GOING_AWAY = 1001; +WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR = 1002; +WebSocketConnection.CLOSE_REASON_UNPROCESSABLE_INPUT = 1003; +WebSocketConnection.CLOSE_REASON_RESERVED = 1004; // Reserved value. Undefined meaning. +WebSocketConnection.CLOSE_REASON_NOT_PROVIDED = 1005; // Not to be used on the wire +WebSocketConnection.CLOSE_REASON_ABNORMAL = 1006; // Not to be used on the wire +WebSocketConnection.CLOSE_REASON_INVALID_DATA = 1007; +WebSocketConnection.CLOSE_REASON_POLICY_VIOLATION = 1008; +WebSocketConnection.CLOSE_REASON_MESSAGE_TOO_BIG = 1009; +WebSocketConnection.CLOSE_REASON_EXTENSION_REQUIRED = 1010; +WebSocketConnection.CLOSE_REASON_INTERNAL_SERVER_ERROR = 1011; +WebSocketConnection.CLOSE_REASON_TLS_HANDSHAKE_FAILED = 1015; // Not to be used on the wire + +WebSocketConnection.CLOSE_DESCRIPTIONS = { + 1000: 'Normal connection closure', + 1001: 'Remote peer is going away', + 1002: 'Protocol error', + 1003: 'Unprocessable input', + 1004: 'Reserved', + 1005: 'Reason not provided', + 1006: 'Abnormal closure, no further detail available', + 1007: 'Invalid data received', + 1008: 'Policy violation', + 1009: 'Message too big', + 1010: 'Extension requested by client is required', + 1011: 'Internal Server Error', + 1015: 'TLS Handshake Failed' +}; + +function validateCloseReason(code) { + if (code < 1000) { + // Status codes in the range 0-999 are not used + return false; + } + if (code >= 1000 && code <= 2999) { + // Codes from 1000 - 2999 are reserved for use by the protocol. Only + // a few codes are defined, all others are currently illegal. + return [1000, 1001, 1002, 1003, 1007, 1008, 1009, 1010, 1011, 1012, 1013, 1014].indexOf(code) !== -1; + } + if (code >= 3000 && code <= 3999) { + // Reserved for use by libraries, frameworks, and applications. + // Should be registered with IANA. Interpretation of these codes is + // undefined by the WebSocket protocol. + return true; + } + if (code >= 4000 && code <= 4999) { + // Reserved for private use. Interpretation of these codes is + // undefined by the WebSocket protocol. + return true; + } + if (code >= 5000) { + return false; + } +} + +util.inherits(WebSocketConnection, EventEmitter); + +WebSocketConnection.prototype._addSocketEventListeners = function() { + this.socket.on('error', this.handleSocketError.bind(this)); + this.socket.on('end', this.handleSocketEnd.bind(this)); + this.socket.on('close', this.handleSocketClose.bind(this)); +}; + +WebSocketConnection.prototype.handleSocketError = function(error) { + this._debug('handleSocketError: %j', error); + if (this.state === STATE_CLOSED) { + // See https://github.com/theturtle32/WebSocket-Node/issues/288 + this._debug(' --- Socket \'error\' after \'close\''); + return; + } + this.closeReasonCode = WebSocketConnection.CLOSE_REASON_ABNORMAL; + this.closeDescription = 'Socket Error: ' + error.syscall + ' ' + error.code; + this.connected = false; + this.state = STATE_CLOSED; + this.fragmentationSize = 0; + if (utils.eventEmitterListenerCount(this, 'error') > 0) { + this.emit('error', error); + } + this.socket.destroy(error); + this._debug.printOutput(); + + this._end(); +}; + +WebSocketConnection.prototype.handleSocketEnd = function() { + this._debug('handleSocketEnd: received socket end. state = %s', this.state); + this.receivedEnd = true; + if (this.state === STATE_CLOSED) { + // When using the TLS module, sometimes the socket will emit 'end' + // after it emits 'close'. I don't think that's correct behavior, + // but we should deal with it gracefully by ignoring it. + this._debug(' --- Socket \'end\' after \'close\''); + return; + } + if (this.state !== STATE_PEER_REQUESTED_CLOSE && + this.state !== STATE_ENDING) { + this._debug(' --- UNEXPECTED socket end.'); + this.socket.end(); + + this._end(); + } +}; + +WebSocketConnection.prototype.handleSocketClose = function(hadError) { + this._debug('handleSocketClose: received socket close'); + this.socketHadError = hadError; + this.connected = false; + this.state = STATE_CLOSED; + // If closeReasonCode is still set to -1 at this point then we must + // not have received a close frame!! + if (this.closeReasonCode === -1) { + this.closeReasonCode = WebSocketConnection.CLOSE_REASON_ABNORMAL; + this.closeDescription = 'Connection dropped by remote peer.'; + } + this.clearCloseTimer(); + if (!this.closeEventEmitted) { + this.closeEventEmitted = true; + this._debug('-- Emitting WebSocketConnection close event'); + this.emit('close', this.closeReasonCode, this.closeDescription); + } +}; + +WebSocketConnection.prototype.close = function(reasonCode, description) { + if (this.connected) { + this._debug('close: Initating clean WebSocket close sequence.'); + if ('number' !== typeof reasonCode) { + reasonCode = WebSocketConnection.CLOSE_REASON_NORMAL; + } + if (!validateCloseReason(reasonCode)) { + throw new Error('Close code ' + reasonCode + ' is not valid.'); + } + if ('string' !== typeof description) { + description = WebSocketConnection.CLOSE_DESCRIPTIONS[reasonCode]; + } + this.closeReasonCode = reasonCode; + this.closeDescription = description; + this.setCloseTimer(); + this.sendCloseFrame(this.closeReasonCode, this.closeDescription); + this.state = STATE_ENDING; + this.connected = false; + } +}; + +WebSocketConnection.prototype.drop = function(reasonCode, description, skipCloseFrame) { + this._debug('drop'); + if (typeof(reasonCode) !== 'number') { + reasonCode = WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR; + } + + if (typeof(description) !== 'string') { + // If no description is provided, try to look one up based on the + // specified reasonCode. + description = WebSocketConnection.CLOSE_DESCRIPTIONS[reasonCode]; + } + + this._debug('Forcefully dropping connection. skipCloseFrame: %s, code: %d, description: %s', + skipCloseFrame, reasonCode, description + ); + + this.closeReasonCode = reasonCode; + this.closeDescription = description; + this.frameQueue = []; + this.fragmentationSize = 0; + if (!skipCloseFrame) { + this.sendCloseFrame(reasonCode, description); + } + this.connected = false; + this.state = STATE_CLOSED; + this.clearCloseTimer(); + + if (!this.closeEventEmitted) { + this.closeEventEmitted = true; + this._debug('Emitting WebSocketConnection close event'); + this.emit('close', this.closeReasonCode, this.closeDescription); + } + + this._debug('Drop: destroying socket'); + this.socket.destroy(); + + this._end(); +}; + +WebSocketConnection.prototype.setCloseTimer = function() { + this._debug('setCloseTimer'); + this.clearCloseTimer(); + this._debug('Setting close timer'); + this.waitingForCloseResponse = true; + this.closeTimer = setTimeout(this._closeTimerHandler, this.closeTimeout); +}; + +WebSocketConnection.prototype.clearCloseTimer = function() { + this._debug('clearCloseTimer'); + if (this.closeTimer) { + this._debug('Clearing close timer'); + clearTimeout(this.closeTimer); + this.waitingForCloseResponse = false; + this.closeTimer = null; + } +}; + +WebSocketConnection.prototype.handleCloseTimer = function() { + this._debug('handleCloseTimer'); + this.closeTimer = null; + if (this.waitingForCloseResponse) { + this._debug('Close response not received from client. Forcing socket end.'); + this.waitingForCloseResponse = false; + this.state = STATE_CLOSED; + this.socket.end(); + + this._end(); + } +}; + +WebSocketConnection.prototype.processFrame = function(frame) { + if (!this.connected) { + return; + } + + this._debug('processFrame'); + this._debug(' -- frame: %s', frame); + + // Any non-control opcode besides 0x00 (continuation) received in the + // middle of a fragmented message is illegal. + if (this.frameQueue.length !== 0 && (frame.opcode > 0x00 && frame.opcode < 0x08)) { + this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR, + 'Illegal frame opcode 0x' + frame.opcode.toString(16) + ' ' + + 'received in middle of fragmented message.'); + return; + } + + switch(frame.opcode) { + case 0x02: // WebSocketFrame.BINARY_FRAME + this._debug('-- Binary Frame'); + if (this.assembleFragments) { + if (frame.fin) { + // Complete single-frame message received + this._debug('---- Emitting \'message\' event'); + this.emit('message', { + type: 'binary', + binaryData: frame.binaryPayload + }); + } + else { + // beginning of a fragmented message + this.frameQueue.push(frame); + this.fragmentationSize = frame.length; + } + } + break; + case 0x01: // WebSocketFrame.TEXT_FRAME + this._debug('-- Text Frame'); + if (this.assembleFragments) { + if (frame.fin) { + // Complete single-frame message received + this._debug('---- Emitting \'message\' event'); + this.emit('message', { + type: 'utf8', + utf8Data: frame.binaryPayload.toString('utf8') + }); + } + else { + // beginning of a fragmented message + this.frameQueue.push(frame); + this.fragmentationSize = frame.length; + } + } + break; + case 0x00: // WebSocketFrame.CONTINUATION + this._debug('-- Continuation Frame'); + if (this.assembleFragments) { + if (this.frameQueue.length === 0) { + this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR, + 'Unexpected Continuation Frame'); + return; + } + + this.fragmentationSize += frame.length; + + if (this.fragmentationSize > this.maxReceivedMessageSize) { + this.drop(WebSocketConnection.CLOSE_REASON_MESSAGE_TOO_BIG, + 'Maximum message size exceeded.'); + return; + } + + this.frameQueue.push(frame); + + if (frame.fin) { + // end of fragmented message, so we process the whole + // message now. We also have to decode the utf-8 data + // for text frames after combining all the fragments. + var bytesCopied = 0; + var binaryPayload = bufferAllocUnsafe(this.fragmentationSize); + var opcode = this.frameQueue[0].opcode; + this.frameQueue.forEach(function (currentFrame) { + currentFrame.binaryPayload.copy(binaryPayload, bytesCopied); + bytesCopied += currentFrame.binaryPayload.length; + }); + this.frameQueue = []; + this.fragmentationSize = 0; + + switch (opcode) { + case 0x02: // WebSocketOpcode.BINARY_FRAME + this.emit('message', { + type: 'binary', + binaryData: binaryPayload + }); + break; + case 0x01: // WebSocketOpcode.TEXT_FRAME + this.emit('message', { + type: 'utf8', + utf8Data: binaryPayload.toString('utf8') + }); + break; + default: + this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR, + 'Unexpected first opcode in fragmentation sequence: 0x' + opcode.toString(16)); + return; + } + } + } + break; + case 0x09: // WebSocketFrame.PING + this._debug('-- Ping Frame'); + + if (this._pingListenerCount > 0) { + // logic to emit the ping frame: this is only done when a listener is known to exist + // Expose a function allowing the user to override the default ping() behavior + var cancelled = false; + var cancel = function() { + cancelled = true; + }; + this.emit('ping', cancel, frame.binaryPayload); + + // Only send a pong if the client did not indicate that he would like to cancel + if (!cancelled) { + this.pong(frame.binaryPayload); + } + } + else { + this.pong(frame.binaryPayload); + } + + break; + case 0x0A: // WebSocketFrame.PONG + this._debug('-- Pong Frame'); + this.emit('pong', frame.binaryPayload); + break; + case 0x08: // WebSocketFrame.CONNECTION_CLOSE + this._debug('-- Close Frame'); + if (this.waitingForCloseResponse) { + // Got response to our request to close the connection. + // Close is complete, so we just hang up. + this._debug('---- Got close response from peer. Completing closing handshake.'); + this.clearCloseTimer(); + this.waitingForCloseResponse = false; + this.state = STATE_CLOSED; + this.socket.end(); + + this._end(); + return; + } + + this._debug('---- Closing handshake initiated by peer.'); + // Got request from other party to close connection. + // Send back acknowledgement and then hang up. + this.state = STATE_PEER_REQUESTED_CLOSE; + var respondCloseReasonCode; + + // Make sure the close reason provided is legal according to + // the protocol spec. Providing no close status is legal. + // WebSocketFrame sets closeStatus to -1 by default, so if it + // is still -1, then no status was provided. + if (frame.invalidCloseFrameLength) { + this.closeReasonCode = 1005; // 1005 = No reason provided. + respondCloseReasonCode = WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR; + } + else if (frame.closeStatus === -1 || validateCloseReason(frame.closeStatus)) { + this.closeReasonCode = frame.closeStatus; + respondCloseReasonCode = WebSocketConnection.CLOSE_REASON_NORMAL; + } + else { + this.closeReasonCode = frame.closeStatus; + respondCloseReasonCode = WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR; + } + + // If there is a textual description in the close frame, extract it. + if (frame.binaryPayload.length > 1) { + this.closeDescription = frame.binaryPayload.toString('utf8'); + } + else { + this.closeDescription = WebSocketConnection.CLOSE_DESCRIPTIONS[this.closeReasonCode]; + } + this._debug( + '------ Remote peer %s - code: %d - %s - close frame payload length: %d', + this.remoteAddress, this.closeReasonCode, + this.closeDescription, frame.length + ); + this._debug('------ responding to remote peer\'s close request.'); + this.drop(respondCloseReasonCode, null); + this.connected = false; + break; + default: + this._debug('-- Unrecognized Opcode %d', frame.opcode); + this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR, + 'Unrecognized Opcode: 0x' + frame.opcode.toString(16)); + break; + } +}; + +WebSocketConnection.prototype.send = function(data, cb) { + this._debug('send'); + if (Buffer.isBuffer(data)) { + this.sendBytes(data, cb); + } + else if (typeof(data['toString']) === 'function') { + this.sendUTF(data, cb); + } + else { + throw new Error('Data provided must either be a Node Buffer or implement toString()'); + } +}; + +WebSocketConnection.prototype.sendUTF = function(data, cb) { + data = bufferFromString(data.toString(), 'utf8'); + this._debug('sendUTF: %d bytes', data.length); + + var frame = new WebSocketFrame(); + frame.opcode = 0x01; // WebSocketOpcode.TEXT_FRAME + frame.binaryPayload = data; + + this.fragmentAndSend(frame, cb); +}; + +WebSocketConnection.prototype.sendBytes = function(data, cb) { + this._debug('sendBytes'); + if (!Buffer.isBuffer(data)) { + throw new Error('You must pass a Node Buffer object to WebSocketConnection.prototype.sendBytes()'); + } + + var frame = new WebSocketFrame(); + frame.opcode = 0x02; // WebSocketOpcode.BINARY_FRAME + frame.binaryPayload = data; + + this.fragmentAndSend(frame, cb); +}; + +WebSocketConnection.prototype.ping = function(data) { + this._debug('ping'); + + var frame = new WebSocketFrame(); + frame.opcode = 0x09; // WebSocketOpcode.PING + frame.fin = true; + + if (data) { + if (!Buffer.isBuffer(data)) { + data = bufferFromString(data.toString(), 'utf8'); + } + if (data.length > 125) { + this._debug('WebSocket: Data for ping is longer than 125 bytes. Truncating.'); + data = data.slice(0,124); + } + frame.binaryPayload = data; + } + + this.sendFrame(frame); +}; + +// Pong frames have to echo back the contents of the data portion of the +// ping frame exactly, byte for byte. +WebSocketConnection.prototype.pong = function(binaryPayload) { + this._debug('pong'); + + var frame = new WebSocketFrame(); + frame.opcode = 0x0A; // WebSocketOpcode.PONG + if (Buffer.isBuffer(binaryPayload) && binaryPayload.length > 125) { + this._debug('WebSocket: Data for pong is longer than 125 bytes. Truncating.'); + binaryPayload = binaryPayload.slice(0,124); + } + frame.binaryPayload = binaryPayload; + frame.fin = true; + + this.sendFrame(frame); +}; + +WebSocketConnection.prototype.fragmentAndSend = function(frame, cb) { + this._debug('fragmentAndSend'); + if (frame.opcode > 0x07) { + throw new Error('You cannot fragment control frames.'); + } + + var threshold = this.config.fragmentationThreshold; + var length = frame.binaryPayload.length; + + // Send immediately if fragmentation is disabled or the message is not + // larger than the fragmentation threshold. + if (!this.config.fragmentOutgoingMessages || (frame.binaryPayload && length <= threshold)) { + frame.fin = true; + this.sendFrame(frame, cb); + return; + } + + var numFragments = Math.ceil(length / threshold); + var sentFragments = 0; + var sentCallback = function fragmentSentCallback(err) { + if (err) { + if (typeof cb === 'function') { + // pass only the first error + cb(err); + cb = null; + } + return; + } + ++sentFragments; + if ((sentFragments === numFragments) && (typeof cb === 'function')) { + cb(); + } + }; + for (var i=1; i <= numFragments; i++) { + var currentFrame = new WebSocketFrame(); + + // continuation opcode except for first frame. + currentFrame.opcode = (i === 1) ? frame.opcode : 0x00; + + // fin set on last frame only + currentFrame.fin = (i === numFragments); + + // length is likely to be shorter on the last fragment + var currentLength = (i === numFragments) ? length - (threshold * (i-1)) : threshold; + var sliceStart = threshold * (i-1); + + // Slice the right portion of the original payload + currentFrame.binaryPayload = frame.binaryPayload.slice(sliceStart, sliceStart + currentLength); + + this.sendFrame(currentFrame, sentCallback); + } +}; + +WebSocketConnection.prototype.sendCloseFrame = function(reasonCode, description, cb) { + if (typeof(reasonCode) !== 'number') { + reasonCode = WebSocketConnection.CLOSE_REASON_NORMAL; + } + + this._debug('sendCloseFrame state: %s, reasonCode: %d, description: %s', this.state, reasonCode, description); + + if (this.state !== STATE_OPEN && this.state !== STATE_PEER_REQUESTED_CLOSE) { return; } + + var frame = new WebSocketFrame(); + frame.fin = true; + frame.opcode = 0x08; // WebSocketOpcode.CONNECTION_CLOSE + frame.closeStatus = reasonCode; + if (typeof(description) === 'string') { + frame.binaryPayload = bufferFromString(description, 'utf8'); + } + + this.sendFrame(frame, cb); + this.socket.end(); +}; + +WebSocketConnection.prototype._send_frame = unit_lib.websocket_send_frame; + +WebSocketConnection.prototype.sendFrame = function(frame, cb) { + this._debug('sendFrame'); + + frame.mask = this.maskOutgoingPackets; + + this._send_frame(frame); + + if (typeof cb === 'function') { + cb(); + } + + var flushed = 0; // this.socket.write(frame.toBuffer(), cb); + this.outputBufferFull = !flushed; + return flushed; +}; + +module.exports = WebSocketConnection; diff --git a/src/nodejs/unit-http/websocket_frame.js b/src/nodejs/unit-http/websocket_frame.js new file mode 100644 index 00000000..9989937d --- /dev/null +++ b/src/nodejs/unit-http/websocket_frame.js @@ -0,0 +1,11 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +'use strict'; + +function WebSocketFrame() { +} + +module.exports = WebSocketFrame; diff --git a/src/nodejs/unit-http/websocket_request.js b/src/nodejs/unit-http/websocket_request.js new file mode 100644 index 00000000..d84e428b --- /dev/null +++ b/src/nodejs/unit-http/websocket_request.js @@ -0,0 +1,509 @@ +/************************************************************************ + * Copyright 2010-2015 Brian McKelvey. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***********************************************************************/ + +var util = require('util'); +var url = require('url'); +var EventEmitter = require('events').EventEmitter; +var WebSocketConnection = require('./websocket_connection'); + +var headerValueSplitRegExp = /,\s*/; +var headerParamSplitRegExp = /;\s*/; +var headerSanitizeRegExp = /[\r\n]/g; +var xForwardedForSeparatorRegExp = /,\s*/; +var separators = [ + '(', ')', '<', '>', '@', + ',', ';', ':', '\\', '\"', + '/', '[', ']', '?', '=', + '{', '}', ' ', String.fromCharCode(9) +]; +var controlChars = [String.fromCharCode(127) /* DEL */]; +for (var i=0; i < 31; i ++) { + /* US-ASCII Control Characters */ + controlChars.push(String.fromCharCode(i)); +} + +var cookieNameValidateRegEx = /([\x00-\x20\x22\x28\x29\x2c\x2f\x3a-\x3f\x40\x5b-\x5e\x7b\x7d\x7f])/; +var cookieValueValidateRegEx = /[^\x21\x23-\x2b\x2d-\x3a\x3c-\x5b\x5d-\x7e]/; +var cookieValueDQuoteValidateRegEx = /^"[^"]*"$/; +var controlCharsAndSemicolonRegEx = /[\x00-\x20\x3b]/g; + +var cookieSeparatorRegEx = /[;,] */; + +var httpStatusDescriptions = { + 100: 'Continue', + 101: 'Switching Protocols', + 200: 'OK', + 201: 'Created', + 203: 'Non-Authoritative Information', + 204: 'No Content', + 205: 'Reset Content', + 206: 'Partial Content', + 300: 'Multiple Choices', + 301: 'Moved Permanently', + 302: 'Found', + 303: 'See Other', + 304: 'Not Modified', + 305: 'Use Proxy', + 307: 'Temporary Redirect', + 400: 'Bad Request', + 401: 'Unauthorized', + 402: 'Payment Required', + 403: 'Forbidden', + 404: 'Not Found', + 406: 'Not Acceptable', + 407: 'Proxy Authorization Required', + 408: 'Request Timeout', + 409: 'Conflict', + 410: 'Gone', + 411: 'Length Required', + 412: 'Precondition Failed', + 413: 'Request Entity Too Long', + 414: 'Request-URI Too Long', + 415: 'Unsupported Media Type', + 416: 'Requested Range Not Satisfiable', + 417: 'Expectation Failed', + 426: 'Upgrade Required', + 500: 'Internal Server Error', + 501: 'Not Implemented', + 502: 'Bad Gateway', + 503: 'Service Unavailable', + 504: 'Gateway Timeout', + 505: 'HTTP Version Not Supported' +}; + +function WebSocketRequest(socket, httpRequest, serverConfig) { + // Superclass Constructor + EventEmitter.call(this); + + this.socket = socket; + this.httpRequest = httpRequest; + this.resource = httpRequest.url; + this.remoteAddress = socket.remoteAddress; + this.remoteAddresses = [this.remoteAddress]; + this.serverConfig = serverConfig; + + // Watch for the underlying TCP socket closing before we call accept + this._socketIsClosing = false; + this._socketCloseHandler = this._handleSocketCloseBeforeAccept.bind(this); + this.socket.on('end', this._socketCloseHandler); + this.socket.on('close', this._socketCloseHandler); + + this._resolved = false; +} + +util.inherits(WebSocketRequest, EventEmitter); + +WebSocketRequest.prototype.readHandshake = function() { + var self = this; + var request = this.httpRequest; + + // Decode URL + this.resourceURL = url.parse(this.resource, true); + + this.host = request.headers['host']; + if (!this.host) { + throw new Error('Client must provide a Host header.'); + } + + this.key = request.headers['sec-websocket-key']; + if (!this.key) { + throw new Error('Client must provide a value for Sec-WebSocket-Key.'); + } + + this.webSocketVersion = parseInt(request.headers['sec-websocket-version'], 10); + + if (!this.webSocketVersion || isNaN(this.webSocketVersion)) { + throw new Error('Client must provide a value for Sec-WebSocket-Version.'); + } + + switch (this.webSocketVersion) { + case 8: + case 13: + break; + default: + var e = new Error('Unsupported websocket client version: ' + this.webSocketVersion + + 'Only versions 8 and 13 are supported.'); + e.httpCode = 426; + e.headers = { + 'Sec-WebSocket-Version': '13' + }; + throw e; + } + + if (this.webSocketVersion === 13) { + this.origin = request.headers['origin']; + } + else if (this.webSocketVersion === 8) { + this.origin = request.headers['sec-websocket-origin']; + } + + // Protocol is optional. + var protocolString = request.headers['sec-websocket-protocol']; + this.protocolFullCaseMap = {}; + this.requestedProtocols = []; + if (protocolString) { + var requestedProtocolsFullCase = protocolString.split(headerValueSplitRegExp); + requestedProtocolsFullCase.forEach(function(protocol) { + var lcProtocol = protocol.toLocaleLowerCase(); + self.requestedProtocols.push(lcProtocol); + self.protocolFullCaseMap[lcProtocol] = protocol; + }); + } + + if (!this.serverConfig.ignoreXForwardedFor && + request.headers['x-forwarded-for']) { + var immediatePeerIP = this.remoteAddress; + this.remoteAddresses = request.headers['x-forwarded-for'] + .split(xForwardedForSeparatorRegExp); + this.remoteAddresses.push(immediatePeerIP); + this.remoteAddress = this.remoteAddresses[0]; + } + + // Extensions are optional. + var extensionsString = request.headers['sec-websocket-extensions']; + this.requestedExtensions = this.parseExtensions(extensionsString); + + // Cookies are optional + var cookieString = request.headers['cookie']; + this.cookies = this.parseCookies(cookieString); +}; + +WebSocketRequest.prototype.parseExtensions = function(extensionsString) { + if (!extensionsString || extensionsString.length === 0) { + return []; + } + var extensions = extensionsString.toLocaleLowerCase().split(headerValueSplitRegExp); + extensions.forEach(function(extension, index, array) { + var params = extension.split(headerParamSplitRegExp); + var extensionName = params[0]; + var extensionParams = params.slice(1); + extensionParams.forEach(function(rawParam, index, array) { + var arr = rawParam.split('='); + var obj = { + name: arr[0], + value: arr[1] + }; + array.splice(index, 1, obj); + }); + var obj = { + name: extensionName, + params: extensionParams + }; + array.splice(index, 1, obj); + }); + return extensions; +}; + +// This function adapted from node-cookie +// https://github.com/shtylman/node-cookie +WebSocketRequest.prototype.parseCookies = function(str) { + // Sanity Check + if (!str || typeof(str) !== 'string') { + return []; + } + + var cookies = []; + var pairs = str.split(cookieSeparatorRegEx); + + pairs.forEach(function(pair) { + var eq_idx = pair.indexOf('='); + if (eq_idx === -1) { + cookies.push({ + name: pair, + value: null + }); + return; + } + + var key = pair.substr(0, eq_idx).trim(); + var val = pair.substr(++eq_idx, pair.length).trim(); + + // quoted values + if ('"' === val[0]) { + val = val.slice(1, -1); + } + + cookies.push({ + name: key, + value: decodeURIComponent(val) + }); + }); + + return cookies; +}; + +WebSocketRequest.prototype.accept = function(acceptedProtocol, allowedOrigin, cookies) { + this._verifyResolution(); + + // TODO: Handle extensions + + var protocolFullCase; + + if (acceptedProtocol) { + protocolFullCase = this.protocolFullCaseMap[acceptedProtocol.toLocaleLowerCase()]; + if (typeof(protocolFullCase) === 'undefined') { + protocolFullCase = acceptedProtocol; + } + } + else { + protocolFullCase = acceptedProtocol; + } + this.protocolFullCaseMap = null; + + var response = this.httpRequest._response; + response.statusCode = 101; + + if (protocolFullCase) { + // validate protocol + for (var i=0; i < protocolFullCase.length; i++) { + var charCode = protocolFullCase.charCodeAt(i); + var character = protocolFullCase.charAt(i); + if (charCode < 0x21 || charCode > 0x7E || separators.indexOf(character) !== -1) { + this.reject(500); + throw new Error('Illegal character "' + String.fromCharCode(character) + '" in subprotocol.'); + } + } + if (this.requestedProtocols.indexOf(acceptedProtocol) === -1) { + this.reject(500); + throw new Error('Specified protocol was not requested by the client.'); + } + + protocolFullCase = protocolFullCase.replace(headerSanitizeRegExp, ''); + response += 'Sec-WebSocket-Protocol: ' + protocolFullCase + '\r\n'; + } + this.requestedProtocols = null; + + if (allowedOrigin) { + allowedOrigin = allowedOrigin.replace(headerSanitizeRegExp, ''); + if (this.webSocketVersion === 13) { + response.setHeader('Origin', allowedOrigin); + } + else if (this.webSocketVersion === 8) { + response.setHeader('Sec-WebSocket-Origin', allowedOrigin); + } + } + + if (cookies) { + if (!Array.isArray(cookies)) { + this.reject(500); + throw new Error('Value supplied for "cookies" argument must be an array.'); + } + var seenCookies = {}; + cookies.forEach(function(cookie) { + if (!cookie.name || !cookie.value) { + this.reject(500); + throw new Error('Each cookie to set must at least provide a "name" and "value"'); + } + + // Make sure there are no \r\n sequences inserted + cookie.name = cookie.name.replace(controlCharsAndSemicolonRegEx, ''); + cookie.value = cookie.value.replace(controlCharsAndSemicolonRegEx, ''); + + if (seenCookies[cookie.name]) { + this.reject(500); + throw new Error('You may not specify the same cookie name twice.'); + } + seenCookies[cookie.name] = true; + + // token (RFC 2616, Section 2.2) + var invalidChar = cookie.name.match(cookieNameValidateRegEx); + if (invalidChar) { + this.reject(500); + throw new Error('Illegal character ' + invalidChar[0] + ' in cookie name'); + } + + // RFC 6265, Section 4.1.1 + // *cookie-octet / ( DQUOTE *cookie-octet DQUOTE ) | %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E + if (cookie.value.match(cookieValueDQuoteValidateRegEx)) { + invalidChar = cookie.value.slice(1, -1).match(cookieValueValidateRegEx); + } else { + invalidChar = cookie.value.match(cookieValueValidateRegEx); + } + if (invalidChar) { + this.reject(500); + throw new Error('Illegal character ' + invalidChar[0] + ' in cookie value'); + } + + var cookieParts = [cookie.name + '=' + cookie.value]; + + // RFC 6265, Section 4.1.1 + // 'Path=' path-value | + if(cookie.path){ + invalidChar = cookie.path.match(controlCharsAndSemicolonRegEx); + if (invalidChar) { + this.reject(500); + throw new Error('Illegal character ' + invalidChar[0] + ' in cookie path'); + } + cookieParts.push('Path=' + cookie.path); + } + + // RFC 6265, Section 4.1.2.3 + // 'Domain=' subdomain + if (cookie.domain) { + if (typeof(cookie.domain) !== 'string') { + this.reject(500); + throw new Error('Domain must be specified and must be a string.'); + } + invalidChar = cookie.domain.match(controlCharsAndSemicolonRegEx); + if (invalidChar) { + this.reject(500); + throw new Error('Illegal character ' + invalidChar[0] + ' in cookie domain'); + } + cookieParts.push('Domain=' + cookie.domain.toLowerCase()); + } + + // RFC 6265, Section 4.1.1 + //'Expires=' sane-cookie-date | Force Date object requirement by using only epoch + if (cookie.expires) { + if (!(cookie.expires instanceof Date)){ + this.reject(500); + throw new Error('Value supplied for cookie "expires" must be a vaild date object'); + } + cookieParts.push('Expires=' + cookie.expires.toGMTString()); + } + + // RFC 6265, Section 4.1.1 + //'Max-Age=' non-zero-digit *DIGIT + if (cookie.maxage) { + var maxage = cookie.maxage; + if (typeof(maxage) === 'string') { + maxage = parseInt(maxage, 10); + } + if (isNaN(maxage) || maxage <= 0 ) { + this.reject(500); + throw new Error('Value supplied for cookie "maxage" must be a non-zero number'); + } + maxage = Math.round(maxage); + cookieParts.push('Max-Age=' + maxage.toString(10)); + } + + // RFC 6265, Section 4.1.1 + //'Secure;' + if (cookie.secure) { + if (typeof(cookie.secure) !== 'boolean') { + this.reject(500); + throw new Error('Value supplied for cookie "secure" must be of type boolean'); + } + cookieParts.push('Secure'); + } + + // RFC 6265, Section 4.1.1 + //'HttpOnly;' + if (cookie.httponly) { + if (typeof(cookie.httponly) !== 'boolean') { + this.reject(500); + throw new Error('Value supplied for cookie "httponly" must be of type boolean'); + } + cookieParts.push('HttpOnly'); + } + + response.addHeader('Set-Cookie', cookieParts.join(';')); + }.bind(this)); + } + + // TODO: handle negotiated extensions + // if (negotiatedExtensions) { + // response += 'Sec-WebSocket-Extensions: ' + negotiatedExtensions.join(', ') + '\r\n'; + // } + + // Mark the request resolved now so that the user can't call accept or + // reject a second time. + this._resolved = true; + this.emit('requestResolved', this); + + var connection = new WebSocketConnection(this.socket, [], acceptedProtocol, false, this.serverConfig); + connection.webSocketVersion = this.webSocketVersion; + connection.remoteAddress = this.remoteAddress; + connection.remoteAddresses = this.remoteAddresses; + + var self = this; + + if (this._socketIsClosing) { + // Handle case when the client hangs up before we get a chance to + // accept the connection and send our side of the opening handshake. + cleanupFailedConnection(connection); + + } else { + response._sendHeaders(); + connection._addSocketEventListeners(); + } + + this.emit('requestAccepted', connection); + return connection; +}; + +WebSocketRequest.prototype.reject = function(status, reason, extraHeaders) { + this._verifyResolution(); + + // Mark the request resolved now so that the user can't call accept or + // reject a second time. + this._resolved = true; + this.emit('requestResolved', this); + + if (typeof(status) !== 'number') { + status = 403; + } + + var response = this.httpRequest._response; + + response.statusCode = status; + + if (reason) { + reason = reason.replace(headerSanitizeRegExp, ''); + response.addHeader('X-WebSocket-Reject-Reason', reason); + } + + if (extraHeaders) { + for (var key in extraHeaders) { + var sanitizedValue = extraHeaders[key].toString().replace(headerSanitizeRegExp, ''); + var sanitizedKey = key.replace(headerSanitizeRegExp, ''); + response += (sanitizedKey + ': ' + sanitizedValue + '\r\n'); + } + } + + response.end(); + + this.emit('requestRejected', this); +}; + +WebSocketRequest.prototype._handleSocketCloseBeforeAccept = function() { + this._socketIsClosing = true; + this._removeSocketCloseListeners(); +}; + +WebSocketRequest.prototype._removeSocketCloseListeners = function() { + this.socket.removeListener('end', this._socketCloseHandler); + this.socket.removeListener('close', this._socketCloseHandler); +}; + +WebSocketRequest.prototype._verifyResolution = function() { + if (this._resolved) { + throw new Error('WebSocketRequest may only be accepted or rejected one time.'); + } +}; + +function cleanupFailedConnection(connection) { + // Since we have to return a connection object even if the socket is + // already dead in order not to break the API, we schedule a 'close' + // event on the connection object to occur immediately. + process.nextTick(function() { + // WebSocketConnection.CLOSE_REASON_ABNORMAL = 1006 + // Third param: Skip sending the close frame to a dead socket + connection.drop(1006, 'TCP connection lost before handshake completed.', true); + }); +} + +module.exports = WebSocketRequest; diff --git a/src/nodejs/unit-http/websocket_router.js b/src/nodejs/unit-http/websocket_router.js new file mode 100644 index 00000000..4efa35d2 --- /dev/null +++ b/src/nodejs/unit-http/websocket_router.js @@ -0,0 +1,157 @@ +/************************************************************************ + * Copyright 2010-2015 Brian McKelvey. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***********************************************************************/ + +var extend = require('./utils').extend; +var util = require('util'); +var EventEmitter = require('events').EventEmitter; +var WebSocketRouterRequest = require('./websocket_router_request'); + +function WebSocketRouter(config) { + // Superclass Constructor + EventEmitter.call(this); + + this.config = { + // The WebSocketServer instance to attach to. + server: null + }; + if (config) { + extend(this.config, config); + } + this.handlers = []; + + this._requestHandler = this.handleRequest.bind(this); + if (this.config.server) { + this.attachServer(this.config.server); + } +} + +util.inherits(WebSocketRouter, EventEmitter); + +WebSocketRouter.prototype.attachServer = function(server) { + if (server) { + this.server = server; + this.server.on('request', this._requestHandler); + } + else { + throw new Error('You must specify a WebSocketServer instance to attach to.'); + } +}; + +WebSocketRouter.prototype.detachServer = function() { + if (this.server) { + this.server.removeListener('request', this._requestHandler); + this.server = null; + } + else { + throw new Error('Cannot detach from server: not attached.'); + } +}; + +WebSocketRouter.prototype.mount = function(path, protocol, callback) { + if (!path) { + throw new Error('You must specify a path for this handler.'); + } + if (!protocol) { + protocol = '____no_protocol____'; + } + if (!callback) { + throw new Error('You must specify a callback for this handler.'); + } + + path = this.pathToRegExp(path); + if (!(path instanceof RegExp)) { + throw new Error('Path must be specified as either a string or a RegExp.'); + } + var pathString = path.toString(); + + // normalize protocol to lower-case + protocol = protocol.toLocaleLowerCase(); + + if (this.findHandlerIndex(pathString, protocol) !== -1) { + throw new Error('You may only mount one handler per path/protocol combination.'); + } + + this.handlers.push({ + 'path': path, + 'pathString': pathString, + 'protocol': protocol, + 'callback': callback + }); +}; +WebSocketRouter.prototype.unmount = function(path, protocol) { + var index = this.findHandlerIndex(this.pathToRegExp(path).toString(), protocol); + if (index !== -1) { + this.handlers.splice(index, 1); + } + else { + throw new Error('Unable to find a route matching the specified path and protocol.'); + } +}; + +WebSocketRouter.prototype.findHandlerIndex = function(pathString, protocol) { + protocol = protocol.toLocaleLowerCase(); + for (var i=0, len=this.handlers.length; i < len; i++) { + var handler = this.handlers[i]; + if (handler.pathString === pathString && handler.protocol === protocol) { + return i; + } + } + return -1; +}; + +WebSocketRouter.prototype.pathToRegExp = function(path) { + if (typeof(path) === 'string') { + if (path === '*') { + path = /^.*$/; + } + else { + path = path.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); + path = new RegExp('^' + path + '$'); + } + } + return path; +}; + +WebSocketRouter.prototype.handleRequest = function(request) { + var requestedProtocols = request.requestedProtocols; + if (requestedProtocols.length === 0) { + requestedProtocols = ['____no_protocol____']; + } + + // Find a handler with the first requested protocol first + for (var i=0; i < requestedProtocols.length; i++) { + var requestedProtocol = requestedProtocols[i].toLocaleLowerCase(); + + // find the first handler that can process this request + for (var j=0, len=this.handlers.length; j < len; j++) { + var handler = this.handlers[j]; + if (handler.path.test(request.resourceURL.pathname)) { + if (requestedProtocol === handler.protocol || + handler.protocol === '*') + { + var routerRequest = new WebSocketRouterRequest(request, requestedProtocol); + handler.callback(routerRequest); + return; + } + } + } + } + + // If we get here we were unable to find a suitable handler. + request.reject(404, 'No handler is available for the given request.'); +}; + +module.exports = WebSocketRouter; diff --git a/src/nodejs/unit-http/websocket_router_request.js b/src/nodejs/unit-http/websocket_router_request.js new file mode 100644 index 00000000..d3e37457 --- /dev/null +++ b/src/nodejs/unit-http/websocket_router_request.js @@ -0,0 +1,54 @@ +/************************************************************************ + * Copyright 2010-2015 Brian McKelvey. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***********************************************************************/ + +var util = require('util'); +var EventEmitter = require('events').EventEmitter; + +function WebSocketRouterRequest(webSocketRequest, resolvedProtocol) { + // Superclass Constructor + EventEmitter.call(this); + + this.webSocketRequest = webSocketRequest; + if (resolvedProtocol === '____no_protocol____') { + this.protocol = null; + } + else { + this.protocol = resolvedProtocol; + } + this.origin = webSocketRequest.origin; + this.resource = webSocketRequest.resource; + this.resourceURL = webSocketRequest.resourceURL; + this.httpRequest = webSocketRequest.httpRequest; + this.remoteAddress = webSocketRequest.remoteAddress; + this.webSocketVersion = webSocketRequest.webSocketVersion; + this.requestedExtensions = webSocketRequest.requestedExtensions; + this.cookies = webSocketRequest.cookies; +} + +util.inherits(WebSocketRouterRequest, EventEmitter); + +WebSocketRouterRequest.prototype.accept = function(origin, cookies) { + var connection = this.webSocketRequest.accept(this.protocol, origin, cookies); + this.emit('requestAccepted', connection); + return connection; +}; + +WebSocketRouterRequest.prototype.reject = function(status, reason, extraHeaders) { + this.webSocketRequest.reject(status, reason, extraHeaders); + this.emit('requestRejected', this); +}; + +module.exports = WebSocketRouterRequest; diff --git a/src/nodejs/unit-http/websocket_server.js b/src/nodejs/unit-http/websocket_server.js new file mode 100644 index 00000000..306f3f67 --- /dev/null +++ b/src/nodejs/unit-http/websocket_server.js @@ -0,0 +1,213 @@ +/************************************************************************ + * Copyright 2010-2015 Brian McKelvey. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***********************************************************************/ + +var extend = require('./utils').extend; +var utils = require('./utils'); +var util = require('util'); +var EventEmitter = require('events').EventEmitter; +var WebSocketRequest = require('./websocket_request'); + +var WebSocketServer = function WebSocketServer(config) { + // Superclass Constructor + EventEmitter.call(this); + + this._handlers = { + upgrade: this.handleUpgrade.bind(this), + requestAccepted: this.handleRequestAccepted.bind(this), + requestResolved: this.handleRequestResolved.bind(this) + }; + this.connections = []; + this.pendingRequests = []; + if (config) { + this.mount(config); + } +}; + +util.inherits(WebSocketServer, EventEmitter); + +WebSocketServer.prototype.mount = function(config) { + this.config = { + // The http server instance to attach to. Required. + httpServer: null, + + // 64KiB max frame size. + maxReceivedFrameSize: 0x10000, + + // 1MiB max message size, only applicable if + // assembleFragments is true + maxReceivedMessageSize: 0x100000, + + // Outgoing messages larger than fragmentationThreshold will be + // split into multiple fragments. + fragmentOutgoingMessages: true, + + // Outgoing frames are fragmented if they exceed this threshold. + // Default is 16KiB + fragmentationThreshold: 0x4000, + + // If true, fragmented messages will be automatically assembled + // and the full message will be emitted via a 'message' event. + // If false, each frame will be emitted via a 'frame' event and + // the application will be responsible for aggregating multiple + // fragmented frames. Single-frame messages will emit a 'message' + // event in addition to the 'frame' event. + // Most users will want to leave this set to 'true' + assembleFragments: true, + + // If this is true, websocket connections will be accepted + // regardless of the path and protocol specified by the client. + // The protocol accepted will be the first that was requested + // by the client. Clients from any origin will be accepted. + // This should only be used in the simplest of cases. You should + // probably leave this set to 'false' and inspect the request + // object to make sure it's acceptable before accepting it. + autoAcceptConnections: false, + + // Whether or not the X-Forwarded-For header should be respected. + // It's important to set this to 'true' when accepting connections + // from untrusted clients, as a malicious client could spoof its + // IP address by simply setting this header. It's meant to be added + // by a trusted proxy or other intermediary within your own + // infrastructure. + // See: http://en.wikipedia.org/wiki/X-Forwarded-For + ignoreXForwardedFor: false, + + // The Nagle Algorithm makes more efficient use of network resources + // by introducing a small delay before sending small packets so that + // multiple messages can be batched together before going onto the + // wire. This however comes at the cost of latency, so the default + // is to disable it. If you don't need low latency and are streaming + // lots of small messages, you can change this to 'false' + disableNagleAlgorithm: true, + + // The number of milliseconds to wait after sending a close frame + // for an acknowledgement to come back before giving up and just + // closing the socket. + closeTimeout: 5000 + }; + extend(this.config, config); + + if (this.config.httpServer) { + if (!Array.isArray(this.config.httpServer)) { + this.config.httpServer = [this.config.httpServer]; + } + var upgradeHandler = this._handlers.upgrade; + this.config.httpServer.forEach(function(httpServer) { + httpServer.on('upgrade', upgradeHandler); + }); + } + else { + throw new Error('You must specify an httpServer on which to mount the WebSocket server.'); + } +}; + +WebSocketServer.prototype.unmount = function() { + var upgradeHandler = this._handlers.upgrade; + this.config.httpServer.forEach(function(httpServer) { + httpServer.removeListener('upgrade', upgradeHandler); + }); +}; + +WebSocketServer.prototype.closeAllConnections = function() { + this.connections.forEach(function(connection) { + connection.close(); + }); + this.pendingRequests.forEach(function(request) { + process.nextTick(function() { + request.reject(503); // HTTP 503 Service Unavailable + }); + }); +}; + +WebSocketServer.prototype.broadcast = function(data) { + if (Buffer.isBuffer(data)) { + this.broadcastBytes(data); + } + else if (typeof(data.toString) === 'function') { + this.broadcastUTF(data); + } +}; + +WebSocketServer.prototype.broadcastUTF = function(utfData) { + this.connections.forEach(function(connection) { + connection.sendUTF(utfData); + }); +}; + +WebSocketServer.prototype.broadcastBytes = function(binaryData) { + this.connections.forEach(function(connection) { + connection.sendBytes(binaryData); + }); +}; + +WebSocketServer.prototype.shutDown = function() { + this.unmount(); + this.closeAllConnections(); +}; + +WebSocketServer.prototype.handleUpgrade = function(request, socket) { + var wsRequest = new WebSocketRequest(socket, request, this.config); + try { + wsRequest.readHandshake(); + } + catch(e) { + wsRequest.reject( + e.httpCode ? e.httpCode : 400, + e.message, + e.headers + ); + return; + } + + this.pendingRequests.push(wsRequest); + + wsRequest.once('requestAccepted', this._handlers.requestAccepted); + wsRequest.once('requestResolved', this._handlers.requestResolved); + + if (!this.config.autoAcceptConnections && utils.eventEmitterListenerCount(this, 'request') > 0) { + this.emit('request', wsRequest); + } + else if (this.config.autoAcceptConnections) { + wsRequest.accept(wsRequest.requestedProtocols[0], wsRequest.origin); + } + else { + wsRequest.reject(404, 'No handler is configured to accept the connection.'); + } +}; + +WebSocketServer.prototype.handleRequestAccepted = function(connection) { + var self = this; + connection.once('close', function(closeReason, description) { + self.handleConnectionClose(connection, closeReason, description); + }); + this.connections.push(connection); + this.emit('connect', connection); +}; + +WebSocketServer.prototype.handleConnectionClose = function(connection, closeReason, description) { + var index = this.connections.indexOf(connection); + if (index !== -1) { + this.connections.splice(index, 1); + } + this.emit('close', connection, closeReason, description); +}; + +WebSocketServer.prototype.handleRequestResolved = function(request) { + var index = this.pendingRequests.indexOf(request); + if (index !== -1) { this.pendingRequests.splice(index, 1); } +}; + +module.exports = WebSocketServer; -- cgit From 72b56388b765686cc947bd84e6bc569316ad8de2 Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Thu, 22 Aug 2019 18:28:23 +0300 Subject: Tests: Node.js websockets tests tuned. --- test/test_node_websockets.py | 17 ++++++++++++++--- test/unit/applications/websockets.py | 4 ++-- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/test/test_node_websockets.py b/test/test_node_websockets.py index 655b5202..895019c5 100644 --- a/test/test_node_websockets.py +++ b/test/test_node_websockets.py @@ -9,9 +9,15 @@ class TestNodeWebsockets(TestApplicationNode): ws = TestApplicationWebsocket() - @classmethod - def setUpClass(cls): - raise unittest.SkipTest('Websockets is not available') + def setUp(self): + super().setUp() + + self.skip_alerts.extend( + [ + r'last message send failed', + r'socket close\(\d+\) failed', + ] + ) def close_connection(self, sock): self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty sock') @@ -432,6 +438,7 @@ class TestNodeWebsockets(TestApplicationNode): # validation for websocket frames. It should be implemented # by application, if necessary. + @unittest.skip('not yet') def test_node_websockets_1_1_1__1_1_8(self): self.load('websockets/mirror') @@ -458,6 +465,7 @@ class TestNodeWebsockets(TestApplicationNode): self.close_connection(sock) + @unittest.skip('not yet') def test_node_websockets_1_2_1__1_2_8(self): self.load('websockets/mirror') @@ -799,6 +807,7 @@ class TestNodeWebsockets(TestApplicationNode): self.check_close(sock, 1002) + @unittest.skip('not yet') def test_node_websockets_5_1__5_20(self): self.load('websockets/mirror') @@ -1521,6 +1530,7 @@ class TestNodeWebsockets(TestApplicationNode): self.ws.frame_write(sock, opcode, payload) # frame length is 101 self.check_close(sock, 1009) # 1009 - CLOSE_TOO_LARGE + @unittest.skip('not yet') def test_node_websockets_read_timeout(self): self.load('websockets/mirror') @@ -1541,6 +1551,7 @@ class TestNodeWebsockets(TestApplicationNode): self.check_close(sock, 1001) # 1001 - CLOSE_GOING_AWAY + @unittest.skip('not yet') def test_node_websockets_keepalive_interval(self): self.load('websockets/mirror') diff --git a/test/unit/applications/websockets.py b/test/unit/applications/websockets.py index 7b516239..d64b960c 100644 --- a/test/unit/applications/websockets.py +++ b/test/unit/applications/websockets.py @@ -57,7 +57,7 @@ class TestApplicationWebsocket(TestApplicationProto): def serialize_close(self, code = 1000, reason = ''): return struct.pack('!H', code) + reason.encode('utf-8') - def frame_read(self, sock, read_timeout=1): + def frame_read(self, sock, read_timeout=5): def recv_bytes(sock, bytes): data = b'' while select.select([sock], [], [], read_timeout)[0]: @@ -204,7 +204,7 @@ class TestApplicationWebsocket(TestApplicationProto): op_code = self.OP_CONT pos = end - def message_read(self, sock, read_timeout=1): + def message_read(self, sock, read_timeout=5): frame = self.frame_read(sock, read_timeout=read_timeout) while(not frame['fin']): -- cgit From 0a106e3cbcc4f468a22522288e16ac68cff5bdd2 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Thu, 22 Aug 2019 18:43:02 +0300 Subject: Added version 1.10.0 CHANGES. --- CHANGES | 25 ++++++++++++++++ docs/changes.xml | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+) diff --git a/CHANGES b/CHANGES index 79761847..1c2852dc 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,29 @@ +Changes with Unit 1.10.0 22 Aug 2019 + + *) Change: matching of cookies in routes made case sensitive. + + *) Change: decreased log level of common errors when clients close + connections. + + *) Change: removed the Perl module's "--include=" ./configure option. + + *) Feature: built-in WebSocket server implementation for Node.js module. + + *) Feature: splitting PATH_INFO from request URI in PHP module. + + *) Feature: request routing by scheme (HTTP or HTTPS). + + *) Feature: support for multipart requests body in Java module. + + *) Feature: improved API compatibility with Node.js 11.10 or later. + + *) Bugfix: reconfiguration failed if "listeners" or "applications" + objects were missing. + + *) Bugfix: applying a large configuration might have failed. + + Changes with Unit 1.9.0 30 May 2019 *) Feature: request routing by arguments, headers, and cookies. diff --git a/docs/changes.xml b/docs/changes.xml index e1a8734c..1358e8b8 100644 --- a/docs/changes.xml +++ b/docs/changes.xml @@ -5,6 +5,93 @@ + + + + +NGINX Unit updated to 1.10.0. + + + + + + + + + + +matching of cookies in routes made case sensitive. + + + + + +decreased log level of common errors when clients close connections. + + + + + +removed the Perl module's "--include=" ./configure option. + + + + + +built-in WebSocket server implementation for Node.js module. + + + + + +splitting PATH_INFO from request URI in PHP module. + + + + + +request routing by scheme (HTTP or HTTPS). + + + + + +support for multipart requests body in Java module. + + + + + +improved API compatibility with Node.js 11.10 or later. + + + + + +reconfiguration failed if "listeners" or "applications" objects were missing. + + + + + +applying a large configuration might have failed. + + + + + + -- cgit From 700987c3557314c7a2ab7514c6096eb5db97d710 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Thu, 22 Aug 2019 18:43:21 +0300 Subject: Fixed rebuilding of Dockerfiles. --- pkg/docker/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/docker/Makefile b/pkg/docker/Makefile index cf6de78d..e826cb95 100644 --- a/pkg/docker/Makefile +++ b/pkg/docker/Makefile @@ -49,7 +49,7 @@ dockerfiles: $(addprefix Dockerfile., $(MODULES)) build: dockerfiles $(addprefix build-,$(MODULES)) push: build $(addprefix push-,$(MODULES)) latest -Dockerfile.%: ../../src/nxt_main.h +Dockerfile.%: ../../version @echo "===> Building $@" cat Dockerfile.tmpl | sed \ -e 's,@@UNITPACKAGES@@,$(MODULE_$*),g' \ -- cgit From 19f59b221dec9580ede4912303b437ec655b307b Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Thu, 22 Aug 2019 18:43:25 +0300 Subject: Generated Dockerfiles for Unit 1.10.0. --- pkg/docker/Dockerfile.full | 2 +- pkg/docker/Dockerfile.go1.7-dev | 2 +- pkg/docker/Dockerfile.go1.8-dev | 2 +- pkg/docker/Dockerfile.minimal | 2 +- pkg/docker/Dockerfile.perl5.24 | 2 +- pkg/docker/Dockerfile.php7.0 | 2 +- pkg/docker/Dockerfile.python2.7 | 2 +- pkg/docker/Dockerfile.python3.5 | 2 +- pkg/docker/Dockerfile.ruby2.3 | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pkg/docker/Dockerfile.full b/pkg/docker/Dockerfile.full index 14afc75b..8c4baa35 100644 --- a/pkg/docker/Dockerfile.full +++ b/pkg/docker/Dockerfile.full @@ -2,7 +2,7 @@ FROM debian:stretch-slim LABEL maintainer="NGINX Docker Maintainers " -ENV UNIT_VERSION 1.9.0-1~stretch +ENV UNIT_VERSION 1.10.0-1~stretch RUN set -x \ && apt-get update \ diff --git a/pkg/docker/Dockerfile.go1.7-dev b/pkg/docker/Dockerfile.go1.7-dev index ad3d888d..895ac69e 100644 --- a/pkg/docker/Dockerfile.go1.7-dev +++ b/pkg/docker/Dockerfile.go1.7-dev @@ -2,7 +2,7 @@ FROM debian:stretch-slim LABEL maintainer="NGINX Docker Maintainers " -ENV UNIT_VERSION 1.9.0-1~stretch +ENV UNIT_VERSION 1.10.0-1~stretch RUN set -x \ && apt-get update \ diff --git a/pkg/docker/Dockerfile.go1.8-dev b/pkg/docker/Dockerfile.go1.8-dev index 915d859a..1ba8a92e 100644 --- a/pkg/docker/Dockerfile.go1.8-dev +++ b/pkg/docker/Dockerfile.go1.8-dev @@ -2,7 +2,7 @@ FROM debian:stretch-slim LABEL maintainer="NGINX Docker Maintainers " -ENV UNIT_VERSION 1.9.0-1~stretch +ENV UNIT_VERSION 1.10.0-1~stretch RUN set -x \ && apt-get update \ diff --git a/pkg/docker/Dockerfile.minimal b/pkg/docker/Dockerfile.minimal index 5214c24a..44592364 100644 --- a/pkg/docker/Dockerfile.minimal +++ b/pkg/docker/Dockerfile.minimal @@ -2,7 +2,7 @@ FROM debian:stretch-slim LABEL maintainer="NGINX Docker Maintainers " -ENV UNIT_VERSION 1.9.0-1~stretch +ENV UNIT_VERSION 1.10.0-1~stretch RUN set -x \ && apt-get update \ diff --git a/pkg/docker/Dockerfile.perl5.24 b/pkg/docker/Dockerfile.perl5.24 index c2c91866..fc483796 100644 --- a/pkg/docker/Dockerfile.perl5.24 +++ b/pkg/docker/Dockerfile.perl5.24 @@ -2,7 +2,7 @@ FROM debian:stretch-slim LABEL maintainer="NGINX Docker Maintainers " -ENV UNIT_VERSION 1.9.0-1~stretch +ENV UNIT_VERSION 1.10.0-1~stretch RUN set -x \ && apt-get update \ diff --git a/pkg/docker/Dockerfile.php7.0 b/pkg/docker/Dockerfile.php7.0 index b14a38b4..e766bfbf 100644 --- a/pkg/docker/Dockerfile.php7.0 +++ b/pkg/docker/Dockerfile.php7.0 @@ -2,7 +2,7 @@ FROM debian:stretch-slim LABEL maintainer="NGINX Docker Maintainers " -ENV UNIT_VERSION 1.9.0-1~stretch +ENV UNIT_VERSION 1.10.0-1~stretch RUN set -x \ && apt-get update \ diff --git a/pkg/docker/Dockerfile.python2.7 b/pkg/docker/Dockerfile.python2.7 index 82de3318..1d0410e1 100644 --- a/pkg/docker/Dockerfile.python2.7 +++ b/pkg/docker/Dockerfile.python2.7 @@ -2,7 +2,7 @@ FROM debian:stretch-slim LABEL maintainer="NGINX Docker Maintainers " -ENV UNIT_VERSION 1.9.0-1~stretch +ENV UNIT_VERSION 1.10.0-1~stretch RUN set -x \ && apt-get update \ diff --git a/pkg/docker/Dockerfile.python3.5 b/pkg/docker/Dockerfile.python3.5 index b103d8f9..d83fb984 100644 --- a/pkg/docker/Dockerfile.python3.5 +++ b/pkg/docker/Dockerfile.python3.5 @@ -2,7 +2,7 @@ FROM debian:stretch-slim LABEL maintainer="NGINX Docker Maintainers " -ENV UNIT_VERSION 1.9.0-1~stretch +ENV UNIT_VERSION 1.10.0-1~stretch RUN set -x \ && apt-get update \ diff --git a/pkg/docker/Dockerfile.ruby2.3 b/pkg/docker/Dockerfile.ruby2.3 index 50ec7ed6..0cdc76c2 100644 --- a/pkg/docker/Dockerfile.ruby2.3 +++ b/pkg/docker/Dockerfile.ruby2.3 @@ -2,7 +2,7 @@ FROM debian:stretch-slim LABEL maintainer="NGINX Docker Maintainers " -ENV UNIT_VERSION 1.9.0-1~stretch +ENV UNIT_VERSION 1.10.0-1~stretch RUN set -x \ && apt-get update \ -- cgit From bc57d1d07680e7605deb8ab959e19a76b5400af3 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Thu, 22 Aug 2019 18:56:22 +0300 Subject: Added tag 1.10.0 for changeset cdbba3c3e376 --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index 2868e225..619400ac 100644 --- a/.hgtags +++ b/.hgtags @@ -16,3 +16,4 @@ d411e7fdee9e03036adb652f8d9f4c45a420bdd5 1.6 0f04ef991fbc1dadbc590ab7fb229d4f3d6357bc 1.7.1 0a18a14d169f156f8e2daca35aa86d5a6dd9b1ae 1.8.0 dda6319de785dc2d225d818349aba56fc48d47f6 1.9.0 +cdbba3c3e3762eacc308a5407877c3665a05058d 1.10.0 -- cgit From c47af243b0e805376c4ec908f21e07dc811b33f0 Mon Sep 17 00:00:00 2001 From: Andrey Zelenkov Date: Thu, 22 Aug 2019 21:28:03 +0300 Subject: Tests: removed keepalive_interval for websocket tests. Also increased read_timeout in frame_read() for slow hosts. --- test/test_node_websockets.py | 14 ++++++++++---- test/unit/applications/websockets.py | 4 ++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/test/test_node_websockets.py b/test/test_node_websockets.py index 895019c5..6652d8c5 100644 --- a/test/test_node_websockets.py +++ b/test/test_node_websockets.py @@ -12,6 +12,14 @@ class TestNodeWebsockets(TestApplicationNode): def setUp(self): super().setUp() + self.assertIn( + 'success', + self.conf( + {'http': {'websocket': {'keepalive_interval': 0}}}, 'settings' + ), + 'clear keepalive_interval', + ) + self.skip_alerts.extend( [ r'last message send failed', @@ -1530,14 +1538,13 @@ class TestNodeWebsockets(TestApplicationNode): self.ws.frame_write(sock, opcode, payload) # frame length is 101 self.check_close(sock, 1009) # 1009 - CLOSE_TOO_LARGE - @unittest.skip('not yet') def test_node_websockets_read_timeout(self): self.load('websockets/mirror') self.assertIn( 'success', self.conf( - {'http': {'websocket': {'read_timeout': 1}}}, 'settings' + {'http': {'websocket': {'read_timeout': 5}}}, 'settings' ), 'configure read_timeout', ) @@ -1551,14 +1558,13 @@ class TestNodeWebsockets(TestApplicationNode): self.check_close(sock, 1001) # 1001 - CLOSE_GOING_AWAY - @unittest.skip('not yet') def test_node_websockets_keepalive_interval(self): self.load('websockets/mirror') self.assertIn( 'success', self.conf( - {'http': {'websocket': {'keepalive_interval': 1}}}, 'settings' + {'http': {'websocket': {'keepalive_interval': 5}}}, 'settings' ), 'configure keepalive_interval', ) diff --git a/test/unit/applications/websockets.py b/test/unit/applications/websockets.py index d64b960c..417e9504 100644 --- a/test/unit/applications/websockets.py +++ b/test/unit/applications/websockets.py @@ -57,7 +57,7 @@ class TestApplicationWebsocket(TestApplicationProto): def serialize_close(self, code = 1000, reason = ''): return struct.pack('!H', code) + reason.encode('utf-8') - def frame_read(self, sock, read_timeout=5): + def frame_read(self, sock, read_timeout=10): def recv_bytes(sock, bytes): data = b'' while select.select([sock], [], [], read_timeout)[0]: @@ -204,7 +204,7 @@ class TestApplicationWebsocket(TestApplicationProto): op_code = self.OP_CONT pos = end - def message_read(self, sock, read_timeout=5): + def message_read(self, sock, read_timeout=10): frame = self.frame_read(sock, read_timeout=read_timeout) while(not frame['fin']): -- cgit