From e4d9cffedfda046134ad7b152aa751c6e6404e06 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Thu, 15 Nov 2018 20:08:43 +0300 Subject: Version bump. --- src/nxt_main.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nxt_main.h b/src/nxt_main.h index 12c0ce6d..71ee6599 100644 --- a/src/nxt_main.h +++ b/src/nxt_main.h @@ -11,8 +11,8 @@ #include -#define NXT_VERSION "1.6" -#define NXT_VERNUM 10600 +#define NXT_VERSION "1.7" +#define NXT_VERNUM 10700 #define NXT_SERVER "Unit/" NXT_VERSION -- cgit From adf22b6a0d3481f7fc4d38ade08a2a0dd4ea6f19 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Wed, 21 Nov 2018 18:22:19 +0300 Subject: PHP: fixed compatibility with ZTS. This closes #184 issue on GitHub. --- src/nxt_php_sapi.c | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/nxt_php_sapi.c b/src/nxt_php_sapi.c index 413764f1..fbddd779 100644 --- a/src/nxt_php_sapi.c +++ b/src/nxt_php_sapi.c @@ -45,19 +45,19 @@ static void nxt_php_set_options(nxt_task_t *task, nxt_conf_value_t *options, int type); static nxt_int_t nxt_php_alter_option(nxt_str_t *name, nxt_str_t *value, int type); -static int nxt_php_send_headers(sapi_headers_struct *sapi_headers); -static char *nxt_php_read_cookies(void); +static int nxt_php_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC); +static char *nxt_php_read_cookies(TSRMLS_D); static void nxt_php_set_sptr(nxt_unit_request_info_t *req, const char *name, nxt_unit_sptr_t *v, uint32_t len, zval *track_vars_array TSRMLS_DC); nxt_inline void nxt_php_set_str(nxt_unit_request_info_t *req, const char *name, nxt_str_t *s, zval *track_vars_array TSRMLS_DC); static void nxt_php_set_cstr(nxt_unit_request_info_t *req, const char *name, char *str, uint32_t len, zval *track_vars_array TSRMLS_DC); -static void nxt_php_register_variables(zval *track_vars_array); +static void nxt_php_register_variables(zval *track_vars_array TSRMLS_DC); #ifdef NXT_HAVE_PHP_LOG_MESSAGE_WITH_SYSLOG_TYPE static void nxt_php_log_message(char *message, int syslog_type_int); #else -static void nxt_php_log_message(char *message); +static void nxt_php_log_message(char *message TSRMLS_DC); #endif #ifdef NXT_PHP7 @@ -159,6 +159,9 @@ NXT_EXPORT nxt_app_module_t nxt_app_module = { static nxt_task_t *nxt_php_task; +#ifdef ZTS +static void ***tsrm_ls; +#endif static nxt_int_t @@ -262,6 +265,17 @@ nxt_php_init(nxt_task_t *task, nxt_common_app_conf_t *conf) nxt_memcpy(index->start, c->index.start, c->index.length); } +#ifdef ZTS + tsrm_startup(1, 1, 0, NULL); + tsrm_ls = ts_resource(0); +#endif + +#ifdef NXT_PHP7 +#if defined(ZEND_SIGNALS) || PHP_MINOR_VERSION > 0 + zend_signal_startup(); +#endif +#endif + sapi_startup(&nxt_php_sapi_module); if (c->options != NULL) { @@ -433,7 +447,8 @@ nxt_php_alter_option(nxt_str_t *name, nxt_str_t *value, int type) if (ini_entry->on_modify && ini_entry->on_modify(ini_entry, cstr, value->length, ini_entry->mh_arg1, ini_entry->mh_arg2, - ini_entry->mh_arg3, ZEND_INI_STAGE_ACTIVATE) + ini_entry->mh_arg3, ZEND_INI_STAGE_ACTIVATE + TSRMLS_CC) != SUCCESS) { nxt_free(cstr); @@ -573,7 +588,11 @@ nxt_php_request_handler(nxt_unit_request_info_t *req) (char *) ctx->script.start); } +#if (NXT_PHP7) if (nxt_slow_path(php_request_startup() == FAILURE)) { +#else + if (nxt_slow_path(php_request_startup(TSRMLS_C) == FAILURE)) { +#endif nxt_unit_req_debug(req, "php_request_startup() failed"); rc = NXT_UNIT_ERROR; @@ -915,7 +934,7 @@ static void nxt_php_log_message(char *message, int syslog_type_int) #else static void -nxt_php_log_message(char *message) +nxt_php_log_message(char *message TSRMLS_DC) #endif { nxt_log(nxt_php_task, NXT_LOG_NOTICE, "php message: %s", message); -- cgit From 262578dc71e4c1aaad01656a9b0c78539b1e7371 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Thu, 22 Nov 2018 20:23:43 +0300 Subject: PHP: workaround for bug #71041. Since PHP 7, a zend_signal_startup() call is required if the interpreter was built with ZEND_SIGNALS defined; such a call was added in 3fd76e4ce70a. However, the zend_signal_startup() export is missing from the PHP library; as the result, dlopen() fails with the 'Undefined symbol "zend_signal_startup"' error while loading the PHP module. Meanwhile, if PHP is built without ZTS, the zend_signal_startup() call can be omitted; otherwise, the missing call causes segmentation fault. The PHP fix already was committed to upstream, but we still have to deal with numerous unpatched versions remaining at large. See the related PHP bug: https://bugs.php.net/bug.php?id=71041 --- src/nxt_php_sapi.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nxt_php_sapi.c b/src/nxt_php_sapi.c index fbddd779..47b5ff04 100644 --- a/src/nxt_php_sapi.c +++ b/src/nxt_php_sapi.c @@ -270,10 +270,14 @@ nxt_php_init(nxt_task_t *task, nxt_common_app_conf_t *conf) tsrm_ls = ts_resource(0); #endif -#ifdef NXT_PHP7 -#if defined(ZEND_SIGNALS) || PHP_MINOR_VERSION > 0 +#if defined(NXT_PHP7) && defined(ZEND_SIGNALS) + +#if (NXT_ZEND_SIGNAL_STARTUP) zend_signal_startup(); +#elif defined(ZTS) +#error PHP is built with thread safety and broken signals. #endif + #endif sapi_startup(&nxt_php_sapi_module); -- cgit From db631917190c44b3b55a15e4e5e88aa92e6b5334 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Tue, 27 Nov 2018 22:06:39 +0300 Subject: PHP: fixed "disable_functions" and "disable_classes" options. It turned out they need additional processing to work. This closes #183 issue on GitHub. --- src/nxt_php_sapi.c | 97 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 87 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/nxt_php_sapi.c b/src/nxt_php_sapi.c index 47b5ff04..8c25f82a 100644 --- a/src/nxt_php_sapi.c +++ b/src/nxt_php_sapi.c @@ -15,16 +15,6 @@ #include -typedef struct nxt_php_run_ctx_s nxt_php_run_ctx_t; - -static nxt_int_t nxt_php_init(nxt_task_t *task, nxt_common_app_conf_t *conf); - -static void nxt_php_str_trim_trail(nxt_str_t *str, u_char t); -static void nxt_php_str_trim_lead(nxt_str_t *str, u_char t); -nxt_inline u_char *nxt_realpath(const void *c); - -static void nxt_php_request_handler(nxt_unit_request_info_t *req); - #if PHP_MAJOR_VERSION >= 7 # define NXT_PHP7 1 # if PHP_MINOR_VERSION >= 1 @@ -40,11 +30,31 @@ static void nxt_php_request_handler(nxt_unit_request_info_t *req); # endif #endif + +typedef struct nxt_php_run_ctx_s nxt_php_run_ctx_t; + +#ifdef NXT_PHP7 +typedef int (*nxt_php_disable_t)(char *p, size_t size); +#else +typedef int (*nxt_php_disable_t)(char *p, uint TSRMLS_DC); +#endif + + +static nxt_int_t nxt_php_init(nxt_task_t *task, nxt_common_app_conf_t *conf); + +static void nxt_php_str_trim_trail(nxt_str_t *str, u_char t); +static void nxt_php_str_trim_lead(nxt_str_t *str, u_char t); +nxt_inline u_char *nxt_realpath(const void *c); + +static void nxt_php_request_handler(nxt_unit_request_info_t *req); + static int nxt_php_startup(sapi_module_struct *sapi_module); static void nxt_php_set_options(nxt_task_t *task, nxt_conf_value_t *options, int type); static nxt_int_t nxt_php_alter_option(nxt_str_t *name, nxt_str_t *value, int type); +static void nxt_php_disable(nxt_task_t *task, const char *type, + nxt_str_t *value, char **ptr, nxt_php_disable_t disable); static int nxt_php_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC); static char *nxt_php_read_cookies(TSRMLS_D); static void nxt_php_set_sptr(nxt_unit_request_info_t *req, const char *name, @@ -377,6 +387,21 @@ nxt_php_set_options(nxt_task_t *task, nxt_conf_value_t *options, int type) if (nxt_php_alter_option(&name, &value, type) != NXT_OK) { nxt_log(task, NXT_LOG_ERR, "setting PHP option \"%V: %V\" failed", &name, &value); + continue; + } + + if (nxt_str_eq(&name, "disable_functions", 17)) { + nxt_php_disable(task, "function", &value, + &PG(disable_functions), + zend_disable_function); + continue; + } + + if (nxt_str_eq(&name, "disable_classes", 15)) { + nxt_php_disable(task, "class", &value, + &PG(disable_classes), + zend_disable_class); + continue; } } } @@ -469,6 +494,58 @@ nxt_php_alter_option(nxt_str_t *name, nxt_str_t *value, int type) #endif +static void +nxt_php_disable(nxt_task_t *task, const char *type, nxt_str_t *value, + char **ptr, nxt_php_disable_t disable) +{ + char c, *p, *start; + + p = nxt_malloc(value->length + 1); + if (nxt_slow_path(p == NULL)) { + return; + } + + /* + * PHP frees this memory on module shutdown. + * See core_globals_dtor() for details. + */ + *ptr = p; + + nxt_memcpy(p, value->start, value->length); + p[value->length] = '\0'; + + start = p; + + do { + c = *p; + + if (c == ' ' || c == ',' || c == '\0') { + + if (p != start) { + *p = '\0'; + +#ifdef NXT_PHP7 + if (disable(start, p - start) +#else + if (disable(start, p - start TSRMLS_CC) +#endif + != SUCCESS) + { + nxt_log(task, NXT_LOG_ERR, + "PHP: failed to disable \"%s\": no such %s", + start, type); + } + } + + start = p + 1; + } + + p++; + + } while (c != '\0'); +} + + static void nxt_php_str_trim_trail(nxt_str_t *str, u_char t) { -- cgit From aeb026c8ab41b907f2e19a9f2fa978717d034830 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Wed, 12 Dec 2018 19:57:15 +0300 Subject: Node.js: removed unused dependency. --- src/nodejs/unit-http/package.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'src') diff --git a/src/nodejs/unit-http/package.json b/src/nodejs/unit-http/package.json index 3a15d573..6a2cd27c 100644 --- a/src/nodejs/unit-http/package.json +++ b/src/nodejs/unit-http/package.json @@ -22,8 +22,5 @@ }, "author": "Alexander Borisov", "license": "Apache-2.0", - "gypfile": true, - "dependencies": { - "node-addon-api": "1.2.0" - } + "gypfile": true } -- cgit From de3c062c6e3e869d726b93a1ffe617059df7611a Mon Sep 17 00:00:00 2001 From: Alexander Borisov Date: Wed, 19 Dec 2018 15:55:54 +0300 Subject: Node.js: buffering HTTP headers before writing the body. --- src/nodejs/unit-http/http_server.js | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/nodejs/unit-http/http_server.js b/src/nodejs/unit-http/http_server.js index 57163c0b..47851c98 100755 --- a/src/nodejs/unit-http/http_server.js +++ b/src/nodejs/unit-http/http_server.js @@ -178,21 +178,16 @@ function writeHead(statusCode, reason, obj) { } } } - - unit_lib.unit_response_headers(this, statusCode, this.headers, this.headers_count, this.headers_len); - - this.headersSent = true; }; ServerResponse.prototype._writeBody = function(chunk, encoding, callback) { var contentLength = 0; if (!this.headersSent) { - this.writeHead(this.statusCode); - } + unit_lib.unit_response_headers(this, this.statusCode, this.headers, + this.headers_count, this.headers_len); - if (this.finished) { - return this; + this.headersSent = true; } if (typeof chunk === 'function') { @@ -225,15 +220,23 @@ ServerResponse.prototype._writeBody = function(chunk, encoding, callback) { }; ServerResponse.prototype.write = function write(chunk, encoding, callback) { + if (this.finished) { + throw new Error("Write after end"); + } + this._writeBody(chunk, encoding, callback); return true; }; ServerResponse.prototype.end = function end(chunk, encoding, callback) { - this._writeBody(chunk, encoding, callback); + if (!this.finished) { + this._writeBody(chunk, encoding, callback); - this.finished = true; + unit_lib.unit_response_end(this); + + this.finished = true; + } return this; }; -- cgit From 13c9ebccca9c7bee80f4b9c1da4c128435d9dac1 Mon Sep 17 00:00:00 2001 From: Alexander Borisov Date: Wed, 19 Dec 2018 15:55:58 +0300 Subject: Node.js: changed the 'data' event calling sequence for the request. The problem is caused by Promises' inconsistency. The 'date' event could have been triggered before the user has started listening for it. To resolve the issue, we override the 'on' method of the request's emitter. --- src/nodejs/unit-http/http_server.js | 40 +++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/nodejs/unit-http/http_server.js b/src/nodejs/unit-http/http_server.js index 47851c98..9b7b8403 100755 --- a/src/nodejs/unit-http/http_server.js +++ b/src/nodejs/unit-http/http_server.js @@ -288,6 +288,28 @@ ServerRequest.prototype.resume = function resume() { return []; }; +/* + * The "on" method is overridden to defer reading data until user code is + * ready, that is (ev === "data"). This can occur after req.emit("end") is + * executed, since the user code can be scheduled asynchronously by Promises + * and so on. Passing the data is postponed by process.nextTick() until + * the "on" method caller completes. + */ +ServerRequest.prototype.on = function on(ev, fn) { + Server.prototype.on.call(this, ev, fn); + + if (ev === "data") { + process.nextTick(function () { + if (this.server.buffer.length !== 0) { + this.emit("data", this.server.buffer); + } + + }.bind(this)); + } +}; + +ServerRequest.prototype.addListener = ServerRequest.prototype.on; + function Server(requestListener) { EventEmitter.call(this); @@ -321,22 +343,20 @@ Server.prototype.listen = function () { }; Server.prototype.run_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); + /* Important!!! setImmediate starts the next iteration in Node.js loop. */ setImmediate(function () { server.emit("request", req, res); - Promise.resolve().then(() => { - let buf = server.unit._read(req.socket.req_pointer); - - if (buf.length != 0) { - req.emit("data", buf); - } - - req.emit("end"); - }); - Promise.resolve().then(() => { req.emit("finish"); + req.emit("end"); if (res.finished) { unit_lib.unit_response_end(res); -- cgit From f47a5db506911f1cf117bdc15474d40508bb7601 Mon Sep 17 00:00:00 2001 From: Alexander Borisov Date: Wed, 19 Dec 2018 15:56:01 +0300 Subject: Node.js: napi_call_function() replaced with napi_make_callback(). The sequence of napi_open_callback_scope(), napi_call_function(), and napi_close_callback_scope() functions calls executes the provided JS code and all functions enqueued by process.nextTick() and Promises during this execution. --- src/nodejs/unit-http/http_server.js | 17 +++------ src/nodejs/unit-http/unit.cpp | 73 +++++++++++++++++++++++++++++-------- 2 files changed, 63 insertions(+), 27 deletions(-) (limited to 'src') diff --git a/src/nodejs/unit-http/http_server.js b/src/nodejs/unit-http/http_server.js index 9b7b8403..8a536cde 100755 --- a/src/nodejs/unit-http/http_server.js +++ b/src/nodejs/unit-http/http_server.js @@ -342,7 +342,7 @@ Server.prototype.listen = function () { this.unit.listen(); }; -Server.prototype.run_events = function (server, req, res) { +Server.prototype.emit_events = function (server, req, res) { req.server = server; res.server = server; req.res = res; @@ -350,18 +350,11 @@ Server.prototype.run_events = function (server, req, res) { server.buffer = server.unit._read(req.socket.req_pointer); - /* Important!!! setImmediate starts the next iteration in Node.js loop. */ - setImmediate(function () { - server.emit("request", req, res); + server.emit("request", req, res); - Promise.resolve().then(() => { - req.emit("finish"); - req.emit("end"); - - if (res.finished) { - unit_lib.unit_response_end(res); - } - }); + process.nextTick(() => { + req.emit("finish"); + req.emit("end"); }); }; diff --git a/src/nodejs/unit-http/unit.cpp b/src/nodejs/unit-http/unit.cpp index be64a59b..b233359c 100644 --- a/src/nodejs/unit-http/unit.cpp +++ b/src/nodejs/unit-http/unit.cpp @@ -276,12 +276,13 @@ Unit::_read(napi_env env, napi_callback_info info) void Unit::request_handler(nxt_unit_request_info_t *req) { - Unit *obj; - napi_value socket, request, response; - napi_value global, server_obj; - napi_value run_events, events_res; - napi_status status; - napi_value events_args[3]; + Unit *obj; + napi_value socket, request, response, global, server_obj; + napi_value emit_events, events_res, async_name, resource_object; + napi_status status; + napi_async_context async_context; + napi_callback_scope async_scope; + napi_value events_args[3]; obj = reinterpret_cast(req->unit->data); @@ -328,11 +329,11 @@ Unit::request_handler(nxt_unit_request_info_t *req) return; } - status = napi_get_named_property(obj->env_, server_obj, "run_events", - &run_events); + status = napi_get_named_property(obj->env_, server_obj, "emit_events", + &emit_events); if (status != napi_ok) { - napi_throw_error(obj->env_, NULL, "Failed to get" - " 'run_events' function"); + napi_throw_error(obj->env_, NULL, "Failed to get " + "'emit_events' function"); return; } @@ -340,15 +341,57 @@ Unit::request_handler(nxt_unit_request_info_t *req) events_args[1] = request; events_args[2] = response; - status = napi_call_function(obj->env_, server_obj, run_events, 3, - events_args, &events_res); + status = napi_create_string_utf8(obj->env_, "unit_request_handler", + sizeof("unit_request_handler") - 1, + &async_name); + if (status != napi_ok) { + napi_throw_error(obj->env_, NULL, "Failed to create utf-8 string"); + return; + } + + status = napi_async_init(obj->env_, NULL, async_name, &async_context); + if (status != napi_ok) { + napi_throw_error(obj->env_, NULL, "Failed to init async object"); + return; + } + + status = napi_create_object(obj->env_, &resource_object); + if (status != napi_ok) { + napi_throw_error(obj->env_, NULL, "Failed to create object for " + "callback scope"); + return; + } + + status = napi_open_callback_scope(obj->env_, resource_object, async_context, + &async_scope); if (status != napi_ok) { - napi_throw_error(obj->env_, NULL, "Failed to call" - " 'run_events' function"); + napi_throw_error(obj->env_, NULL, "Failed to open callback scope"); return; } - napi_close_handle_scope(obj->env_, scope); + status = napi_make_callback(obj->env_, async_context, server_obj, + emit_events, 3, events_args, &events_res); + if (status != napi_ok) { + napi_throw_error(obj->env_, NULL, "Failed to make callback"); + return; + } + + status = napi_close_callback_scope(obj->env_, async_scope); + if (status != napi_ok) { + napi_throw_error(obj->env_, NULL, "Failed to close callback scope"); + return; + } + + status = napi_async_destroy(obj->env_, async_context); + if (status != napi_ok) { + napi_throw_error(obj->env_, NULL, "Failed to destroy async object"); + return; + } + + status = napi_close_handle_scope(obj->env_, scope); + if (status != napi_ok) { + napi_throw_error(obj->env_, NULL, "Failed to close handle scope"); + } } -- cgit From dc16885b60f1d31d09114ea6140384531cad78e0 Mon Sep 17 00:00:00 2001 From: Alexander Borisov Date: Wed, 19 Dec 2018 15:56:06 +0300 Subject: Node.js: changed the unit-http socket constructor. Third-party file descriptors are not supported. Socket "readable" and "writable" options are set true by default. --- src/nodejs/unit-http/socket.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/nodejs/unit-http/socket.js b/src/nodejs/unit-http/socket.js index aef065bf..6e836949 100755 --- a/src/nodejs/unit-http/socket.js +++ b/src/nodejs/unit-http/socket.js @@ -18,10 +18,16 @@ function Socket(options) { throw new TypeError('Options must be object'); } - this.readable = (typeof options.readable === 'boolean' ? options.readable - : false); - this.writable = (typeof options.writable === 'boolean' ? options.writable - : false); + if ("fd" in options) { + throw new TypeError('Working with file descriptors not supported'); + } + + /* + * For HTTP TCP socket 'readable' and 'writable' are always true. + * These options are required by Express and Koa frameworks. + */ + this.readable = true; + this.writable = true; } util.inherits(Socket, EventEmitter); @@ -43,7 +49,6 @@ Socket.prototype.connect = function connect(options, connectListener) { this.once('connect', connectListener); this.connecting = true; - this.writable = true; return this; }; -- cgit From 704fe556b423795d06f79cec3a3406ef6defb635 Mon Sep 17 00:00:00 2001 From: Alexander Borisov Date: Wed, 19 Dec 2018 15:56:09 +0300 Subject: Node.js: style fixes. No functional changes. --- src/nodejs/unit-http/http_server.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/nodejs/unit-http/http_server.js b/src/nodejs/unit-http/http_server.js index 8a536cde..c93d2b10 100755 --- a/src/nodejs/unit-http/http_server.js +++ b/src/nodejs/unit-http/http_server.js @@ -105,21 +105,23 @@ ServerResponse.prototype.removeHeader = function removeHeader(name) { } let name_len = Buffer.byteLength(name + "", 'latin1'); + let value = this.headers[name]; - if (Array.isArray(this.headers[name])) { - this.headers_count -= this.headers[name].length; - this.headers_len -= this.headers[name].length * name_len; + delete this.headers[name]; + + if (Array.isArray(value)) { + this.headers_count -= value.length; + this.headers_len -= value.length * name_len; - this.headers[name].forEach(function(val) { + value.forEach(function(val) { this.headers_len -= Buffer.byteLength(val + "", 'latin1'); }); - } else { - this.headers_count--; - this.headers_len -= name_len + Buffer.byteLength(this.headers[name] + "", 'latin1'); + return; } - delete this.headers[name]; + this.headers_count--; + this.headers_len -= name_len + Buffer.byteLength(value + "", 'latin1'); }; ServerResponse.prototype.sendDate = function sendDate() { -- cgit From 607653c0f12a99981dbf3118b60b84ded4084d50 Mon Sep 17 00:00:00 2001 From: Alexander Borisov Date: Wed, 19 Dec 2018 15:56:13 +0300 Subject: Node.js: calling write callback asynchronously. --- src/nodejs/unit-http/http_server.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nodejs/unit-http/http_server.js b/src/nodejs/unit-http/http_server.js index c93d2b10..28f2303f 100755 --- a/src/nodejs/unit-http/http_server.js +++ b/src/nodejs/unit-http/http_server.js @@ -217,7 +217,19 @@ ServerResponse.prototype._writeBody = function(chunk, encoding, callback) { } if (typeof callback === 'function') { - callback(this); + /* + * The callback must be called only when response.write() caller + * completes. process.nextTick() postpones the callback execution. + * + * process.nextTick() is not technically part of the event loop. + * Instead, the nextTickQueue will be processed after the current + * operation completes, regardless of the current phase of + * the event loop. All callbacks passed to process.nextTick() + * will be resolved before the event loop continues. + */ + process.nextTick(function () { + callback(this); + }.bind(this)); } }; -- cgit From dcf51274ce0953e577fbfffd81afb592319a2267 Mon Sep 17 00:00:00 2001 From: Alexander Borisov Date: Wed, 19 Dec 2018 15:56:30 +0300 Subject: Node.js: checking uniqueness of HTTP headers for different case. --- src/nodejs/unit-http/http_server.js | 72 +++++++++++++++++++++++++------------ src/nodejs/unit-http/unit.cpp | 14 ++++++-- 2 files changed, 62 insertions(+), 24 deletions(-) (limited to 'src') diff --git a/src/nodejs/unit-http/http_server.js b/src/nodejs/unit-http/http_server.js index 28f2303f..216c4394 100755 --- a/src/nodejs/unit-http/http_server.js +++ b/src/nodejs/unit-http/http_server.js @@ -47,24 +47,23 @@ ServerResponse.prototype.writeContinue = function writeContinue(cb) { ServerResponse.prototype.writeProcessing = function writeProcessing(cb) { }; -ServerResponse.prototype.setHeader = function setHeader(key, value) { - if (typeof key !== 'string') { - throw new TypeError('Key argument must be a string'); +ServerResponse.prototype.setHeader = function setHeader(name, value) { + if (typeof name !== 'string') { + throw new TypeError('Name argument must be a string'); } - let header_key_len = Buffer.byteLength(key, 'latin1'); - let header_len = 0 - let header_count = 0; + let value_len = 0 + let count = 0; if (Array.isArray(value)) { - header_count = value.length; + count = value.length; value.forEach(function(val) { if (typeof val !== 'string' && typeof val !== 'number') { throw new TypeError('Array entries must be string or number'); } - header_len += Buffer.byteLength(val + "", 'latin1'); + value_len += Buffer.byteLength(val + "", 'latin1'); }); } else { @@ -72,19 +71,27 @@ ServerResponse.prototype.setHeader = function setHeader(key, value) { throw new TypeError('Value argument must be string, number, or array'); } - header_count = 1; - header_len = Buffer.byteLength(value + "", 'latin1'); + count = 1; + value_len = Buffer.byteLength(value + "", 'latin1'); } - this.removeHeader(key); + let lc_name = name.toLowerCase(); - this.headers[key] = value; - this.headers_len += header_len + (header_key_len * header_count); - this.headers_count += header_count; + if (lc_name in this.headers) { + this._removeHeader(lc_name); + } + + let name_len = Buffer.byteLength(name, 'latin1'); + + this.headers[lc_name] = [name, value]; + this.headers_len += value_len + (name_len * count); + this.headers_count += count; }; ServerResponse.prototype.getHeader = function getHeader(name) { - return this.headers[name]; + const entry = this.headers[name.toLowerCase()]; + + return entry && entry[1]; }; ServerResponse.prototype.getHeaderNames = function getHeaderNames() { @@ -92,22 +99,43 @@ ServerResponse.prototype.getHeaderNames = function getHeaderNames() { }; ServerResponse.prototype.getHeaders = function getHeaders() { - return this.headers; + const ret = Object.create(null); + + if (this.headers) { + const keys = Object.keys(this.headers); + + for (var i = 0; i < keys.length; i++) { + const key = keys[i]; + + ret[key] = this.headers[key][1]; + } + } + + return ret; }; ServerResponse.prototype.hasHeader = function hasHeader(name) { - return name in this.headers; + return name.toLowerCase() in this.headers; }; ServerResponse.prototype.removeHeader = function removeHeader(name) { - if (!(name in this.headers)) { - return; + if (typeof name !== 'string') { + throw new TypeError('Name argument must be a string'); } - let name_len = Buffer.byteLength(name + "", 'latin1'); - let value = this.headers[name]; + let lc_name = name.toLowerCase(); + + if (lc_name in this.headers) { + this._removeHeader(lc_name); + } +}; + +ServerResponse.prototype._removeHeader = function _removeHeader(lc_name) { + let entry = this.headers[lc_name]; + let name_len = Buffer.byteLength(entry[0] + "", 'latin1'); + let value = entry[1]; - delete this.headers[name]; + delete this.headers[lc_name]; if (Array.isArray(value)) { this.headers_count -= value.length; diff --git a/src/nodejs/unit-http/unit.cpp b/src/nodejs/unit-http/unit.cpp index b233359c..36bc98db 100644 --- a/src/nodejs/unit-http/unit.cpp +++ b/src/nodejs/unit-http/unit.cpp @@ -737,7 +737,7 @@ Unit::response_send_headers(napi_env env, napi_callback_info info) uint32_t keys_count, i, j; uint16_t hash; napi_value this_arg, headers, keys, name, value, array_val; - napi_value req_num; + napi_value req_num, array_entry; napi_status status; napi_valuetype val_type; nxt_unit_field_t *f; @@ -814,7 +814,17 @@ Unit::response_send_headers(napi_env env, napi_callback_info info) goto failed; } - status = napi_get_property(env, headers, name, &value); + status = napi_get_property(env, headers, name, &array_entry); + if (status != napi_ok) { + goto failed; + } + + status = napi_get_element(env, array_entry, 0, &name); + if (status != napi_ok) { + goto failed; + } + + status = napi_get_element(env, array_entry, 1, &value); if (status != napi_ok) { goto failed; } -- cgit From ab461437b5714d75e710febe3b44da49ebfe83fc Mon Sep 17 00:00:00 2001 From: Alexander Borisov Date: Wed, 19 Dec 2018 15:56:37 +0300 Subject: Node.js: checking for exception after running JS code from C++. --- src/nodejs/unit-http/unit.cpp | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nodejs/unit-http/unit.cpp b/src/nodejs/unit-http/unit.cpp index 36bc98db..60b0412a 100644 --- a/src/nodejs/unit-http/unit.cpp +++ b/src/nodejs/unit-http/unit.cpp @@ -277,7 +277,7 @@ void Unit::request_handler(nxt_unit_request_info_t *req) { Unit *obj; - napi_value socket, request, response, global, server_obj; + napi_value socket, request, response, global, server_obj, except; napi_value emit_events, events_res, async_name, resource_object; napi_status status; napi_async_context async_context; @@ -372,8 +372,25 @@ Unit::request_handler(nxt_unit_request_info_t *req) status = napi_make_callback(obj->env_, async_context, server_obj, emit_events, 3, events_args, &events_res); if (status != napi_ok) { - napi_throw_error(obj->env_, NULL, "Failed to make callback"); - return; + if (status != napi_pending_exception) { + napi_throw_error(obj->env_, NULL, "Failed to make callback"); + return; + } + + status = napi_get_and_clear_last_exception(obj->env_, &except); + if (status != napi_ok) { + napi_throw_error(obj->env_, NULL, + "Failed to get and clear last exception"); + return; + } + + /* Logging a description of the error and call stack. */ + status = napi_fatal_exception(obj->env_, except); + if (status != napi_ok) { + napi_throw_error(obj->env_, NULL, "Failed to call " + "napi_fatal_exception() function"); + return; + } } status = napi_close_callback_scope(obj->env_, async_scope); -- cgit From c55f329ecf8d32bad0d5bc2aae1da1522b3a92f8 Mon Sep 17 00:00:00 2001 From: Alexander Borisov Date: Wed, 19 Dec 2018 15:56:54 +0300 Subject: libunit: added generation of version header file. --- src/nodejs/unit-http/binding.gyp | 2 +- src/nxt_unit.h | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nodejs/unit-http/binding.gyp b/src/nodejs/unit-http/binding.gyp index 171c2eb7..ee09bfed 100644 --- a/src/nodejs/unit-http/binding.gyp +++ b/src/nodejs/unit-http/binding.gyp @@ -3,7 +3,7 @@ 'target_name': "unit-http", 'sources': ["unit.cpp", "addon.cpp"], 'include_dirs': [ - " #include +#include "nxt_unit_version.h" #include "nxt_unit_typedefs.h" + enum { NXT_UNIT_OK = 0, NXT_UNIT_ERROR = 1, -- cgit From d6f38a3268dd4df717bdb9054f3f9ee47fa1f429 Mon Sep 17 00:00:00 2001 From: Alexander Borisov Date: Wed, 19 Dec 2018 15:56:57 +0300 Subject: Node.js: added check for libunit version at compile time. --- src/nodejs/unit-http/package.json | 9 +++++---- src/nodejs/unit-http/unit.h | 9 +++++++-- 2 files changed, 12 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/nodejs/unit-http/package.json b/src/nodejs/unit-http/package.json index 6a2cd27c..13c91018 100644 --- a/src/nodejs/unit-http/package.json +++ b/src/nodejs/unit-http/package.json @@ -4,14 +4,15 @@ "description": "HTTP module for NGINX Unit", "main": "http.js", "files": [ + "unit.h", + "version.h", "addon.cpp", - "binding.gyp", - "http_server.js", + "unit.cpp", "http.js", + "http_server.js", "package.json", "socket.js", - "unit.cpp", - "unit.h", + "binding.gyp", "README.md" ], "scripts": { diff --git a/src/nodejs/unit-http/unit.h b/src/nodejs/unit-http/unit.h index 5f541cc4..8baeb967 100644 --- a/src/nodejs/unit-http/unit.h +++ b/src/nodejs/unit-http/unit.h @@ -6,18 +6,23 @@ #ifndef _NXT_NODEJS_UNIT_H_INCLUDED_ #define _NXT_NODEJS_UNIT_H_INCLUDED_ - #include - #ifdef __cplusplus extern "C" { #endif +#include "version.h" #include + +#if NXT_UNIT_VERNUM != NXT_NODE_VERNUM +#error "libunit version mismatch." +#endif + #include #include + #ifdef __cplusplus } /* extern "C" */ #endif -- cgit From d9dad07934c17319b6aecd5b4958cdef3a196ad0 Mon Sep 17 00:00:00 2001 From: Alexander Borisov Date: Wed, 19 Dec 2018 15:57:04 +0300 Subject: Node.js: removed unused _implicitHeader() function. --- src/nodejs/unit-http/http_server.js | 5 ----- 1 file changed, 5 deletions(-) (limited to 'src') diff --git a/src/nodejs/unit-http/http_server.js b/src/nodejs/unit-http/http_server.js index 216c4394..061c39aa 100755 --- a/src/nodejs/unit-http/http_server.js +++ b/src/nodejs/unit-http/http_server.js @@ -166,11 +166,6 @@ ServerResponse.prototype.setTimeout = function setTimeout(msecs, callback) { return this; }; -// for Express -ServerResponse.prototype._implicitHeader = function _implicitHeader() { - this.writeHead(this.statusCode); -}; - ServerResponse.prototype.writeHead = writeHead; ServerResponse.prototype.writeHeader = ServerResponse.prototype.writeHead; -- cgit From 98e8f366dabbe1b8e923678d9edd17ee61b54e0f Mon Sep 17 00:00:00 2001 From: Alexander Borisov Date: Wed, 19 Dec 2018 15:57:07 +0300 Subject: Node.js: removed value checking for headers. --- src/nodejs/unit-http/http_server.js | 8 -------- 1 file changed, 8 deletions(-) (limited to 'src') diff --git a/src/nodejs/unit-http/http_server.js b/src/nodejs/unit-http/http_server.js index 061c39aa..057a1f26 100755 --- a/src/nodejs/unit-http/http_server.js +++ b/src/nodejs/unit-http/http_server.js @@ -59,18 +59,10 @@ ServerResponse.prototype.setHeader = function setHeader(name, value) { count = value.length; value.forEach(function(val) { - if (typeof val !== 'string' && typeof val !== 'number') { - throw new TypeError('Array entries must be string or number'); - } - value_len += Buffer.byteLength(val + "", 'latin1'); }); } else { - if (typeof value !== 'string' && typeof value !== 'number') { - throw new TypeError('Value argument must be string, number, or array'); - } - count = 1; value_len = Buffer.byteLength(value + "", 'latin1'); } -- cgit From cebec46353b3a185f96520e012cbf52e40238cae Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Wed, 19 Dec 2018 20:06:53 +0300 Subject: Python: replaced PyErr_PrintEx(1) with PyErr_Print(). These function calls are equivalent. No functional changes. --- src/nxt_python_wsgi.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nxt_python_wsgi.c b/src/nxt_python_wsgi.c index 3a5f1913..739ee0b9 100644 --- a/src/nxt_python_wsgi.c +++ b/src/nxt_python_wsgi.c @@ -354,7 +354,7 @@ nxt_python_init(nxt_task_t *task, nxt_common_app_conf_t *conf) if (nxt_slow_path(module == NULL)) { nxt_alert(task, "Python failed to import module \"%s\"", nxt_py_module); - PyErr_PrintEx(1); + PyErr_Print(); return NXT_ERROR; } @@ -369,7 +369,7 @@ nxt_python_init(nxt_task_t *task, nxt_common_app_conf_t *conf) if (nxt_slow_path(PyCallable_Check(obj) == 0)) { nxt_alert(task, "\"application\" in module \"%s\" " "is not a callable object", nxt_py_module); - PyErr_PrintEx(1); + PyErr_Print(); goto fail; } @@ -804,7 +804,7 @@ nxt_python_add_sptr(nxt_python_run_ctx_t *ctx, const char *name, nxt_unit_req_error(ctx->req, "Python failed to create value string \"%.*s\"", (int) size, src); - PyErr_PrintEx(1); + PyErr_Print(); return NXT_UNIT_ERROR; } @@ -839,7 +839,7 @@ nxt_python_add_str(nxt_python_run_ctx_t *ctx, const char *name, nxt_unit_req_error(ctx->req, "Python failed to create value string \"%.*s\"", (int) size, str); - PyErr_PrintEx(1); + PyErr_Print(); return NXT_UNIT_ERROR; } -- cgit From 9c03079e3a4a7aead2f761a3a425bee065cc89d1 Mon Sep 17 00:00:00 2001 From: Artem Konev Date: Thu, 20 Dec 2018 14:17:58 +0300 Subject: Python: fixed a typo in path error message. --- src/nxt_python_wsgi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nxt_python_wsgi.c b/src/nxt_python_wsgi.c index 739ee0b9..df1c74ad 100644 --- a/src/nxt_python_wsgi.c +++ b/src/nxt_python_wsgi.c @@ -284,7 +284,7 @@ nxt_python_init(nxt_task_t *task, nxt_common_app_conf_t *conf) c->path.length); if (nxt_slow_path(obj == NULL)) { - nxt_alert(task, "Python failed create string object \"%V\"", + nxt_alert(task, "Python failed to create string object \"%V\"", &c->path); goto fail; } -- cgit From 1ce7e860b2913bc5c89258dca6c8fa9cf86ade35 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Thu, 20 Dec 2018 15:47:10 +0300 Subject: Python: cleanup of nxt_python_init(). - Removed surplus NULL assignments; - Added missing nxt_slow_path(); - Style cleanup. --- src/nxt_python_wsgi.c | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/nxt_python_wsgi.c b/src/nxt_python_wsgi.c index df1c74ad..b6566788 100644 --- a/src/nxt_python_wsgi.c +++ b/src/nxt_python_wsgi.c @@ -276,7 +276,6 @@ nxt_python_init(nxt_task_t *task, nxt_common_app_conf_t *conf) Py_InitializeEx(0); - obj = NULL; module = NULL; if (c->path.length > 0) { @@ -303,11 +302,9 @@ nxt_python_init(nxt_task_t *task, nxt_common_app_conf_t *conf) } Py_DECREF(obj); - obj = NULL; } obj = PyCFunction_New(nxt_py_start_resp_method, NULL); - if (nxt_slow_path(obj == NULL)) { nxt_alert(task, "Python failed to initialize the \"start_response\" function"); @@ -317,7 +314,6 @@ nxt_python_init(nxt_task_t *task, nxt_common_app_conf_t *conf) nxt_py_start_resp_obj = obj; obj = PyCFunction_New(nxt_py_write_method, NULL); - if (nxt_slow_path(obj == NULL)) { nxt_alert(task, "Python failed to initialize the \"write\" function"); goto fail; @@ -326,20 +322,19 @@ nxt_python_init(nxt_task_t *task, nxt_common_app_conf_t *conf) nxt_py_write_obj = obj; obj = nxt_python_create_environ(task); - - if (obj == NULL) { + if (nxt_slow_path(obj == NULL)) { goto fail; } nxt_py_environ_ptyp = obj; obj = Py_BuildValue("[s]", "unit"); - if (obj == NULL) { + if (nxt_slow_path(obj == NULL)) { nxt_alert(task, "Python failed to create the \"sys.argv\" list"); goto fail; } - if (PySys_SetObject((char *) "argv", obj) != 0) { + if (nxt_slow_path(PySys_SetObject((char *) "argv", obj) != 0)) { nxt_alert(task, "Python failed to set the \"sys.argv\" list"); goto fail; } @@ -351,7 +346,6 @@ nxt_python_init(nxt_task_t *task, nxt_common_app_conf_t *conf) nxt_py_module[c->module.length] = '\0'; module = PyImport_ImportModule(nxt_py_module); - if (nxt_slow_path(module == NULL)) { nxt_alert(task, "Python failed to import module \"%s\"", nxt_py_module); PyErr_Print(); @@ -359,7 +353,6 @@ nxt_python_init(nxt_task_t *task, nxt_common_app_conf_t *conf) } obj = PyDict_GetItemString(PyModule_GetDict(module), "application"); - if (nxt_slow_path(obj == NULL)) { nxt_alert(task, "Python failed to get \"application\" " "from module \"%s\"", nxt_py_module); -- cgit From 27394118b32ab395be50de15ecf514d5529090c5 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Thu, 20 Dec 2018 15:47:10 +0300 Subject: Python: fixed error reporting on initialization of applications. PyErr_Print() writes traceback to "sys.stderr", which is a file object that can buffer the output. If the process exits immediately, the buffer can be destroyed before flushing to the log. As a result, the user doesn't see the traceback. Now Py_Finalize() is also called in case of any errors during initialization. It finalizes the interpreter and flushes all data. --- src/nxt_python_wsgi.c | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/nxt_python_wsgi.c b/src/nxt_python_wsgi.c index b6566788..bd3a2cb2 100644 --- a/src/nxt_python_wsgi.c +++ b/src/nxt_python_wsgi.c @@ -339,7 +339,7 @@ nxt_python_init(nxt_task_t *task, nxt_common_app_conf_t *conf) goto fail; } - Py_DECREF(obj); + Py_CLEAR(obj); nxt_py_module = nxt_alloca(c->module.length + 1); nxt_memcpy(nxt_py_module, c->module.start, c->module.length); @@ -349,7 +349,7 @@ nxt_python_init(nxt_task_t *task, nxt_common_app_conf_t *conf) if (nxt_slow_path(module == NULL)) { nxt_alert(task, "Python failed to import module \"%s\"", nxt_py_module); PyErr_Print(); - return NXT_ERROR; + goto fail; } obj = PyDict_GetItemString(PyModule_GetDict(module), "application"); @@ -367,9 +367,10 @@ nxt_python_init(nxt_task_t *task, nxt_common_app_conf_t *conf) } Py_INCREF(obj); - Py_DECREF(module); + Py_CLEAR(module); nxt_py_application = obj; + obj = NULL; nxt_unit_default_init(task, &python_init); @@ -377,7 +378,7 @@ nxt_python_init(nxt_task_t *task, nxt_common_app_conf_t *conf) unit_ctx = nxt_unit_init(&python_init); if (nxt_slow_path(unit_ctx == NULL)) { - return NXT_ERROR; + goto fail; } rc = nxt_unit_run(unit_ctx); @@ -395,9 +396,7 @@ fail: Py_XDECREF(obj); Py_XDECREF(module); - if (nxt_py_home != NULL) { - nxt_free(nxt_py_home); - } + nxt_python_atexit(); return NXT_ERROR; } @@ -529,10 +528,10 @@ fail: static void nxt_python_atexit(void) { - Py_DECREF(nxt_py_application); - Py_DECREF(nxt_py_start_resp_obj); - Py_DECREF(nxt_py_write_obj); - Py_DECREF(nxt_py_environ_ptyp); + Py_XDECREF(nxt_py_application); + Py_XDECREF(nxt_py_start_resp_obj); + Py_XDECREF(nxt_py_write_obj); + Py_XDECREF(nxt_py_environ_ptyp); Py_Finalize(); -- cgit