summaryrefslogtreecommitdiffhomepage
path: root/src/nodejs/unit-http/unit.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/nodejs/unit-http/unit.cpp905
1 files changed, 905 insertions, 0 deletions
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<Unit *>(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<void *>(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<void **>(&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<void **>(&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<Unit *>(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;
+}