diff options
Diffstat (limited to '')
-rw-r--r-- | src/nodejs/unit-http/unit.cpp | 905 |
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; +} |