From ea62327b008b39dc48a51aa80343b20a0a122cd6 Mon Sep 17 00:00:00 2001 From: Alexander Borisov Date: Wed, 3 Oct 2018 17:50:03 +0300 Subject: [PATCH] Added Node.js support. --- auto/modules/conf | 4 + auto/modules/nodejs | 161 +++++ auto/save | 2 + src/nodejs/unit-http/README.md | 2 + src/nodejs/unit-http/addon.cpp | 15 + src/nodejs/unit-http/binding.gyp | 12 + src/nodejs/unit-http/binding_pub.gyp | 7 + src/nodejs/unit-http/http.js | 23 + src/nodejs/unit-http/http_server.js | 331 ++++++++++ src/nodejs/unit-http/package.json | 29 + src/nodejs/unit-http/socket.js | 99 +++ src/nodejs/unit-http/unit.cpp | 905 +++++++++++++++++++++++++++ src/nodejs/unit-http/unit.h | 77 +++ 13 files changed, 1667 insertions(+) create mode 100644 auto/modules/nodejs create mode 100644 src/nodejs/unit-http/README.md create mode 100644 src/nodejs/unit-http/addon.cpp create mode 100644 src/nodejs/unit-http/binding.gyp create mode 100644 src/nodejs/unit-http/binding_pub.gyp create mode 100755 src/nodejs/unit-http/http.js create mode 100755 src/nodejs/unit-http/http_server.js create mode 100644 src/nodejs/unit-http/package.json create mode 100755 src/nodejs/unit-http/socket.js create mode 100644 src/nodejs/unit-http/unit.cpp create mode 100644 src/nodejs/unit-http/unit.h diff --git a/auto/modules/conf b/auto/modules/conf index 93d28601..409b4bea 100644 --- a/auto/modules/conf +++ b/auto/modules/conf @@ -25,6 +25,10 @@ case "$nxt_module" in . auto/modules/ruby ;; + nodejs) + . auto/modules/nodejs + ;; + *) echo echo $0: error: invalid module \"$nxt_module\". diff --git a/auto/modules/nodejs b/auto/modules/nodejs new file mode 100644 index 00000000..25e75f01 --- /dev/null +++ b/auto/modules/nodejs @@ -0,0 +1,161 @@ + +# Copyright (C) NGINX, Inc. + + +shift + +for nxt_option; do + + case "$nxt_option" in + -*=*) value=`echo "$nxt_option" | sed -e 's/[-_a-zA-Z0-9]*=//'` ;; + *) value="" ;; + esac + + case "$nxt_option" in + --node=*) NXT_NODE="$value" ;; + --npm=*) NXT_NPM="$value" ;; + --node-gyp=*) NXT_NODE_GYP="$value" ;; + + --help) + cat << END + + --node=NAME set node executable + --npm=NAME set npm executable + --node-gyp=NAME set node-gyp executable + +END + exit 0 + ;; + + *) + echo + echo $0: error: invalid Node option \"$nxt_option\" + echo + exit 1 + ;; + + esac + +done + + +if [ ! -f $NXT_AUTOCONF_DATA ]; then + echo + echo Please run common $0 before configuring module \"$nxt_module\". + echo + exit 1 +fi + +. $NXT_AUTOCONF_DATA + + +$echo "configuring nodejs module" +$echo "configuring nodejs module..." >> $NXT_AUTOCONF_ERR + +NXT_NODE=${NXT_NODE=node} +NXT_NPM=${NXT_NPM=npm} +NXT_NODE_GYP=${NXT_NODE_GYP=node-gyp} + +$echo -n "checking for node ..." +$echo "checking for node ..." >> $NXT_AUTOCONF_ERR + +if /bin/sh -c "${NXT_NODE} -v" >> $NXT_AUTOCONF_ERR 2>&1; then + $echo " found" + + NXT_NODE_VERSION="`${NXT_NODE} -v`" + $echo " + node version ${NXT_NODE_VERSION}" + +else + $echo " not found" + $echo + $echo $0: error: no Node found. + $echo + exit 1; +fi + +$echo -n "checking for npm ..." +$echo "checking for npm ..." >> $NXT_AUTOCONF_ERR + +if /bin/sh -c "${NXT_NPM} -v" >> $NXT_AUTOCONF_ERR 2>&1; then + $echo " found" + + NXT_NPM_VERSION="`${NXT_NPM} -v`" + $echo " + npm version ${NXT_NPM_VERSION}" + +else + $echo " not found" + $echo + $echo $0: error: no npm found. + $echo + exit 1; +fi + +$echo -n "checking for node-gyp ..." +$echo "checking for node-gyp ..." >> $NXT_AUTOCONF_ERR + +if /bin/sh -c "${NXT_NODE_GYP} -v" >> $NXT_AUTOCONF_ERR 2>&1; then + $echo " found" + + NXT_NODE_GYP_VERSION="`${NXT_NODE_GYP} -v`" + $echo " + node-gyp version ${NXT_NODE_GYP_VERSION}" + +else + $echo " not found" + $echo + $echo $0: error: no node-gyp found. + $echo + exit 1; +fi + +if grep ^$NXT_NODE: $NXT_MAKEFILE 2>&1 > /dev/null; then + $echo + $echo $0: error: duplicate \"$NXT_NODE\" package configured. + $echo + exit 1; +fi + +NXT_NODE_TMP=${NXT_BUILD_DIR}/src/${NXT_NODE}/unit-http +NXT_NODE_TARBALL=${PWD}/${NXT_BUILD_DIR}/${NXT_NODE}-unit-http.tar.gz +NXT_NODE_EXPORTS="export UNIT_SRC_PATH=${PWD}/src && \ + export UNIT_LIB_STATIC_PATH=${PWD}/${NXT_BUILD_DIR}/libunit.a" + +cat << END >> $NXT_MAKEFILE + +.PHONY: ${NXT_NODE} +.PHONY: ${NXT_NODE}-copy +.PHONY: ${NXT_NODE}-install +.PHONY: ${NXT_NODE}-uninstall + +all: + +${NXT_NODE}: ${NXT_NODE}-copy $NXT_BUILD_DIR/$NXT_LIB_UNIT_STATIC + ${NXT_NODE_EXPORTS} && \\ + cd ${NXT_NODE_TMP} && ${NXT_NODE_GYP} configure build clean + +${NXT_NODE}-copy: + mkdir -p ${NXT_BUILD_DIR}/src/ + cp -rp src/nodejs/ ${NXT_BUILD_DIR}/src/${NXT_NODE} + +${NXT_NODE_TARBALL}: ${NXT_NODE}-copy + tar -zcvf ${NXT_NODE_TARBALL} -C ${NXT_NODE_TMP} . + +${NXT_NODE}-install: ${NXT_NODE_TARBALL} \ + $NXT_BUILD_DIR/$NXT_LIB_UNIT_STATIC + ${NXT_NODE_EXPORTS} && \\ + ${NXT_NPM} install -g ${NXT_NODE_TARBALL} --unsafe-perm=true + +${NXT_NODE}-local-install: ${NXT_NODE_TARBALL} \ + $NXT_BUILD_DIR/$NXT_LIB_UNIT_STATIC + ${NXT_NODE_EXPORTS} && \\ + mkdir -p \$(DESTDIR) && \\ + cd \$(DESTDIR) && ${NXT_NPM} install ${NXT_NODE_TARBALL} + +${NXT_NODE}-build: ${NXT_NODE} + +${NXT_NODE}-publish: ${NXT_NODE} + cd ${NXT_NODE_TMP} && ${NXT_NPM} publish + +${NXT_NODE}-uninstall: + ${NXT_NPM} uninstall -g unit-http + +END diff --git a/auto/save b/auto/save index 3a04a698..350c9c1f 100644 --- a/auto/save +++ b/auto/save @@ -26,6 +26,8 @@ echo=$NXT_BUILD_DIR/echo NXT_LIB_AUX_CFLAGS= NXT_LIB_AUX_LIBS= +NXT_LIB_UNIT_STATIC='$NXT_LIB_UNIT_STATIC' + NXT_MODULES='$NXT_MODULES' END diff --git a/src/nodejs/unit-http/README.md b/src/nodejs/unit-http/README.md new file mode 100644 index 00000000..139597f9 --- /dev/null +++ b/src/nodejs/unit-http/README.md @@ -0,0 +1,2 @@ + + diff --git a/src/nodejs/unit-http/addon.cpp b/src/nodejs/unit-http/addon.cpp new file mode 100644 index 00000000..6ced9538 --- /dev/null +++ b/src/nodejs/unit-http/addon.cpp @@ -0,0 +1,15 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#include "unit.h" + + +napi_value +Init(napi_env env, napi_value exports) +{ + return Unit::init(env, exports); +} + +NAPI_MODULE(Unit, Init) diff --git a/src/nodejs/unit-http/binding.gyp b/src/nodejs/unit-http/binding.gyp new file mode 100644 index 00000000..171c2eb7 --- /dev/null +++ b/src/nodejs/unit-http/binding.gyp @@ -0,0 +1,12 @@ +{ + 'targets': [{ + 'target_name': "unit-http", + 'sources': ["unit.cpp", "addon.cpp"], + 'include_dirs': [ + " 999) { + throw new ERR_HTTP_INVALID_STATUS_CODE(originalStatusCode); + } + + if (typeof reason === 'string') { + this.statusMessage = reason; + + } else { + if (!this.statusMessage) { + this.statusMessage = http.STATUS_CODES[statusCode] || 'unknown'; + } + + obj = reason; + } + + this.statusCode = statusCode; + + if (obj) { + var k; + var keys = Object.keys(obj); + + for (var i = 0; i < keys.length; i++) { + k = keys[i]; + + if (k) { + this.setHeader(k, obj[k]); + } + } + } + + 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); + } + + if (this.finished) { + return this; + } + + if (typeof chunk === 'function') { + callback = chunk; + chunk = null; + + } else if (typeof encoding === 'function') { + callback = encoding; + encoding = null; + } + + if (chunk) { + if (typeof chunk !== 'string' && !(chunk instanceof Buffer)) { + throw new TypeError('First argument must be a string or Buffer'); + } + + if (typeof chunk === 'string') { + contentLength = Buffer.byteLength(chunk, encoding); + + } else { + contentLength = chunk.length; + } + + unit_lib.unit_response_write(this, chunk, contentLength); + } + + if (typeof callback === 'function') { + callback(this); + } +}; + +ServerResponse.prototype.write = function write(chunk, encoding, callback) { + this._writeBody(chunk, encoding, callback); + + return this; +}; + +ServerResponse.prototype.end = function end(chunk, encoding, callback) { + this._writeBody(chunk, encoding, callback); + unit_lib.unit_response_end(this) + + this.finished = true; + + return this; +}; + +function ServerRequest(server) { + EventEmitter.call(this); + + this.server = server; +} +util.inherits(ServerRequest, EventEmitter); + +ServerRequest.prototype.unpipe = undefined; + +ServerRequest.prototype.setTimeout = function setTimeout(msecs, callback) { + this.timeout = msecs; + + if (callback) { + this.on('timeout', callback); + } + + return this; +}; + +ServerRequest.prototype.statusCode = function statusCode() { + /* Only valid for response obtained from http.ClientRequest. */ +}; + +ServerRequest.prototype.statusMessage = function statusMessage() { + /* Only valid for response obtained from http.ClientRequest. */ +}; + +ServerRequest.prototype.trailers = function trailers() { + throw new Error("Not supported"); +}; + +ServerRequest.prototype.METHODS = function METHODS() { + return http.METHODS; +}; + +ServerRequest.prototype.STATUS_CODES = function STATUS_CODES() { + return http.STATUS_CODES; +}; + +ServerRequest.prototype.listeners = function listeners() { + return []; +}; + +ServerRequest.prototype.resume = function resume() { + return []; +}; + +function Server(requestListener) { + EventEmitter.call(this); + + this.unit = new unit_lib.Unit(); + this.unit.createServer(); + + this.unit.server = this; + + this.socket = Socket; + this.request = ServerRequest; + this.response = ServerResponse; + + if (requestListener) { + this.on('request', requestListener); + } +} +util.inherits(Server, EventEmitter); + +Server.prototype.setTimeout = function setTimeout(msecs, callback) { + this.timeout = msecs; + + if (callback) { + this.on('timeout', callback); + } + + return this; +}; + +Server.prototype.listen = function () { + this.unit.listen(); +}; + +function connectionListener(socket) { +} + + +module.exports = { + STATUS_CODES: http.STATUS_CODES, + Server, + ServerResponse, + ServerRequest, + _connectionListener: connectionListener +}; diff --git a/src/nodejs/unit-http/package.json b/src/nodejs/unit-http/package.json new file mode 100644 index 00000000..bc481bc9 --- /dev/null +++ b/src/nodejs/unit-http/package.json @@ -0,0 +1,29 @@ +{ + "name": "unit-http", + "version": "1.0.0", + "description": "HTTP module for NGINX Unit", + "main": "http.js", + "files": [ + "addon.cpp", + "binding.gyp", + "http_server.js", + "http.js", + "package.json", + "socket.js", + "unit.cpp", + "unit.h", + "README.md" + ], + "scripts": { + "clean": "node-gyp clean", + "configure": "node-gyp configure", + "build": "node-gyp build", + "install": "node-gyp configure build" + }, + "author": "Alexander Borisov", + "license": "Apache 2.0", + "gypfile": true, + "dependencies": { + "node-addon-api": "1.2.0" + } +} diff --git a/src/nodejs/unit-http/socket.js b/src/nodejs/unit-http/socket.js new file mode 100755 index 00000000..89702834 --- /dev/null +++ b/src/nodejs/unit-http/socket.js @@ -0,0 +1,99 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +'use strict'; + +const EventEmitter = require('events'); +const util = require('util'); +const unit_lib = require('unit-http/build/Release/unit-http.node'); + +function Socket(options) { + EventEmitter.call(this); + + if (typeof options === 'number') { + options = { fd: options }; + + } else if (options === undefined) { + options = {}; + } + + this.readable = options.readable !== false; + this.writable = options.writable !== false; +} +util.inherits(Socket, EventEmitter); + +Socket.prototype.bufferSize = 0; +Socket.prototype.bytesRead = 0; +Socket.prototype.bytesWritten = 0; +Socket.prototype.connecting = false; +Socket.prototype.destroyed = false; +Socket.prototype.localAddress = ""; +Socket.prototype.localPort = 0; +Socket.prototype.remoteAddress = ""; +Socket.prototype.remoteFamily = ""; +Socket.prototype.remotePort = 0; + +Socket.prototype.address = function address() { +}; + +Socket.prototype.connect = function connect(options, callback) { + if (callback !== null) { + this.once('connect', cb); + } + + this.connecting = true; + this.writable = true; +}; + +Socket.prototype.address = function address() { +}; + +Socket.prototype.destroy = function destroy(exception) { + this.connecting = false; + this.readable = false; + this.writable = false; +}; + +Socket.prototype.end = function end(data, encoding) { +}; + +Socket.prototype.pause = function pause() { +}; + +Socket.prototype.ref = function ref() { +}; + +Socket.prototype.resume = function resume() { +}; + +Socket.prototype.setEncoding = function setEncoding(encoding) { +}; + +Socket.prototype.setKeepAlive = function setKeepAlive(enable, initialDelay) { +}; + +Socket.prototype.setNoDelay = function setNoDelay(noDelay) { +}; + +Socket.prototype.setTimeout = function setTimeout(msecs, callback) { + this.timeout = msecs; + + if (callback) { + this.on('timeout', callback); + } + + return this; +}; + +Socket.prototype.unref = function unref() { +}; + +Socket.prototype.write = function write(data, encoding, callback) { +}; + + +module.exports = { + Socket +}; diff --git a/src/nodejs/unit-http/unit.cpp b/src/nodejs/unit-http/unit.cpp new file mode 100644 index 00000000..40f641a6 --- /dev/null +++ b/src/nodejs/unit-http/unit.cpp @@ -0,0 +1,905 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#include "unit.h" + + +napi_ref Unit::constructor_; + + +Unit::Unit(napi_env env): + env_(env), + wrapper_(nullptr), + unit_ctx_(nullptr) +{ +} + + +Unit::~Unit() +{ + napi_delete_reference(env_, wrapper_); +} + + +napi_value +Unit::init(napi_env env, napi_value exports) +{ + napi_value cons, fn; + napi_status status; + + napi_property_descriptor properties[] = { + { "createServer", 0, create_server, 0, 0, 0, napi_default, 0 }, + { "listen", 0, listen, 0, 0, 0, napi_default, 0 } + }; + + status = napi_define_class(env, "Unit", NAPI_AUTO_LENGTH, create, nullptr, + 2, properties, &cons); + if (status != napi_ok) { + goto failed; + } + + status = napi_create_reference(env, cons, 1, &constructor_); + if (status != napi_ok) { + goto failed; + } + + status = napi_set_named_property(env, exports, "Unit", cons); + if (status != napi_ok) { + goto failed; + } + + status = napi_create_function(env, NULL, 0, response_send_headers, NULL, + &fn); + if (status != napi_ok) { + goto failed; + } + + status = napi_set_named_property(env, exports, + "unit_response_headers", fn); + if (status != napi_ok) { + goto failed; + } + + status = napi_create_function(env, NULL, 0, response_write, NULL, &fn); + if (status != napi_ok) { + goto failed; + } + + status = napi_set_named_property(env, exports, "unit_response_write", fn); + if (status != napi_ok) { + goto failed; + } + + status = napi_create_function(env, NULL, 0, response_end, NULL, &fn); + if (status != napi_ok) { + goto failed; + } + + status = napi_set_named_property(env, exports, "unit_response_end", fn); + if (status != napi_ok) { + goto failed; + } + + return exports; + +failed: + + napi_throw_error(env, NULL, "Failed to define Unit class"); + + return nullptr; +} + + +void +Unit::destroy(napi_env env, void *nativeObject, void *finalize_hint) +{ + Unit *obj = reinterpret_cast(nativeObject); + + delete obj; +} + + +napi_value +Unit::create(napi_env env, napi_callback_info info) +{ + Unit *obj; + napi_value target, cons, instance, jsthis; + napi_status status; + + status = napi_get_new_target(env, info, &target); + if (status != napi_ok) { + goto failed; + } + + if (target != nullptr) { + /* Invoked as constructor: `new Unit(...)` */ + status = napi_get_cb_info(env, info, nullptr, nullptr, &jsthis, + nullptr); + if (status != napi_ok) { + goto failed; + } + + obj = new Unit(env); + + status = napi_wrap(env, jsthis, reinterpret_cast(obj), + destroy, nullptr, &obj->wrapper_); + if (status != napi_ok) { + goto failed; + } + + return jsthis; + } + + /* Invoked as plain function `Unit(...)`, turn into construct call. */ + status = napi_get_reference_value(env, constructor_, &cons); + if (status != napi_ok) { + goto failed; + } + + status = napi_new_instance(env, cons, 0, nullptr, &instance); + if (status != napi_ok) { + goto failed; + } + + return instance; + +failed: + + napi_throw_error(env, NULL, "Failed to create Unit object"); + + return nullptr; +} + + +napi_value +Unit::create_server(napi_env env, napi_callback_info info) +{ + Unit *obj; + size_t argc; + napi_value jsthis; + napi_status status; + napi_value argv[1]; + nxt_unit_init_t unit_init; + + argc = 1; + + status = napi_get_cb_info(env, info, &argc, argv, &jsthis, nullptr); + if (status != napi_ok) { + goto failed; + } + + status = napi_unwrap(env, jsthis, reinterpret_cast(&obj)); + if (status != napi_ok) { + goto failed; + } + + memset(&unit_init, 0, sizeof(nxt_unit_init_t)); + + unit_init.data = obj; + unit_init.callbacks.request_handler = request_handler; + + obj->unit_ctx_ = nxt_unit_init(&unit_init); + if (obj->unit_ctx_ == NULL) { + goto failed; + } + + return nullptr; + +failed: + + napi_throw_error(env, NULL, "Failed to create Unit object"); + + return nullptr; +} + + +napi_value +Unit::listen(napi_env env, napi_callback_info info) +{ + int ret; + Unit *obj; + napi_value jsthis; + napi_status status; + + status = napi_get_cb_info(env, info, nullptr, nullptr, &jsthis, nullptr); + if (status != napi_ok) { + goto failed; + } + + status = napi_unwrap(env, jsthis, reinterpret_cast(&obj)); + if (status != napi_ok) { + goto failed; + } + + if (obj->unit_ctx_ == NULL) { + napi_throw_error(env, NULL, "Unit context was not created"); + return nullptr; + } + + ret = nxt_unit_run(obj->unit_ctx_); + if (ret != NXT_UNIT_OK) { + napi_throw_error(env, NULL, "Failed to run Unit"); + return nullptr; + } + + nxt_unit_done(obj->unit_ctx_); + + return nullptr; + +failed: + + napi_throw_error(env, NULL, "Failed to listen Unit socket"); + + return nullptr; +} + + +void +Unit::request_handler(nxt_unit_request_info_t *req) +{ + Unit *obj; + napi_value socket, request, response; + napi_value global, server_obj; + napi_value req_argv[3]; + napi_status status; + + obj = reinterpret_cast(req->unit->data); + + napi_handle_scope scope; + status = napi_open_handle_scope(obj->env_, &scope); + if (status != napi_ok) { + napi_throw_error(obj->env_, NULL, "Failed to create handle scope"); + return; + } + + server_obj = obj->get_server_object(); + if (server_obj == nullptr) { + napi_throw_error(obj->env_, NULL, "Failed to get server object"); + return; + } + + status = napi_get_global(obj->env_, &global); + if (status != napi_ok) { + napi_throw_error(obj->env_, NULL, "Failed to get global variable"); + return; + } + + socket = obj->create_socket(server_obj, req); + if (socket == nullptr) { + napi_throw_error(obj->env_, NULL, "Failed to create socket object"); + return; + } + + request = obj->create_request(server_obj, socket); + if (request == nullptr) { + napi_throw_error(obj->env_, NULL, "Failed to create request object"); + return; + } + + response = obj->create_response(server_obj, socket, request, req, obj); + if (response == nullptr) { + napi_throw_error(obj->env_, NULL, "Failed to create response object"); + return; + } + + req_argv[1] = request; + req_argv[2] = response; + + status = obj->create_headers(req, request); + if (status != napi_ok) { + napi_throw_error(obj->env_, NULL, "Failed to create headers"); + return; + } + + obj->emit(server_obj, "request", sizeof("request") - 1, 3, req_argv); + obj->emit_post_data(request, req); + + napi_close_handle_scope(obj->env_, scope); +} + + +napi_value +Unit::get_server_object() +{ + napi_value unit_obj, server_obj; + napi_status status; + + status = napi_get_reference_value(env_, wrapper_, &unit_obj); + if (status != napi_ok) { + return nullptr; + } + + status = napi_get_named_property(env_, unit_obj, "server", &server_obj); + if (status != napi_ok) { + return nullptr; + } + + return server_obj; +} + + +napi_value +Unit::emit(napi_value obj, const char *name, size_t name_len, size_t argc, + napi_value *argv) +{ + napi_value emitter, return_val, str; + napi_status status; + + status = napi_get_named_property(env_, obj, "emit", &emitter); + if (status != napi_ok) { + return nullptr; + } + + status = napi_create_string_latin1(env_, name, name_len, &str); + if (status != napi_ok) { + return nullptr; + } + + if (argc != 0) { + argv[0] = str; + + } else { + argc = 1; + argv = &str; + } + + status = napi_call_function(env_, obj, emitter, argc, argv, &return_val); + if (status != napi_ok) { + return nullptr; + } + + return return_val; +} + + +napi_status +Unit::create_headers(nxt_unit_request_info_t *req, napi_value request) +{ + uint32_t i; + const char *p; + napi_value headers, raw_headers, str; + napi_status status; + nxt_unit_field_t *f; + nxt_unit_request_t *r; + + r = req->request; + + status = napi_create_object(env_, &headers); + if (status != napi_ok) { + return status; + } + + status = napi_create_array_with_length(env_, r->fields_count * 2, + &raw_headers); + if (status != napi_ok) { + return status; + } + + for (i = 0; i < r->fields_count; i++) { + f = r->fields + i; + + status = this->append_header(f, headers, raw_headers, i); + if (status != napi_ok) { + return status; + } + } + + status = napi_set_named_property(env_, request, "headers", headers); + if (status != napi_ok) { + return status; + } + + status = napi_set_named_property(env_, request, "raw_headers", raw_headers); + if (status != napi_ok) { + return status; + } + + p = (const char *) nxt_unit_sptr_get(&r->version); + + status = napi_create_string_latin1(env_, p, r->version_length, &str); + if (status != napi_ok) { + return status; + } + + status = napi_set_named_property(env_, request, "httpVersion", str); + if (status != napi_ok) { + return status; + } + + p = (const char *) nxt_unit_sptr_get(&r->method); + + status = napi_create_string_latin1(env_, p, r->method_length, &str); + if (status != napi_ok) { + return status; + } + + status = napi_set_named_property(env_, request, "method", str); + if (status != napi_ok) { + return status; + } + + p = (const char *) nxt_unit_sptr_get(&r->target); + + status = napi_create_string_latin1(env_, p, r->target_length, &str); + if (status != napi_ok) { + return status; + } + + status = napi_set_named_property(env_, request, "url", str); + if (status != napi_ok) { + return status; + } + + return napi_ok; +} + + +inline napi_status +Unit::append_header(nxt_unit_field_t *f, napi_value headers, + napi_value raw_headers, uint32_t idx) +{ + const char *name, *value; + napi_value str, vstr; + napi_status status; + + value = (const char *) nxt_unit_sptr_get(&f->value); + + status = napi_create_string_latin1(env_, value, f->value_length, &vstr); + if (status != napi_ok) { + return status; + } + + name = (const char *) nxt_unit_sptr_get(&f->name); + + status = napi_set_named_property(env_, headers, name, vstr); + if (status != napi_ok) { + return status; + } + + status = napi_create_string_latin1(env_, name, f->name_length, &str); + if (status != napi_ok) { + return status; + } + + status = napi_set_element(env_, raw_headers, idx * 2, str); + if (status != napi_ok) { + return status; + } + + status = napi_set_element(env_, raw_headers, idx * 2 + 1, vstr); + if (status != napi_ok) { + return status; + } + + return napi_ok; +} + + +napi_value +Unit::create_socket(napi_value server_obj, nxt_unit_request_info_t *req) +{ + napi_value constructor, return_val; + napi_status status; + + status = napi_get_named_property(env_, server_obj, "socket", + &constructor); + if (status != napi_ok) { + return nullptr; + } + + status = napi_new_instance(env_, constructor, 0, NULL, &return_val); + if (status != napi_ok) { + return nullptr; + } + + return return_val; +} + + +napi_value +Unit::create_request(napi_value server_obj, napi_value socket) +{ + napi_value constructor, return_val; + napi_status status; + + status = napi_get_named_property(env_, server_obj, "request", + &constructor); + if (status != napi_ok) { + return nullptr; + } + + status = napi_new_instance(env_, constructor, 1, &server_obj, + &return_val); + if (status != napi_ok) { + return nullptr; + } + + status = napi_set_named_property(env_, return_val, "socket", socket); + if (status != napi_ok) { + return nullptr; + } + + return return_val; +} + + +napi_value +Unit::create_response(napi_value server_obj, napi_value socket, + napi_value request, nxt_unit_request_info_t *req, + Unit *obj) +{ + napi_value constructor, return_val, req_num; + napi_status status; + + status = napi_get_named_property(env_, server_obj, "response", + &constructor); + if (status != napi_ok) { + return nullptr; + } + + status = napi_new_instance(env_, constructor, 1, &request, &return_val); + if (status != napi_ok) { + return nullptr; + } + + status = napi_set_named_property(env_, return_val, "socket", socket); + if (status != napi_ok) { + return nullptr; + } + + status = napi_create_int64(env_, (int64_t) (uintptr_t) req, &req_num); + if (status != napi_ok) { + return nullptr; + } + + status = napi_set_named_property(env_, return_val, "_req_point", req_num); + if (status != napi_ok) { + return nullptr; + } + + return return_val; +} + + +void +Unit::emit_post_data(napi_value request, nxt_unit_request_info_t *req) +{ + void *data; + napi_value req_argv[2]; + napi_status status; + + status = napi_create_buffer(env_, (size_t) req->content_length, + &data, &req_argv[1]); + if (status != napi_ok) { + napi_throw_error(env_, NULL, "Failed to create request buffer"); + return; + } + + nxt_unit_request_read(req, data, req->content_length); + + emit(request, "data", sizeof("data") - 1, 2, req_argv); + emit(request, "end", sizeof("end") - 1, 0, nullptr); +} + + +napi_value +Unit::response_send_headers(napi_env env, napi_callback_info info) +{ + int ret; + char *ptr, *name_ptr; + bool is_array; + size_t argc, name_len, value_len; + int64_t req_p; + uint32_t status_code, header_len, keys_len, array_len; + uint32_t keys_count, i, j; + uint16_t hash; + napi_value this_arg, headers, keys, name, value, array_val; + napi_value req_num; + napi_status status; + nxt_unit_field_t *f; + nxt_unit_request_info_t *req; + napi_value argv[5]; + + argc = 5; + + status = napi_get_cb_info(env, info, &argc, argv, &this_arg, NULL); + if (status != napi_ok) { + return nullptr; + } + + if (argc != 5) { + napi_throw_error(env, NULL, "Wrong args count. Need three: " + "statusCode, headers, headers count, headers length"); + return nullptr; + } + + status = napi_get_named_property(env, argv[0], "_req_point", &req_num); + if (status != napi_ok) { + napi_throw_error(env, NULL, "Failed to get request pointer"); + return nullptr; + } + + status = napi_get_value_int64(env, req_num, &req_p); + if (status != napi_ok) { + napi_throw_error(env, NULL, "Failed to get request pointer"); + return nullptr; + } + + req = (nxt_unit_request_info_t *) (uintptr_t) req_p; + + status = napi_get_value_uint32(env, argv[1], &status_code); + if (status != napi_ok) { + goto failed; + } + + status = napi_get_value_uint32(env, argv[3], &keys_count); + if (status != napi_ok) { + goto failed; + } + + status = napi_get_value_uint32(env, argv[4], &header_len); + if (status != napi_ok) { + goto failed; + } + + /* Need to reserve extra byte for C-string 0-termination. */ + header_len++; + + headers = argv[2]; + + ret = nxt_unit_response_init(req, status_code, keys_count, header_len); + if (ret != NXT_UNIT_OK) { + goto failed; + } + + status = napi_get_property_names(env, headers, &keys); + if (status != napi_ok) { + goto failed; + } + + status = napi_get_array_length(env, keys, &keys_len); + if (status != napi_ok) { + goto failed; + } + + ptr = req->response_buf->free; + + for (i = 0; i < keys_len; i++) { + status = napi_get_element(env, keys, i, &name); + if (status != napi_ok) { + goto failed; + } + + status = napi_get_property(env, headers, name, &value); + if (status != napi_ok) { + goto failed; + } + + status = napi_get_value_string_latin1(env, name, ptr, header_len, + &name_len); + if (status != napi_ok) { + goto failed; + } + + name_ptr = ptr; + + ptr += name_len; + header_len -= name_len; + + hash = nxt_unit_field_hash(name_ptr, name_len); + + status = napi_is_array(env, value, &is_array); + if (status != napi_ok) { + goto failed; + } + + if (is_array) { + status = napi_get_array_length(env, value, &array_len); + if (status != napi_ok) { + goto failed; + } + + for (j = 0; j < array_len; j++) { + status = napi_get_element(env, value, j, &array_val); + if (status != napi_ok) { + goto failed; + } + + status = napi_get_value_string_latin1(env, array_val, ptr, + header_len, + &value_len); + if (status != napi_ok) { + goto failed; + } + + f = req->response->fields + req->response->fields_count; + f->skip = 0; + + nxt_unit_sptr_set(&f->name, name_ptr); + + f->name_length = name_len; + f->hash = hash; + + nxt_unit_sptr_set(&f->value, ptr); + f->value_length = (uint32_t) value_len; + + ptr += value_len; + header_len -= value_len; + + req->response->fields_count++; + } + + } else { + status = napi_get_value_string_latin1(env, value, ptr, header_len, + &value_len); + if (status != napi_ok) { + goto failed; + } + + f = req->response->fields + req->response->fields_count; + f->skip = 0; + + nxt_unit_sptr_set(&f->name, name_ptr); + + f->name_length = name_len; + f->hash = hash; + + nxt_unit_sptr_set(&f->value, ptr); + f->value_length = (uint32_t) value_len; + + ptr += value_len; + header_len -= value_len; + + req->response->fields_count++; + } + } + + req->response_buf->free = ptr; + + ret = nxt_unit_response_send(req); + if (ret != NXT_UNIT_OK) { + goto failed; + } + + return this_arg; + +failed: + + req->response->fields_count = 0; + + napi_throw_error(env, NULL, "Failed to write headers"); + + return nullptr; +} + + +napi_value +Unit::response_write(napi_env env, napi_callback_info info) +{ + int ret; + char *ptr; + size_t argc, have_buf_len; + int64_t req_p; + uint32_t buf_len; + napi_value this_arg, req_num; + napi_status status; + nxt_unit_buf_t *buf; + napi_valuetype buf_type; + nxt_unit_request_info_t *req; + napi_value argv[3]; + + argc = 3; + + status = napi_get_cb_info(env, info, &argc, argv, &this_arg, NULL); + if (status != napi_ok) { + goto failed; + } + + if (argc != 3) { + napi_throw_error(env, NULL, "Wrong args count. Need two: " + "chunk, chunk length"); + return nullptr; + } + + status = napi_get_named_property(env, argv[0], "_req_point", &req_num); + if (status != napi_ok) { + napi_throw_error(env, NULL, "Failed to get request pointer"); + return nullptr; + } + + status = napi_get_value_int64(env, req_num, &req_p); + if (status != napi_ok) { + napi_throw_error(env, NULL, "Failed to get request pointer"); + return nullptr; + } + + req = (nxt_unit_request_info_t *) (uintptr_t) req_p; + + status = napi_get_value_uint32(env, argv[2], &buf_len); + if (status != napi_ok) { + goto failed; + } + + status = napi_typeof(env, argv[1], &buf_type); + if (status != napi_ok) { + goto failed; + } + + buf_len++; + + 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 */ + + status = napi_get_value_string_utf8(env, argv[1], buf->free, + buf_len, &have_buf_len); + + } else { + status = napi_get_buffer_info(env, argv[1], (void **) &ptr, + &have_buf_len); + + memcpy(buf->free, ptr, have_buf_len); + } + + if (status != napi_ok) { + goto failed; + } + + buf->free += have_buf_len; + + ret = nxt_unit_buf_send(buf); + if (ret != NXT_UNIT_OK) { + goto failed; + } + + return this_arg; + +failed: + + napi_throw_error(env, NULL, "Failed to write body"); + + return nullptr; +} + + +napi_value +Unit::response_end(napi_env env, napi_callback_info info) +{ + size_t argc; + int64_t req_p; + napi_value resp, this_arg, req_num; + napi_status status; + nxt_unit_request_info_t *req; + + argc = 1; + + status = napi_get_cb_info(env, info, &argc, &resp, &this_arg, NULL); + if (status != napi_ok) { + napi_throw_error(env, NULL, "Failed to finalize sending body"); + return nullptr; + } + + status = napi_get_named_property(env, resp, "_req_point", &req_num); + if (status != napi_ok) { + napi_throw_error(env, NULL, "Failed to get request pointer"); + return nullptr; + } + + status = napi_get_value_int64(env, req_num, &req_p); + if (status != napi_ok) { + napi_throw_error(env, NULL, "Failed to get request pointer"); + return nullptr; + } + + req = (nxt_unit_request_info_t *) (uintptr_t) req_p; + + nxt_unit_request_done(req, NXT_UNIT_OK); + + return this_arg; +} diff --git a/src/nodejs/unit-http/unit.h b/src/nodejs/unit-http/unit.h new file mode 100644 index 00000000..753a14d8 --- /dev/null +++ b/src/nodejs/unit-http/unit.h @@ -0,0 +1,77 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NXT_NODEJS_UNIT_H_INCLUDED_ +#define _NXT_NODEJS_UNIT_H_INCLUDED_ + + +#include + + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#ifdef __cplusplus +} /* extern "C" */ +#endif + + +class Unit { +public: + static napi_value init(napi_env env, napi_value exports); + +private: + Unit(napi_env env); + ~Unit(); + + static napi_value create(napi_env env, napi_callback_info info); + static void 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 void request_handler(nxt_unit_request_info_t *req); + + napi_value get_server_object(); + + napi_value emit(napi_value obj, const char *name, size_t name_len, + size_t argc, napi_value *argv); + + napi_value create_socket(napi_value server_obj, + nxt_unit_request_info_t *req); + + 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, + nxt_unit_request_info_t *req, Unit *obj); + + void emit_post_data(napi_value request, nxt_unit_request_info_t *req); + + 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); + + napi_status create_headers(nxt_unit_request_info_t *req, + napi_value request); + + inline napi_status append_header(nxt_unit_field_t *f, napi_value headers, + napi_value raw_headers, uint32_t idx); + + static napi_ref constructor_; + + napi_env env_; + napi_ref wrapper_; + nxt_unit_ctx_t *unit_ctx_; +}; + + +#endif /* _NXT_NODEJS_H_INCLUDED_ */