From fac0ef29d564dbd8f59c6c8aaa89f0d656f1f786 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Mon, 24 Dec 2018 20:31:45 +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 71ee6599..dc821e07 100644 --- a/src/nxt_main.h +++ b/src/nxt_main.h @@ -11,8 +11,8 @@ #include -#define NXT_VERSION "1.7" -#define NXT_VERNUM 10700 +#define NXT_VERSION "1.8" +#define NXT_VERNUM 10800 #define NXT_SERVER "Unit/" NXT_VERSION -- cgit From 23a6a8e45177a88f36e6b860a2db4848a9e3eada Mon Sep 17 00:00:00 2001 From: Igor Sysoev Date: Fri, 18 Jan 2019 16:18:36 +0300 Subject: Testing correct value. --- src/nxt_http_error.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nxt_http_error.c b/src/nxt_http_error.c index 65f8ba38..99d27903 100644 --- a/src/nxt_http_error.c +++ b/src/nxt_http_error.c @@ -37,7 +37,7 @@ nxt_http_request_error(nxt_task_t *task, nxt_http_request_t *r, r->status = status; r->resp.fields = nxt_list_create(r->mem_pool, 8, sizeof(nxt_http_field_t)); - if (nxt_slow_path(r == NULL)) { + if (nxt_slow_path(r->resp.fields == NULL)) { goto fail; } -- cgit From fd6a6a5514c420dae2b393f62fee65f87758dec9 Mon Sep 17 00:00:00 2001 From: Igor Sysoev Date: Mon, 21 Jan 2019 18:39:19 +0300 Subject: Fixed processing Unix listening socket failures. This is related to issue #198 on GitHub. --- src/nxt_main_process.c | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) (limited to 'src') diff --git a/src/nxt_main_process.c b/src/nxt_main_process.c index 819ed44c..a4c3f119 100644 --- a/src/nxt_main_process.c +++ b/src/nxt_main_process.c @@ -1116,24 +1116,23 @@ nxt_main_listening_socket(nxt_sockaddr_t *sa, nxt_listening_socket_t *ls) break; } - goto next; - } - + } else #endif + { + switch (err) { - switch (err) { - - case EACCES: - ls->error = NXT_SOCKET_ERROR_PORT; - break; + case EACCES: + ls->error = NXT_SOCKET_ERROR_PORT; + break; - case EADDRINUSE: - ls->error = NXT_SOCKET_ERROR_INUSE; - break; + case EADDRINUSE: + ls->error = NXT_SOCKET_ERROR_INUSE; + break; - case EADDRNOTAVAIL: - ls->error = NXT_SOCKET_ERROR_NOADDR; - break; + case EADDRNOTAVAIL: + ls->error = NXT_SOCKET_ERROR_NOADDR; + break; + } } ls->end = nxt_sprintf(ls->start, ls->end, "bind(\\\"%*s\\\") failed %E", @@ -1143,8 +1142,6 @@ nxt_main_listening_socket(nxt_sockaddr_t *sa, nxt_listening_socket_t *ls) #if (NXT_HAVE_UNIX_DOMAIN) -next: - if (sa->u.sockaddr.sa_family == AF_UNIX) { char *filename; mode_t access; -- cgit From a2cbe890a19ca2341de5b54ffe9c9f5924cf10ec Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Thu, 7 Feb 2019 17:40:27 +0300 Subject: Rejecting requests with invalid "Content-Length". --- src/nxt_http_request.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nxt_http_request.c b/src/nxt_http_request.c index b80998cb..c8adb499 100644 --- a/src/nxt_http_request.c +++ b/src/nxt_http_request.c @@ -79,14 +79,20 @@ nxt_int_t nxt_http_request_content_length(void *ctx, nxt_http_field_t *field, uintptr_t data) { + nxt_off_t n; nxt_http_request_t *r; r = ctx; - r->content_length = field; - r->content_length_n = nxt_off_t_parse(field->value, field->value_length); - return NXT_OK; + n = nxt_off_t_parse(field->value, field->value_length); + + if (nxt_fast_path(n >= 0)) { + r->content_length_n = n; + return NXT_OK; + } + + return NXT_ERROR; } -- cgit From 2a6b54c23020a24eaadfb4006235ca17dfa877f8 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Mon, 18 Feb 2019 16:51:30 +0300 Subject: Rejecting requests with duplicate "Content-Length". --- src/nxt_http_request.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/nxt_http_request.c b/src/nxt_http_request.c index c8adb499..bbc138a7 100644 --- a/src/nxt_http_request.c +++ b/src/nxt_http_request.c @@ -83,13 +83,16 @@ nxt_http_request_content_length(void *ctx, nxt_http_field_t *field, nxt_http_request_t *r; r = ctx; - r->content_length = field; - n = nxt_off_t_parse(field->value, field->value_length); + if (nxt_fast_path(r->content_length == NULL)) { + r->content_length = field; - if (nxt_fast_path(n >= 0)) { - r->content_length_n = n; - return NXT_OK; + n = nxt_off_t_parse(field->value, field->value_length); + + if (nxt_fast_path(n >= 0)) { + r->content_length_n = n; + return NXT_OK; + } } return NXT_ERROR; -- cgit From bb11e9036f8c5bf6f36899bb462709bdbf55bfa4 Mon Sep 17 00:00:00 2001 From: Igor Sysoev Date: Mon, 18 Feb 2019 17:28:55 +0300 Subject: Fixed memory leak on response body sending failure. --- src/nxt_http.h | 1 - src/nxt_http_request.c | 4 +++- src/nxt_router.c | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nxt_http.h b/src/nxt_http.h index b2111f90..10c6a9f1 100644 --- a/src/nxt_http.h +++ b/src/nxt_http.h @@ -176,7 +176,6 @@ nxt_buf_t *nxt_http_buf_mem(nxt_task_t *task, nxt_http_request_t *r, size_t size); nxt_buf_t *nxt_http_buf_last(nxt_http_request_t *r); void nxt_http_request_error_handler(nxt_task_t *task, void *obj, void *data); -void nxt_http_request_close_handler(nxt_task_t *task, void *obj, void *data); nxt_int_t nxt_http_request_host(void *ctx, nxt_http_field_t *field, uintptr_t data); diff --git a/src/nxt_http_request.c b/src/nxt_http_request.c index bbc138a7..ed65a592 100644 --- a/src/nxt_http_request.c +++ b/src/nxt_http_request.c @@ -13,6 +13,8 @@ static void nxt_http_app_request(nxt_task_t *task, void *obj, void *data); static void nxt_http_request_mem_buf_completion(nxt_task_t *task, void *obj, void *data); static void nxt_http_request_done(nxt_task_t *task, void *obj, void *data); +static void nxt_http_request_close_handler(nxt_task_t *task, void *obj, + void *data); static u_char *nxt_http_date(u_char *buf, nxt_realtime_t *now, struct tm *tm, size_t size, const char *format); @@ -449,7 +451,7 @@ nxt_http_request_error_handler(nxt_task_t *task, void *obj, void *data) } -void +static void nxt_http_request_close_handler(nxt_task_t *task, void *obj, void *data) { nxt_http_proto_t proto; diff --git a/src/nxt_router.c b/src/nxt_router.c index 7ecbca81..e9233554 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -3500,7 +3500,7 @@ static const nxt_http_request_state_t nxt_http_request_send_state nxt_aligned(64) = { .ready_handler = nxt_http_request_send_body, - .error_handler = nxt_http_request_close_handler, + .error_handler = nxt_http_request_error_handler, }; -- cgit From acb5b0aad7f9aca966f4d40d05c104f4df68b036 Mon Sep 17 00:00:00 2001 From: Igor Sysoev Date: Mon, 18 Feb 2019 18:34:20 +0300 Subject: Ignoring HUP signal in main process. --- src/nxt_main_process.c | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'src') diff --git a/src/nxt_main_process.c b/src/nxt_main_process.c index a4c3f119..a33117e2 100644 --- a/src/nxt_main_process.c +++ b/src/nxt_main_process.c @@ -52,6 +52,8 @@ static void nxt_main_process_sigusr1_handler(nxt_task_t *task, void *obj, void *data); static void nxt_main_process_sigchld_handler(nxt_task_t *task, void *obj, void *data); +static void nxt_main_process_signal_handler(nxt_task_t *task, void *obj, + void *data); static void nxt_main_cleanup_worker_process(nxt_task_t *task, nxt_pid_t pid); static void nxt_main_stop_worker_processes(nxt_task_t *task, nxt_runtime_t *rt); static void nxt_main_port_socket_handler(nxt_task_t *task, @@ -68,6 +70,7 @@ static void nxt_main_port_access_log_handler(nxt_task_t *task, const nxt_sig_event_t nxt_main_process_signals[] = { + nxt_event_signal(SIGHUP, nxt_main_process_signal_handler), nxt_event_signal(SIGINT, nxt_main_process_sigterm_handler), nxt_event_signal(SIGQUIT, nxt_main_process_sigquit_handler), nxt_event_signal(SIGTERM, nxt_main_process_sigterm_handler), @@ -888,6 +891,14 @@ nxt_main_process_sigchld_handler(nxt_task_t *task, void *obj, void *data) } +static void +nxt_main_process_signal_handler(nxt_task_t *task, void *obj, void *data) +{ + nxt_trace(task, "signal signo:%d (%s) recevied, ignored", + (int) (uintptr_t) obj, data); +} + + static void nxt_main_cleanup_worker_process(nxt_task_t *task, nxt_pid_t pid) { -- cgit From 2d4697dbbec7dd51ed5aeebb10e543038e15a359 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Tue, 19 Feb 2019 20:25:25 +0300 Subject: Validation and normalization of request host. --- src/nxt_h1proto.c | 4 +- src/nxt_http.h | 2 +- src/nxt_http_request.c | 117 ++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 112 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/nxt_h1proto.c b/src/nxt_h1proto.c index 2194e56f..4e1b22e9 100644 --- a/src/nxt_h1proto.c +++ b/src/nxt_h1proto.c @@ -526,9 +526,7 @@ nxt_h1p_conn_request_header_parse(nxt_task_t *task, void *obj, void *data) return; } - /* ret == NXT_ERROR */ - status = NXT_HTTP_BAD_REQUEST; - + status = ret; goto error; case NXT_AGAIN: diff --git a/src/nxt_http.h b/src/nxt_http.h index 10c6a9f1..af0b2cd6 100644 --- a/src/nxt_http.h +++ b/src/nxt_http.h @@ -119,12 +119,12 @@ struct nxt_http_request_s { nxt_str_t *args; nxt_list_t *fields; - nxt_http_field_t *host; nxt_http_field_t *content_type; nxt_http_field_t *content_length; nxt_http_field_t *cookie; nxt_http_field_t *referer; nxt_http_field_t *user_agent; + nxt_str_t host; nxt_off_t content_length_n; nxt_sockaddr_t *remote; diff --git a/src/nxt_http_request.c b/src/nxt_http_request.c index ed65a592..6029c5c0 100644 --- a/src/nxt_http_request.c +++ b/src/nxt_http_request.c @@ -8,6 +8,7 @@ #include +static nxt_int_t nxt_http_validate_host(nxt_str_t *host, nxt_mp_t *mp); static void nxt_http_request_start(nxt_task_t *task, void *obj, void *data); static void nxt_http_app_request(nxt_task_t *task, void *obj, void *data); static void nxt_http_request_mem_buf_completion(nxt_task_t *task, void *obj, @@ -52,13 +53,118 @@ nxt_http_init(nxt_task_t *task, nxt_runtime_t *rt) nxt_int_t nxt_http_request_host(void *ctx, nxt_http_field_t *field, uintptr_t data) { + nxt_int_t ret; + nxt_str_t host; nxt_http_request_t *r; r = ctx; - /* TODO: validate host. */ + if (nxt_slow_path(r->host.start != NULL)) { + return NXT_HTTP_BAD_REQUEST; + } + + host.length = field->value_length; + host.start = field->value; + + ret = nxt_http_validate_host(&host, r->mem_pool); + + if (nxt_fast_path(ret == NXT_OK)) { + r->host = host; + } + + return ret; +} + + +static nxt_int_t +nxt_http_validate_host(nxt_str_t *host, nxt_mp_t *mp) +{ + u_char *h, ch; + size_t i, dot_pos, host_length; + nxt_bool_t lowcase; + + enum { + sw_usual, + sw_literal, + sw_rest + } state; + + dot_pos = host->length; + host_length = host->length; + + h = host->start; + + lowcase = 0; + state = sw_usual; + + for (i = 0; i < host->length; i++) { + ch = h[i]; + + if (ch > ']') { + /* Short path. */ + continue; + } + + switch (ch) { + + case '.': + if (dot_pos == i - 1) { + return NXT_HTTP_BAD_REQUEST; + } - r->host = field; + dot_pos = i; + break; + + case ':': + if (state == sw_usual) { + host_length = i; + state = sw_rest; + } + + break; + + case '[': + if (i == 0) { + state = sw_literal; + } + + break; + + case ']': + if (state == sw_literal) { + host_length = i + 1; + state = sw_rest; + } + + break; + + case '/': + case '\0': + return NXT_HTTP_BAD_REQUEST; + + default: + if (ch >= 'A' && ch <= 'Z') { + lowcase = 1; + } + + break; + } + } + + if (dot_pos == host_length - 1) { + host_length--; + } + + host->length = host_length; + + if (lowcase) { + host->start = nxt_mp_nget(mp, host_length); + if (nxt_slow_path(host->start == NULL)) { + return NXT_HTTP_INTERNAL_SERVER_ERROR; + } + + nxt_memcpy_lowcase(host->start, h, host_length); + } return NXT_OK; } @@ -97,7 +203,7 @@ nxt_http_request_content_length(void *ctx, nxt_http_field_t *field, } } - return NXT_ERROR; + return NXT_HTTP_BAD_REQUEST; } @@ -237,10 +343,7 @@ nxt_http_app_request(nxt_task_t *task, void *obj, void *data) ar->r.header.query = *r->args; } - if (r->host != NULL) { - ar->r.header.host.length = r->host->value_length; - ar->r.header.host.start = r->host->value; - } + ar->r.header.host = r->host; if (r->content_type != NULL) { ar->r.header.content_type.length = r->content_type->value_length; -- cgit From 3280b826e373bf88c16640cb434771df378bbb07 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 21 Feb 2019 16:30:59 +0300 Subject: Initializing incoming buffer queue in a proper place. In case nxt_unit_tracking_read() failed, execution would jump to the error path, where it could try to release buffers from uninitialized yet incoming_buf queue. --- src/nxt_unit.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nxt_unit.c b/src/nxt_unit.c index 24e51075..fa1fb1d5 100644 --- a/src/nxt_unit.c +++ b/src/nxt_unit.c @@ -541,6 +541,8 @@ nxt_unit_process_msg(nxt_unit_ctx_t *ctx, nxt_unit_port_id_t *port_id, memcpy(&fd, CMSG_DATA(cm), sizeof(int)); } + nxt_queue_init(&incoming_buf); + if (nxt_slow_path(buf_size < sizeof(nxt_port_msg_t))) { nxt_unit_warn(ctx, "message too small (%d bytes)", (int) buf_size); goto fail; @@ -570,8 +572,6 @@ nxt_unit_process_msg(nxt_unit_ctx_t *ctx, nxt_unit_port_id_t *port_id, } if (port_msg->mmap) { - nxt_queue_init(&incoming_buf); - if (nxt_unit_mmap_read(ctx, &recv_msg, &incoming_buf) != NXT_UNIT_OK) { goto fail; } -- cgit From c96b2baca50fe2f5c17f96406e6d741e88267921 Mon Sep 17 00:00:00 2001 From: Igor Sysoev Date: Fri, 22 Feb 2019 17:32:38 +0300 Subject: TLS certificates should be freed per listener. This fixes memory leak if configuration uses more than one TLS cerificate. --- src/nxt_router.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/nxt_router.c b/src/nxt_router.c index e9233554..8b93e15d 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -2910,6 +2910,7 @@ nxt_router_conf_release(nxt_task_t *task, nxt_socket_conf_joint_t *joint) rtcf, rtcf->count); if (--skcf->count != 0) { + skcf = NULL; rtcf = NULL; app = NULL; @@ -2923,6 +2924,14 @@ nxt_router_conf_release(nxt_task_t *task, nxt_socket_conf_joint_t *joint) nxt_thread_spin_unlock(lock); + if (skcf != NULL) { +#if (NXT_TLS) + if (skcf->tls != NULL) { + task->thread->runtime->tls->server_free(task, skcf->tls); + } +#endif + } + if (app != NULL) { nxt_router_app_use(task, app, -1); } @@ -2933,12 +2942,6 @@ nxt_router_conf_release(nxt_task_t *task, nxt_socket_conf_joint_t *joint) if (rtcf != NULL) { nxt_debug(task, "old router conf is destroyed"); -#if (NXT_TLS) - if (skcf->tls != NULL) { - task->thread->runtime->tls->server_free(task, skcf->tls); - } -#endif - nxt_router_access_log_release(task, lock, rtcf->access_log); nxt_mp_thread_adopt(rtcf->mem_pool); -- cgit From 608e09e9def182756e2f1650c6f3416b209fbf58 Mon Sep 17 00:00:00 2001 From: Alexander Borisov Date: Fri, 22 Feb 2019 16:31:44 +0300 Subject: Improvement and unification of version processing in build scripts. This also eliminates expressions that incompatible with BSD make, thus fixing installation of Node.js module on FreeBSD (broken by dace60fc4926). --- src/nodejs/unit-http/unit.h | 2 +- src/nxt_main.h | 5 +---- src/nxt_unit.h | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/nodejs/unit-http/unit.h b/src/nodejs/unit-http/unit.h index 8baeb967..db85e85c 100644 --- a/src/nodejs/unit-http/unit.h +++ b/src/nodejs/unit-http/unit.h @@ -15,7 +15,7 @@ extern "C" { #include "version.h" #include -#if NXT_UNIT_VERNUM != NXT_NODE_VERNUM +#if NXT_VERNUM != NXT_NODE_VERNUM #error "libunit version mismatch." #endif diff --git a/src/nxt_main.h b/src/nxt_main.h index dc821e07..23c55002 100644 --- a/src/nxt_main.h +++ b/src/nxt_main.h @@ -9,10 +9,7 @@ #include - - -#define NXT_VERSION "1.8" -#define NXT_VERNUM 10800 +#include #define NXT_SERVER "Unit/" NXT_VERSION diff --git a/src/nxt_unit.h b/src/nxt_unit.h index a3fcc541..532de20d 100644 --- a/src/nxt_unit.h +++ b/src/nxt_unit.h @@ -11,7 +11,7 @@ #include #include -#include "nxt_unit_version.h" +#include "nxt_version.h" #include "nxt_unit_typedefs.h" -- cgit From c5563d142dd189050aee08a00e3079e3e7ff14a7 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Sat, 23 Feb 2019 14:48:33 +0300 Subject: Removed surplus check for NUL in nxt_http_validate_host(). Such header fields are already rejected by HTTP parser. --- src/nxt_http_request.c | 1 - 1 file changed, 1 deletion(-) (limited to 'src') diff --git a/src/nxt_http_request.c b/src/nxt_http_request.c index 6029c5c0..b7d46c72 100644 --- a/src/nxt_http_request.c +++ b/src/nxt_http_request.c @@ -139,7 +139,6 @@ nxt_http_validate_host(nxt_str_t *host, nxt_mp_t *mp) break; case '/': - case '\0': return NXT_HTTP_BAD_REQUEST; default: -- cgit From d8c0a0dbe25c2467b6b8ba866cfd67613ff2014a Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Mon, 25 Feb 2019 15:10:44 +0300 Subject: Disabled useless code in nxt_router_listen_socket_error(). It doesn't do anything useful, among creating a JSON message and logging it to debug log. Besides that it causes segmentation fault if the RPC handler is triggered with an empty buffer due to exiting of the main process. --- src/nxt_router.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nxt_router.c b/src/nxt_router.c index 8b93e15d..5621abec 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -1982,13 +1982,18 @@ static void nxt_router_listen_socket_error(nxt_task_t *task, nxt_port_recv_msg_t *msg, void *data) { + nxt_socket_rpc_t *rpc; + nxt_router_temp_conf_t *tmcf; + + rpc = data; + tmcf = rpc->temp_conf; + +#if 0 u_char *p; size_t size; uint8_t error; nxt_buf_t *in, *out; nxt_sockaddr_t *sa; - nxt_socket_rpc_t *rpc; - nxt_router_temp_conf_t *tmcf; static nxt_str_t socket_errors[] = { nxt_string("ListenerSystem"), @@ -2000,9 +2005,7 @@ nxt_router_listen_socket_error(nxt_task_t *task, nxt_port_recv_msg_t *msg, nxt_string("ListenerPath"), }; - rpc = data; sa = rpc->socket_conf->listen->sockaddr; - tmcf = rpc->temp_conf; in = nxt_buf_chk_make_plain(tmcf->mem_pool, msg->buf, msg->size); @@ -2030,6 +2033,7 @@ nxt_router_listen_socket_error(nxt_task_t *task, nxt_port_recv_msg_t *msg, &socket_errors[error], in->mem.free - p, p); nxt_debug(task, "%*s", out->mem.free - out->mem.pos, out->mem.pos); +#endif nxt_router_conf_error(task, tmcf); } -- cgit From bd1eb7b502e3c9b3c3854fafd6ad360eca11c50a Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Mon, 25 Feb 2019 18:09:37 +0300 Subject: RPC error message size fixed. In case of RPC error, special error message passed to handler. Field 'size' expected to be 0 in this case because in contains fake empty buffer. --- src/nxt_port_rpc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nxt_port_rpc.c b/src/nxt_port_rpc.c index 401707f0..77e8af45 100644 --- a/src/nxt_port_rpc.c +++ b/src/nxt_port_rpc.c @@ -489,7 +489,7 @@ nxt_port_rpc_close(nxt_task_t *task, nxt_port_t *port) msg.port_msg.nf = 0; msg.port_msg.mf = 0; msg.port_msg.tracking = 0; - msg.size = sizeof(msg.port_msg); + msg.size = 0; msg.cancelled = 0; msg.u.data = NULL; -- cgit From ce6ce15c2042421588cd7f0635744239caf1cb31 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Tue, 26 Feb 2019 17:42:20 +0300 Subject: Fixed violation of the strict aliasing rules in 5d0edd35c4ce. In order to reduce number of operations over rb-tree and process them in batches simultaneously, all the timers changes are temporary stored in array. While processing of these changes, the same memory is also used for storing pointers to postpone timers adding. As the same block of memory has been referenced by two different types of pointers (nxt_timer_change_t * and nxt_timer_t **), some compilers may reorder operations with these pointers and produce broken code. See ticket #221 on GitHub for a particular case. Now the same "nxt_timer_change_t" structure is used in both cases. Also, reverted the -fno-strict-aliasing flag, which has been introduced in ef76227ec159 as a workaround for this issue. --- src/nxt_timer.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/nxt_timer.c b/src/nxt_timer.c index cba4755b..cb94b77c 100644 --- a/src/nxt_timer.c +++ b/src/nxt_timer.c @@ -159,9 +159,9 @@ nxt_timer_change(nxt_event_engine_t *engine, nxt_timer_t *timer, static void nxt_timer_changes_commit(nxt_event_engine_t *engine) { - nxt_timer_t *timer, **add, **add_end; + nxt_timer_t *timer; nxt_timers_t *timers; - nxt_timer_change_t *ch, *end; + nxt_timer_change_t *ch, *end, *add, *add_end; timers = &engine->timers; @@ -170,7 +170,7 @@ nxt_timer_changes_commit(nxt_event_engine_t *engine) ch = timers->changes; end = ch + timers->nchanges; - add = (nxt_timer_t **) ch; + add = ch; add_end = add; while (ch < end) { @@ -185,7 +185,8 @@ nxt_timer_changes_commit(nxt_event_engine_t *engine) timer->time = ch->time; - *add_end++ = timer; + add_end->timer = timer; + add_end++; if (!nxt_timer_is_in_tree(timer)) { break; @@ -209,7 +210,7 @@ nxt_timer_changes_commit(nxt_event_engine_t *engine) } while (add < add_end) { - timer = *add; + timer = add->timer; nxt_debug(timer->task, "timer rbtree insert: %M±%d", timer->time, timer->bias); -- cgit From 444b9ffea90c3d3f8c06421a03f027c889b4a1e2 Mon Sep 17 00:00:00 2001 From: Igor Sysoev Date: Tue, 26 Feb 2019 19:12:16 +0300 Subject: Keepalive mode is disabled on HTTP header parsing errors. --- src/nxt_h1proto.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src') diff --git a/src/nxt_h1proto.c b/src/nxt_h1proto.c index 4e1b22e9..b5b5d6e6 100644 --- a/src/nxt_h1proto.c +++ b/src/nxt_h1proto.c @@ -563,6 +563,8 @@ nxt_h1p_conn_request_header_parse(nxt_task_t *task, void *obj, void *data) error: + h1p->keepalive = 0; + nxt_http_request_error(task, r, status); } -- cgit From 547082171e5e78561964e0e64e16a47a5f9d92df Mon Sep 17 00:00:00 2001 From: Igor Sysoev Date: Tue, 26 Feb 2019 19:48:38 +0300 Subject: Introduced nxt_conf_array_elements_count(). --- src/nxt_conf.c | 7 +++++++ src/nxt_conf.h | 1 + 2 files changed, 8 insertions(+) (limited to 'src') diff --git a/src/nxt_conf.c b/src/nxt_conf.c index 2255e12f..f69133cc 100644 --- a/src/nxt_conf.c +++ b/src/nxt_conf.c @@ -360,6 +360,13 @@ nxt_conf_set_element_string_dup(nxt_conf_value_t *array, nxt_mp_t *mp, } +nxt_uint_t +nxt_conf_array_elements_count(nxt_conf_value_t *value) +{ + return value->u.array->count; +} + + nxt_uint_t nxt_conf_type(nxt_conf_value_t *value) { diff --git a/src/nxt_conf.h b/src/nxt_conf.h index 48521917..7c360dfe 100644 --- a/src/nxt_conf.h +++ b/src/nxt_conf.h @@ -125,6 +125,7 @@ void nxt_conf_set_element(nxt_conf_value_t *array, nxt_uint_t index, nxt_conf_value_t *value); nxt_int_t nxt_conf_set_element_string_dup(nxt_conf_value_t *array, nxt_mp_t *mp, nxt_uint_t index, nxt_str_t *value); +nxt_uint_t nxt_conf_array_elements_count(nxt_conf_value_t *value); #endif /* _NXT_CONF_INCLUDED_ */ -- cgit From ce650ea0f703f9228638e88ccb62d207aa647bf2 Mon Sep 17 00:00:00 2001 From: Igor Sysoev Date: Tue, 26 Feb 2019 19:48:41 +0300 Subject: Introduced nxt_conf_array_qsort(). --- src/nxt_conf.c | 16 ++++++++++++++++ src/nxt_conf.h | 2 ++ 2 files changed, 18 insertions(+) (limited to 'src') diff --git a/src/nxt_conf.c b/src/nxt_conf.c index f69133cc..4c6d8839 100644 --- a/src/nxt_conf.c +++ b/src/nxt_conf.c @@ -720,6 +720,22 @@ nxt_conf_get_array_element(nxt_conf_value_t *value, uint32_t index) } +void +nxt_conf_array_qsort(nxt_conf_value_t *value, + int (*compare)(const void *, const void *)) +{ + nxt_conf_array_t *array; + + if (value->type != NXT_CONF_VALUE_ARRAY) { + return; + } + + array = value->u.array; + + nxt_qsort(array->elements, array->count, sizeof(nxt_conf_value_t), compare); +} + + nxt_int_t nxt_conf_op_compile(nxt_mp_t *mp, nxt_conf_op_t **ops, nxt_conf_value_t *root, nxt_str_t *path, nxt_conf_value_t *value) diff --git a/src/nxt_conf.h b/src/nxt_conf.h index 7c360dfe..052334fe 100644 --- a/src/nxt_conf.h +++ b/src/nxt_conf.h @@ -126,6 +126,8 @@ void nxt_conf_set_element(nxt_conf_value_t *array, nxt_uint_t index, nxt_int_t nxt_conf_set_element_string_dup(nxt_conf_value_t *array, nxt_mp_t *mp, nxt_uint_t index, nxt_str_t *value); nxt_uint_t nxt_conf_array_elements_count(nxt_conf_value_t *value); +void nxt_conf_array_qsort(nxt_conf_value_t *value, + int (*compare)(const void *, const void *)); #endif /* _NXT_CONF_INCLUDED_ */ -- cgit From 95c9bba33bcb1d0e8e9e7c64d2591187ce11bab1 Mon Sep 17 00:00:00 2001 From: Igor Sysoev Date: Tue, 26 Feb 2019 19:48:44 +0300 Subject: Introduced nxt_memcpy_upcase(). --- src/nxt_string.c | 13 +++++++++++++ src/nxt_string.h | 2 ++ 2 files changed, 15 insertions(+) (limited to 'src') diff --git a/src/nxt_string.c b/src/nxt_string.c index 1858b58b..7d8c1ce3 100644 --- a/src/nxt_string.c +++ b/src/nxt_string.c @@ -96,6 +96,19 @@ nxt_memcpy_lowcase(u_char *dst, const u_char *src, size_t length) } +void +nxt_memcpy_upcase(u_char *dst, const u_char *src, size_t length) +{ + u_char c; + + while (length != 0) { + c = *src++; + *dst++ = nxt_upcase(c); + length--; + } +} + + u_char * nxt_cpystrn(u_char *dst, const u_char *src, size_t length) { diff --git a/src/nxt_string.h b/src/nxt_string.h index 5f82cca8..22a63a17 100644 --- a/src/nxt_string.h +++ b/src/nxt_string.h @@ -43,6 +43,8 @@ nxt_memcpy(dst, src, length) \ NXT_EXPORT void nxt_memcpy_lowcase(u_char *dst, const u_char *src, size_t length); +NXT_EXPORT void nxt_memcpy_upcase(u_char *dst, const u_char *src, + size_t length); /* -- cgit From d4ccaae900f78b13923a9bd9ee7bbaa33c99b18b Mon Sep 17 00:00:00 2001 From: Igor Sysoev Date: Wed, 27 Feb 2019 16:41:11 +0300 Subject: Initial routing implementation. --- src/nxt_http.h | 32 +- src/nxt_http_request.c | 53 ++- src/nxt_http_route.c | 849 +++++++++++++++++++++++++++++++++++++++++++++++++ src/nxt_router.c | 74 +++-- src/nxt_router.h | 10 +- 5 files changed, 978 insertions(+), 40 deletions(-) create mode 100644 src/nxt_http_route.c (limited to 'src') diff --git a/src/nxt_http.h b/src/nxt_http.h index af0b2cd6..23c406d3 100644 --- a/src/nxt_http.h +++ b/src/nxt_http.h @@ -21,6 +21,7 @@ typedef enum { NXT_HTTP_NOT_MODIFIED = 304, NXT_HTTP_BAD_REQUEST = 400, + NXT_HTTP_NOT_FOUND = 404, NXT_HTTP_REQUEST_TIMEOUT = 408, NXT_HTTP_LENGTH_REQUIRED = 411, NXT_HTTP_PAYLOAD_TOO_LARGE = 413, @@ -112,6 +113,7 @@ struct nxt_http_request_s { nxt_buf_t *out; const nxt_http_request_state_t *state; + nxt_str_t host; nxt_str_t target; nxt_str_t version; nxt_str_t *method; @@ -124,7 +126,6 @@ struct nxt_http_request_s { nxt_http_field_t *cookie; nxt_http_field_t *referer; nxt_http_field_t *user_agent; - nxt_str_t host; nxt_off_t content_length_n; nxt_sockaddr_t *remote; @@ -136,6 +137,7 @@ struct nxt_http_request_s { nxt_http_status_t status:16; + uint8_t pass_count; /* 8 bits */ uint8_t protocol; /* 2 bits */ uint8_t logged; /* 1 bit */ uint8_t header_sent; /* 1 bit */ @@ -143,6 +145,22 @@ struct nxt_http_request_s { }; +typedef struct nxt_http_route_s nxt_http_route_t; + + +struct nxt_http_pass_s { + nxt_http_pass_t *(*handler)(nxt_task_t *task, + nxt_http_request_t *r, + nxt_http_pass_t *pass); + union { + nxt_http_route_t *route; + nxt_app_t *application; + } u; + + nxt_str_t name; +}; + + typedef void (*nxt_http_proto_body_read_t)(nxt_task_t *task, nxt_http_request_t *r); typedef void (*nxt_http_proto_local_addr_t)(nxt_task_t *task, @@ -184,6 +202,18 @@ nxt_int_t nxt_http_request_field(void *ctx, nxt_http_field_t *field, nxt_int_t nxt_http_request_content_length(void *ctx, nxt_http_field_t *field, uintptr_t data); +nxt_http_routes_t *nxt_http_routes_create(nxt_task_t *task, + nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *routes_conf); +nxt_http_pass_t *nxt_http_pass_create(nxt_task_t *task, + nxt_router_temp_conf_t *tmcf, nxt_str_t *name); +void nxt_http_routes_resolve(nxt_task_t *task, nxt_router_temp_conf_t *tmcf); +nxt_http_pass_t *nxt_http_pass_application(nxt_task_t *task, + nxt_router_temp_conf_t *tmcf, nxt_str_t *name); +void nxt_http_routes_cleanup(nxt_task_t *task, nxt_http_routes_t *routes); +void nxt_http_pass_cleanup(nxt_task_t *task, nxt_http_pass_t *pass); + +nxt_http_pass_t *nxt_http_request_application(nxt_task_t *task, + nxt_http_request_t *r, nxt_http_pass_t *pass); extern nxt_time_string_t nxt_http_date_cache; diff --git a/src/nxt_http_request.c b/src/nxt_http_request.c index b7d46c72..f6c14df9 100644 --- a/src/nxt_http_request.c +++ b/src/nxt_http_request.c @@ -10,7 +10,7 @@ static nxt_int_t nxt_http_validate_host(nxt_str_t *host, nxt_mp_t *mp); static void nxt_http_request_start(nxt_task_t *task, void *obj, void *data); -static void nxt_http_app_request(nxt_task_t *task, void *obj, void *data); +static void nxt_http_request_pass(nxt_task_t *task, void *obj, void *data); static void nxt_http_request_mem_buf_completion(nxt_task_t *task, void *obj, void *data); static void nxt_http_request_done(nxt_task_t *task, void *obj, void *data); @@ -278,25 +278,60 @@ nxt_http_request_start(nxt_task_t *task, void *obj, void *data) static const nxt_http_request_state_t nxt_http_request_body_state nxt_aligned(64) = { - .ready_handler = nxt_http_app_request, + .ready_handler = nxt_http_request_pass, .error_handler = nxt_http_request_close_handler, }; static void -nxt_http_app_request(nxt_task_t *task, void *obj, void *data) +nxt_http_request_pass(nxt_task_t *task, void *obj, void *data) +{ + nxt_http_pass_t *pass; + nxt_http_request_t *r; + + r = obj; + + pass = r->conf->socket_conf->pass; + + if (nxt_slow_path(pass == NULL)) { + goto fail; + } + + for ( ;; ) { + nxt_debug(task, "http request route: %V", &pass->name); + + pass = pass->handler(task, r, pass); + if (pass == NULL) { + break; + } + + if (nxt_slow_path(r->pass_count++ == 255)) { + goto fail; + } + } + + return; + +fail: + + nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR); +} + + +nxt_http_pass_t * +nxt_http_request_application(nxt_task_t *task, nxt_http_request_t *r, + nxt_http_pass_t *pass) { nxt_int_t ret; nxt_event_engine_t *engine; - nxt_http_request_t *r; nxt_app_parse_ctx_t *ar; - r = obj; + nxt_debug(task, "http request application"); ar = nxt_mp_zget(r->mem_pool, sizeof(nxt_app_parse_ctx_t)); if (nxt_slow_path(ar == NULL)) { nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR); - return; + return NULL; } ar->request = r; @@ -370,10 +405,12 @@ nxt_http_app_request(nxt_task_t *task, void *obj, void *data) ret = nxt_http_parse_request_init(&ar->resp_parser, r->mem_pool); if (nxt_slow_path(ret != NXT_OK)) { nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR); - return; + return NULL; } - nxt_router_process_http_request(task, ar); + nxt_router_process_http_request(task, ar, pass->u.application); + + return NULL; } diff --git a/src/nxt_http_route.c b/src/nxt_http_route.c new file mode 100644 index 00000000..133c39ab --- /dev/null +++ b/src/nxt_http_route.c @@ -0,0 +1,849 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#include +#include + + +typedef enum { + NXT_HTTP_ROUTE_STRING = 0, + NXT_HTTP_ROUTE_STRING_PTR, + NXT_HTTP_ROUTE_FIELD, + NXT_HTTP_ROUTE_HEADER, + NXT_HTTP_ROUTE_ARGUMENT, + NXT_HTTP_ROUTE_COOKIE, +} nxt_http_route_object_t; + + +typedef enum { + NXT_HTTP_ROUTE_PATTERN_EXACT = 0, + NXT_HTTP_ROUTE_PATTERN_BEGIN, + NXT_HTTP_ROUTE_PATTERN_END, + NXT_HTTP_ROUTE_PATTERN_SUBSTRING, +} nxt_http_route_pattern_type_t; + + +typedef enum { + NXT_HTTP_ROUTE_PATTERN_NOCASE = 0, + NXT_HTTP_ROUTE_PATTERN_LOWCASE, + NXT_HTTP_ROUTE_PATTERN_UPCASE, +} nxt_http_route_pattern_case_t; + + +typedef struct { + nxt_conf_value_t *host; + nxt_conf_value_t *uri; + nxt_conf_value_t *method; +} nxt_http_route_match_conf_t; + + +typedef struct { + nxt_str_t test; + uint32_t min_length; + + nxt_http_route_pattern_type_t type:8; + uint8_t case_sensitive; /* 1 bit */ + uint8_t negative; /* 1 bit */ + uint8_t any; /* 1 bit */ +} nxt_http_route_pattern_t; + + +typedef struct { + uintptr_t offset; + uint32_t items; + nxt_http_route_object_t object:8; + nxt_http_route_pattern_t pattern[0]; +} nxt_http_route_rule_t; + + +typedef struct { + uint32_t items; + nxt_http_pass_t pass; + nxt_http_route_rule_t *rule[0]; +} nxt_http_route_match_t; + + +struct nxt_http_route_s { + nxt_str_t name; + uint32_t items; + nxt_http_route_match_t *match[0]; +}; + + +struct nxt_http_routes_s { + uint32_t items; + nxt_http_route_t *route[0]; +}; + + +static nxt_http_route_t *nxt_http_route_create(nxt_task_t *task, + nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv); +static nxt_http_route_match_t *nxt_http_route_match_create(nxt_task_t *task, + nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv); +static nxt_http_route_rule_t *nxt_http_route_rule_create(nxt_task_t *task, + nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv, + nxt_bool_t case_sensitive, nxt_http_route_pattern_case_t pattern_case); +static int nxt_http_pattern_compare(const void *one, const void *two); +static nxt_int_t nxt_http_route_pattern_create(nxt_task_t *task, nxt_mp_t *mp, + nxt_conf_value_t *cv, nxt_http_route_pattern_t *pattern, + nxt_http_route_pattern_case_t pattern_case); + +static void nxt_http_route_resolve(nxt_task_t *task, + nxt_router_temp_conf_t *tmcf, nxt_http_route_t *route); +static void nxt_http_pass_resolve(nxt_task_t *task, + nxt_router_temp_conf_t *tmcf, nxt_http_pass_t *pass); +static nxt_http_route_t *nxt_http_route_find(nxt_http_routes_t *routes, + nxt_str_t *name); +static void nxt_http_route_cleanup(nxt_task_t *task, nxt_http_route_t *routes); + +static nxt_http_pass_t *nxt_http_route_pass(nxt_task_t *task, + nxt_http_request_t *r, nxt_http_pass_t *start); +static nxt_http_pass_t *nxt_http_route_match(nxt_http_request_t *r, + nxt_http_route_match_t *match); +static nxt_bool_t nxt_http_route_rule(nxt_http_request_t *r, + nxt_http_route_rule_t *rule); +static nxt_bool_t nxt_http_route_pattern(nxt_http_request_t *r, + nxt_http_route_pattern_t *pattern, u_char *start, size_t length); + + +nxt_http_routes_t * +nxt_http_routes_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, + nxt_conf_value_t *routes_conf) +{ + size_t size; + uint32_t i, n, next; + nxt_mp_t *mp; + nxt_str_t name, *string; + nxt_bool_t object; + nxt_conf_value_t *route_conf; + nxt_http_route_t *route; + nxt_http_routes_t *routes; + + object = (nxt_conf_type(routes_conf) == NXT_CONF_OBJECT); + n = object ? nxt_conf_object_members_count(routes_conf) : 1; + size = sizeof(nxt_http_routes_t) + n * sizeof(nxt_http_route_t *); + + mp = tmcf->router_conf->mem_pool; + + routes = nxt_mp_alloc(mp, size); + if (nxt_slow_path(routes == NULL)) { + return NULL; + } + + routes->items = n; + + if (object) { + next = 0; + + for (i = 0; i < n; i++) { + route_conf = nxt_conf_next_object_member(routes_conf, &name, &next); + + route = nxt_http_route_create(task, tmcf, route_conf); + if (nxt_slow_path(route == NULL)) { + return NULL; + } + + routes->route[i] = route; + + string = nxt_str_dup(mp, &route->name, &name); + if (nxt_slow_path(string == NULL)) { + return NULL; + } + } + + } else { + route = nxt_http_route_create(task, tmcf, routes_conf); + if (nxt_slow_path(route == NULL)) { + return NULL; + } + + routes->route[0] = route; + + route->name.length = 0; + route->name.start = NULL; + } + + return routes; +} + + +static nxt_conf_map_t nxt_http_route_match_conf[] = { + { + nxt_string("host"), + NXT_CONF_MAP_PTR, + offsetof(nxt_http_route_match_conf_t, host), + }, + + { + nxt_string("uri"), + NXT_CONF_MAP_PTR, + offsetof(nxt_http_route_match_conf_t, uri), + }, + + { + nxt_string("method"), + NXT_CONF_MAP_PTR, + offsetof(nxt_http_route_match_conf_t, method), + }, +}; + + +static nxt_http_route_t * +nxt_http_route_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, + nxt_conf_value_t *cv) +{ + size_t size; + uint32_t i, n; + nxt_conf_value_t *value; + nxt_http_route_t *route; + nxt_http_route_match_t *match, **m; + + n = nxt_conf_array_elements_count(cv); + size = sizeof(nxt_http_route_t) + n * sizeof(nxt_http_route_match_t *); + + route = nxt_mp_alloc(tmcf->router_conf->mem_pool, size); + if (nxt_slow_path(route == NULL)) { + return NULL; + } + + route->items = n; + m = &route->match[0]; + + for (i = 0; i < n; i++) { + value = nxt_conf_get_array_element(cv, i); + + match = nxt_http_route_match_create(task, tmcf, value); + if (match == NULL) { + return NULL; + } + + *m++ = match; + } + + return route; +} + + +static nxt_http_route_match_t * +nxt_http_route_match_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, + nxt_conf_value_t *cv) +{ + size_t size; + uint32_t n; + nxt_int_t ret; + nxt_str_t pass, *string; + nxt_conf_value_t *match_conf, *pass_conf; + nxt_http_route_rule_t *rule, **p; + nxt_http_route_match_t *match; + nxt_http_route_match_conf_t mtcf; + + static nxt_str_t pass_path = nxt_string("/action/pass"); + static nxt_str_t match_path = nxt_string("/match"); + + pass_conf = nxt_conf_get_path(cv, &pass_path); + if (nxt_slow_path(pass_conf == NULL)) { + return NULL; + } + + nxt_conf_get_string(pass_conf, &pass); + + match_conf = nxt_conf_get_path(cv, &match_path); + + n = (match_conf != NULL) ? nxt_conf_object_members_count(match_conf) : 0; + size = sizeof(nxt_http_route_match_t) + n * sizeof(nxt_http_route_rule_t *); + + match = nxt_mp_alloc(tmcf->router_conf->mem_pool, size); + if (nxt_slow_path(match == NULL)) { + return NULL; + } + + match->pass.u.route = NULL; + match->pass.handler = NULL; + match->items = n; + + string = nxt_str_dup(tmcf->router_conf->mem_pool, &match->pass.name, &pass); + if (nxt_slow_path(string == NULL)) { + return NULL; + } + + if (n == 0) { + return match; + } + + nxt_memzero(&mtcf, sizeof(mtcf)); + + ret = nxt_conf_map_object(tmcf->mem_pool, + match_conf, nxt_http_route_match_conf, + nxt_nitems(nxt_http_route_match_conf), &mtcf); + if (ret != NXT_OK) { + return NULL; + } + + p = &match->rule[0]; + + if (mtcf.host != NULL) { + rule = nxt_http_route_rule_create(task, tmcf, mtcf.host, 1, + NXT_HTTP_ROUTE_PATTERN_LOWCASE); + if (rule == NULL) { + return NULL; + } + + rule->offset = offsetof(nxt_http_request_t, host); + rule->object = NXT_HTTP_ROUTE_STRING; + *p++ = rule; + } + + if (mtcf.uri != NULL) { + rule = nxt_http_route_rule_create(task, tmcf, mtcf.uri, 1, + NXT_HTTP_ROUTE_PATTERN_NOCASE); + if (rule == NULL) { + return NULL; + } + + rule->offset = offsetof(nxt_http_request_t, path); + rule->object = NXT_HTTP_ROUTE_STRING_PTR; + *p++ = rule; + } + + if (mtcf.method != NULL) { + rule = nxt_http_route_rule_create(task, tmcf, mtcf.method, 1, + NXT_HTTP_ROUTE_PATTERN_UPCASE); + if (rule == NULL) { + return NULL; + } + + rule->offset = offsetof(nxt_http_request_t, method); + rule->object = NXT_HTTP_ROUTE_STRING_PTR; + *p++ = rule; + } + + return match; +} + + +static nxt_http_route_rule_t * +nxt_http_route_rule_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, + nxt_conf_value_t *cv, nxt_bool_t case_sensitive, + nxt_http_route_pattern_case_t pattern_case) +{ + size_t size; + uint32_t i, n; + nxt_mp_t *mp; + nxt_int_t ret; + nxt_bool_t string; + nxt_conf_value_t *value; + nxt_http_route_rule_t *rule; + nxt_http_route_pattern_t *pattern; + + mp = tmcf->router_conf->mem_pool; + + string = (nxt_conf_type(cv) != NXT_CONF_ARRAY); + n = string ? 1 : nxt_conf_array_elements_count(cv); + size = sizeof(nxt_http_route_rule_t) + n * sizeof(nxt_http_route_pattern_t); + + rule = nxt_mp_alloc(mp, size); + if (nxt_slow_path(rule == NULL)) { + return NULL; + } + + rule->items = n; + + pattern = &rule->pattern[0]; + + if (string) { + pattern[0].case_sensitive = case_sensitive; + ret = nxt_http_route_pattern_create(task, mp, cv, &pattern[0], + pattern_case); + if (nxt_slow_path(ret != NXT_OK)) { + return NULL; + } + + return rule; + } + + nxt_conf_array_qsort(cv, nxt_http_pattern_compare); + + for (i = 0; i < n; i++) { + pattern[i].case_sensitive = case_sensitive; + value = nxt_conf_get_array_element(cv, i); + + ret = nxt_http_route_pattern_create(task, mp, value, &pattern[i], + pattern_case); + if (nxt_slow_path(ret != NXT_OK)) { + return NULL; + } + } + + return rule; +} + + +static int +nxt_http_pattern_compare(const void *one, const void *two) +{ + nxt_str_t test; + nxt_bool_t negative1, negative2; + nxt_conf_value_t *value; + + value = (nxt_conf_value_t *) one; + nxt_conf_get_string(value, &test); + negative1 = (test.length != 0 && test.start[0] == '!'); + + value = (nxt_conf_value_t *) two; + nxt_conf_get_string(value, &test); + negative2 = (test.length != 0 && test.start[0] == '!'); + + return (negative2 - negative1); +} + + +static nxt_int_t +nxt_http_route_pattern_create(nxt_task_t *task, nxt_mp_t *mp, + nxt_conf_value_t *cv, nxt_http_route_pattern_t *pattern, + nxt_http_route_pattern_case_t pattern_case) +{ + u_char *start; + nxt_str_t test; + nxt_http_route_pattern_type_t type; + + type = NXT_HTTP_ROUTE_PATTERN_EXACT; + + nxt_conf_get_string(cv, &test); + + pattern->negative = 0; + pattern->any = 1; + + if (test.length != 0) { + + if (test.start[0] == '!') { + test.start++; + test.length--; + + pattern->negative = 1; + pattern->any = 0; + } + + if (test.length != 0) { + + if (test.start[0] == '*') { + test.start++; + test.length--; + + if (test.length != 0) { + if (test.start[test.length - 1] == '*') { + test.length--; + type = NXT_HTTP_ROUTE_PATTERN_SUBSTRING; + + } else { + type = NXT_HTTP_ROUTE_PATTERN_END; + } + + } else { + type = NXT_HTTP_ROUTE_PATTERN_BEGIN; + } + + } else if (test.start[test.length - 1] == '*') { + test.length--; + type = NXT_HTTP_ROUTE_PATTERN_BEGIN; + } + } + } + + pattern->type = type; + pattern->min_length = test.length; + pattern->test.length = test.length; + + start = nxt_mp_nget(mp, test.length); + if (nxt_slow_path(start == NULL)) { + return NXT_ERROR; + } + + pattern->test.start = start; + + switch (pattern_case) { + + case NXT_HTTP_ROUTE_PATTERN_UPCASE: + nxt_memcpy_upcase(start, test.start, test.length); + break; + + case NXT_HTTP_ROUTE_PATTERN_LOWCASE: + nxt_memcpy_lowcase(start, test.start, test.length); + break; + + case NXT_HTTP_ROUTE_PATTERN_NOCASE: + nxt_memcpy(start, test.start, test.length); + break; + } + + return NXT_OK; +} + + +void +nxt_http_routes_resolve(nxt_task_t *task, nxt_router_temp_conf_t *tmcf) +{ + nxt_uint_t items; + nxt_http_route_t **route; + nxt_http_routes_t *routes; + + routes = tmcf->router_conf->routes; + if (routes != NULL) { + items = routes->items; + route = &routes->route[0]; + + while (items != 0) { + nxt_http_route_resolve(task, tmcf, *route); + + route++; + items--; + } + } +} + + +static void +nxt_http_route_resolve(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, + nxt_http_route_t *route) +{ + nxt_uint_t items; + nxt_http_route_match_t **match; + + items = route->items; + match = &route->match[0]; + + while (items != 0) { + nxt_http_pass_resolve(task, tmcf, &(*match)->pass); + + match++; + items--; + } +} + + +static void +nxt_http_pass_resolve(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, + nxt_http_pass_t *pass) +{ + nxt_str_t name; + + name = pass->name; + + if (nxt_str_start(&name, "applications/", 13)) { + name.length -= 13; + name.start += 13; + + pass->u.application = nxt_router_listener_application(tmcf, &name); + nxt_router_app_use(task, pass->u.application, 1); + + pass->handler = nxt_http_request_application; + + } else if (nxt_str_start(&name, "routes", 6)) { + + if (name.length == 6) { + name.length = 0; + name.start = NULL; + + } else if (name.start[6] == '/') { + name.length -= 7; + name.start += 7; + } + + pass->u.route = nxt_http_route_find(tmcf->router_conf->routes, &name); + + pass->handler = nxt_http_route_pass; + } +} + + +static nxt_http_route_t * +nxt_http_route_find(nxt_http_routes_t *routes, nxt_str_t *name) +{ + nxt_uint_t items; + nxt_http_route_t **route; + + items = routes->items; + route = &routes->route[0]; + + do { + if (nxt_strstr_eq(&(*route)->name, name)) { + return *route; + } + + route++; + items--; + + } while (items != 0); + + return NULL; +} + + +nxt_http_pass_t * +nxt_http_pass_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, + nxt_str_t *name) +{ + nxt_http_pass_t *pass; + + pass = nxt_mp_alloc(tmcf->router_conf->mem_pool, sizeof(nxt_http_pass_t)); + if (nxt_slow_path(pass == NULL)) { + return NULL; + } + + pass->name = *name; + + nxt_http_pass_resolve(task, tmcf, pass); + + return pass; +} + + +/* COMPATIBILITY: listener application. */ + +nxt_http_pass_t * +nxt_http_pass_application(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, + nxt_str_t *name) +{ + nxt_http_pass_t *pass; + + pass = nxt_mp_alloc(tmcf->router_conf->mem_pool, sizeof(nxt_http_pass_t)); + if (nxt_slow_path(pass == NULL)) { + return NULL; + } + + pass->name = *name; + + pass->u.application = nxt_router_listener_application(tmcf, name); + nxt_router_app_use(task, pass->u.application, 1); + + pass->handler = nxt_http_request_application; + + return pass; +} + + +void +nxt_http_routes_cleanup(nxt_task_t *task, nxt_http_routes_t *routes) +{ + nxt_uint_t items; + nxt_http_route_t **route; + + if (routes != NULL) { + items = routes->items; + route = &routes->route[0]; + + do { + nxt_http_route_cleanup(task, *route); + + route++; + items--; + + } while (items != 0); + } +} + + +static void +nxt_http_route_cleanup(nxt_task_t *task, nxt_http_route_t *route) +{ + nxt_uint_t items; + nxt_http_route_match_t **match; + + items = route->items; + match = &route->match[0]; + + do { + nxt_http_pass_cleanup(task, &(*match)->pass); + + match++; + items--; + + } while (items != 0); +} + + +void +nxt_http_pass_cleanup(nxt_task_t *task, nxt_http_pass_t *pass) +{ + if (pass->handler == nxt_http_request_application) { + nxt_router_app_use(task, pass->u.application, -1); + } +} + + +static nxt_http_pass_t * +nxt_http_route_pass(nxt_task_t *task, nxt_http_request_t *r, + nxt_http_pass_t *start) +{ + nxt_uint_t items; + nxt_http_pass_t *pass; + nxt_http_route_t *route; + nxt_http_route_match_t **match; + + route = start->u.route; + items = route->items; + match = &route->match[0]; + + while (items != 0) { + pass = nxt_http_route_match(r, *match); + if (pass != NULL) { + return pass; + } + + match++; + items--; + } + + nxt_http_request_error(task, r, NXT_HTTP_NOT_FOUND); + + return NULL; +} + + +static nxt_http_pass_t * +nxt_http_route_match(nxt_http_request_t *r, nxt_http_route_match_t *match) +{ + nxt_uint_t items; + nxt_http_route_rule_t **rule; + + rule = &match->rule[0]; + items = match->items; + + while (items != 0) { + if (!nxt_http_route_rule(r, *rule)) { + return NULL; + } + + rule++; + items--; + } + + return &match->pass; +} + + +static nxt_bool_t +nxt_http_route_rule(nxt_http_request_t *r, nxt_http_route_rule_t *rule) +{ + void *p, **pp; + u_char *start; + size_t length; + nxt_str_t *s; + nxt_uint_t items; + nxt_bool_t ret; + nxt_http_field_t *f; + nxt_http_route_pattern_t *pattern; + + p = nxt_pointer_to(r, rule->offset); + + if (rule->object == NXT_HTTP_ROUTE_STRING) { + s = p; + length = s->length; + start = s->start; + + } else { + pp = p; + p = *pp; + + if (p == NULL) { + return 0; + } + + switch (rule->object) { + + case NXT_HTTP_ROUTE_STRING_PTR: + s = p; + length = s->length; + start = s->start; + break; + + case NXT_HTTP_ROUTE_FIELD: + f = p; + length = f->value_length; + start = f->value; + break; + + case NXT_HTTP_ROUTE_HEADER: + return 0; + + case NXT_HTTP_ROUTE_ARGUMENT: + return 0; + + case NXT_HTTP_ROUTE_COOKIE: + return 0; + + default: + nxt_unreachable(); + return 0; + } + } + + items = rule->items; + pattern = &rule->pattern[0]; + + do { + ret = nxt_http_route_pattern(r, pattern, start, length); + + ret ^= pattern->negative; + + if (pattern->any == ret) { + return ret; + } + + pattern++; + items--; + + } while (items != 0); + + return ret; +} + + +static nxt_bool_t +nxt_http_route_pattern(nxt_http_request_t *r, nxt_http_route_pattern_t *pattern, + u_char *start, size_t length) +{ + nxt_str_t *test; + + if (length < pattern->min_length) { + return 0; + } + + test = &pattern->test; + + switch (pattern->type) { + + case NXT_HTTP_ROUTE_PATTERN_EXACT: + if (length != test->length) { + return 0; + } + + break; + + case NXT_HTTP_ROUTE_PATTERN_BEGIN: + break; + + case NXT_HTTP_ROUTE_PATTERN_END: + start += length - test->length; + break; + + case NXT_HTTP_ROUTE_PATTERN_SUBSTRING: + if (pattern->case_sensitive) { + return (nxt_memstrn(start, start + length, + (char *) test->start, test->length) + != NULL); + } + + return (nxt_memcasestrn(start, start + length, + (char *) test->start, test->length) + != NULL); + } + + if (pattern->case_sensitive) { + return (nxt_memcmp(start, test->start, test->length) == 0); + } + + return (nxt_memcasecmp(start, test->start, test->length) == 0); +} diff --git a/src/nxt_router.c b/src/nxt_router.c index 5621abec..e81a2f96 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -31,7 +31,8 @@ typedef struct { typedef struct { - nxt_str_t application; + nxt_str_t pass; + nxt_str_t application; } nxt_router_listener_conf_t; @@ -163,8 +164,6 @@ static void nxt_router_conf_send(nxt_task_t *task, static nxt_int_t nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, u_char *start, u_char *end); static nxt_app_t *nxt_router_app_find(nxt_queue_t *queue, nxt_str_t *name); -static nxt_app_t *nxt_router_listener_application(nxt_router_temp_conf_t *tmcf, - nxt_str_t *name); static void nxt_router_listen_socket_rpc_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_socket_conf_t *skcf); static void nxt_router_listen_socket_ready(nxt_task_t *task, @@ -1174,11 +1173,14 @@ nxt_router_conf_error(nxt_task_t *task, nxt_router_temp_conf_t *tmcf) nxt_queue_add(&new_socket_confs, &tmcf->pending); nxt_queue_add(&new_socket_confs, &tmcf->creating); + rtcf = tmcf->router_conf; + + nxt_http_routes_cleanup(task, rtcf->routes); + nxt_queue_each(skcf, &new_socket_confs, nxt_socket_conf_t, link) { - if (skcf->application != NULL) { - nxt_router_app_use(task, skcf->application, -1); - skcf->application = NULL; + if (skcf->pass != NULL) { + nxt_http_pass_cleanup(task, skcf->pass); } } nxt_queue_loop; @@ -1189,7 +1191,6 @@ nxt_router_conf_error(nxt_task_t *task, nxt_router_temp_conf_t *tmcf) } nxt_queue_loop; - rtcf = tmcf->router_conf; router = rtcf->router; nxt_queue_add(&router->sockets, &tmcf->keeping); @@ -1298,9 +1299,15 @@ static nxt_conf_map_t nxt_router_app_processes_conf[] = { static nxt_conf_map_t nxt_router_listener_conf[] = { + { + nxt_string("pass"), + NXT_CONF_MAP_STR_COPY, + offsetof(nxt_router_listener_conf_t, pass), + }, + { nxt_string("application"), - NXT_CONF_MAP_STR, + NXT_CONF_MAP_STR_COPY, offsetof(nxt_router_listener_conf_t, application), }, }; @@ -1379,7 +1386,9 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *conf, *http, *value; nxt_conf_value_t *applications, *application; nxt_conf_value_t *listeners, *listener; + nxt_conf_value_t *routes_conf; nxt_socket_conf_t *skcf; + nxt_http_routes_t *routes; nxt_event_engine_t *engine; nxt_app_lang_module_t *lang; nxt_router_app_conf_t apcf; @@ -1392,6 +1401,7 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, static nxt_str_t http_path = nxt_string("/settings/http"); static nxt_str_t applications_path = nxt_string("/applications"); static nxt_str_t listeners_path = nxt_string("/listeners"); + static nxt_str_t routes_path = nxt_string("/routes"); static nxt_str_t access_log_path = nxt_string("/access_log"); #if (NXT_TLS) static nxt_str_t certificate_path = nxt_string("/tls/certificate"); @@ -1589,6 +1599,15 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, app_joint->free_app_work.obj = app_joint; } + routes_conf = nxt_conf_get_path(conf, &routes_path); + if (nxt_fast_path(routes_conf != NULL)) { + routes = nxt_http_routes_create(task, tmcf, routes_conf); + if (nxt_slow_path(routes == NULL)) { + return NXT_ERROR; + } + tmcf->router_conf->routes = routes; + } + http = nxt_conf_get_path(conf, &http_path); #if 0 if (http == NULL) { @@ -1671,10 +1690,13 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, skcf->router_conf = tmcf->router_conf; skcf->router_conf->count++; - if (lscf.application.length > 0) { - skcf->application = nxt_router_listener_application(tmcf, - &lscf.application); - nxt_router_app_use(task, skcf->application, 1); + if (lscf.pass.length != 0) { + skcf->pass = nxt_http_pass_create(task, tmcf, &lscf.pass); + + /* COMPATIBILITY: listener application. */ + } else if (lscf.application.length > 0) { + skcf->pass = nxt_http_pass_application(task, tmcf, + &lscf.application); } } @@ -1712,6 +1734,8 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, tmcf->router_conf->access_log = access_log; } + nxt_http_routes_resolve(task, tmcf); + nxt_queue_add(&tmcf->deleting, &router->sockets); nxt_queue_init(&router->sockets); @@ -1752,7 +1776,7 @@ nxt_router_app_find(nxt_queue_t *queue, nxt_str_t *name) } -static nxt_app_t * +nxt_app_t * nxt_router_listener_application(nxt_router_temp_conf_t *tmcf, nxt_str_t *name) { nxt_app_t *app; @@ -2885,7 +2909,6 @@ nxt_router_listen_event_release(nxt_task_t *task, nxt_listen_event_t *lev, void nxt_router_conf_release(nxt_task_t *task, nxt_socket_conf_joint_t *joint) { - nxt_app_t *app; nxt_socket_conf_t *skcf; nxt_router_conf_t *rtcf; nxt_thread_spinlock_t *lock; @@ -2904,7 +2927,6 @@ nxt_router_conf_release(nxt_task_t *task, nxt_socket_conf_joint_t *joint) * be already destroyed by another thread. */ skcf = joint->socket_conf; - app = skcf->application; rtcf = skcf->router_conf; lock = &rtcf->router->lock; @@ -2916,7 +2938,6 @@ nxt_router_conf_release(nxt_task_t *task, nxt_socket_conf_joint_t *joint) if (--skcf->count != 0) { skcf = NULL; rtcf = NULL; - app = NULL; } else { nxt_queue_remove(&skcf->link); @@ -2929,6 +2950,10 @@ nxt_router_conf_release(nxt_task_t *task, nxt_socket_conf_joint_t *joint) nxt_thread_spin_unlock(lock); if (skcf != NULL) { + if (skcf->pass != NULL) { + nxt_http_pass_cleanup(task, skcf->pass); + } + #if (NXT_TLS) if (skcf->tls != NULL) { task->thread->runtime->tls->server_free(task, skcf->tls); @@ -2936,16 +2961,14 @@ nxt_router_conf_release(nxt_task_t *task, nxt_socket_conf_joint_t *joint) #endif } - if (app != NULL) { - nxt_router_app_use(task, app, -1); - } - /* TODO remove engine->port */ /* TODO excude from connected ports */ if (rtcf != NULL) { nxt_debug(task, "old router conf is destroyed"); + nxt_http_routes_cleanup(task, rtcf->routes); + nxt_router_access_log_release(task, lock, rtcf->access_log); nxt_mp_thread_adopt(rtcf->mem_pool); @@ -4432,10 +4455,10 @@ nxt_router_app_port(nxt_task_t *task, nxt_app_t *app, nxt_req_app_link_t *ra) void -nxt_router_process_http_request(nxt_task_t *task, nxt_app_parse_ctx_t *ar) +nxt_router_process_http_request(nxt_task_t *task, nxt_app_parse_ctx_t *ar, + nxt_app_t *app) { nxt_int_t res; - nxt_app_t *app; nxt_port_t *port; nxt_event_engine_t *engine; nxt_http_request_t *r; @@ -4443,13 +4466,6 @@ nxt_router_process_http_request(nxt_task_t *task, nxt_app_parse_ctx_t *ar) nxt_req_conn_link_t *rc; r = ar->request; - app = r->conf->socket_conf->application; - - if (app == NULL) { - nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR); - return; - } - engine = task->thread->engine; rc = nxt_port_rpc_register_handler_ex(task, engine->port, diff --git a/src/nxt_router.h b/src/nxt_router.h index a2da8ff9..dec56bd5 100644 --- a/src/nxt_router.h +++ b/src/nxt_router.h @@ -16,6 +16,8 @@ typedef struct nxt_http_request_s nxt_http_request_t; #include +typedef struct nxt_http_pass_s nxt_http_pass_t; +typedef struct nxt_http_routes_s nxt_http_routes_t; typedef struct nxt_router_access_log_s nxt_router_access_log_t; @@ -34,6 +36,7 @@ typedef struct { uint32_t count; uint32_t threads; nxt_router_t *router; + nxt_http_routes_t *routes; nxt_mp_t *mem_pool; nxt_router_access_log_t *access_log; @@ -137,7 +140,7 @@ typedef struct { nxt_queue_link_t link; nxt_router_conf_t *router_conf; - nxt_app_t *application; + nxt_http_pass_t *pass; /* * A listen socket time can be shorter than socket configuration life @@ -189,8 +192,11 @@ void nxt_router_remove_pid_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg); void nxt_router_access_log_reopen_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg); -void nxt_router_process_http_request(nxt_task_t *task, nxt_app_parse_ctx_t *ar); +void nxt_router_process_http_request(nxt_task_t *task, nxt_app_parse_ctx_t *ar, + nxt_app_t *app); void nxt_router_app_port_close(nxt_task_t *task, nxt_port_t *port); +nxt_app_t *nxt_router_listener_application(nxt_router_temp_conf_t *tmcf, + nxt_str_t *name); void nxt_router_app_use(nxt_task_t *task, nxt_app_t *app, int i); void nxt_router_listen_event_release(nxt_task_t *task, nxt_listen_event_t *lev, nxt_socket_conf_joint_t *joint); -- cgit From c24c0deb19034726b6450efcd5bce17632177f1b Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Wed, 27 Feb 2019 16:44:52 +0300 Subject: Controller: added "pass" configuration option. --- src/nxt_conf_validation.c | 62 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) (limited to 'src') diff --git a/src/nxt_conf_validation.c b/src/nxt_conf_validation.c index b1e30955..3485b4e3 100644 --- a/src/nxt_conf_validation.c +++ b/src/nxt_conf_validation.c @@ -54,6 +54,8 @@ static nxt_int_t nxt_conf_vldt_listener(nxt_conf_validation_t *vldt, static nxt_int_t nxt_conf_vldt_certificate(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); #endif +static nxt_int_t nxt_conf_vldt_pass(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value, void *data); static nxt_int_t nxt_conf_vldt_app_name(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); static nxt_int_t nxt_conf_vldt_app(nxt_conf_validation_t *vldt, @@ -158,6 +160,11 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_tls_members[] = { static nxt_conf_vldt_object_t nxt_conf_vldt_listener_members[] = { + { nxt_string("pass"), + NXT_CONF_VLDT_STRING, + &nxt_conf_vldt_pass, + NULL }, + { nxt_string("application"), NXT_CONF_VLDT_STRING, &nxt_conf_vldt_app_name, @@ -495,6 +502,61 @@ nxt_conf_vldt_listener(nxt_conf_validation_t *vldt, nxt_str_t *name, } +static nxt_int_t +nxt_conf_vldt_pass(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, + void *data) +{ + u_char *p; + nxt_str_t pass, first, second; + + nxt_conf_get_string(value, &pass); + + p = nxt_memchr(pass.start, '/', pass.length); + + if (p != NULL) { + first.length = p - pass.start; + first.start = pass.start; + + if (pass.length - first.length == 1) { + goto error; + } + + second.length = pass.length - first.length - 1; + second.start = p + 1; + + } else { + first = pass; + second.length = 0; + } + + if (nxt_str_eq(&first, "applications", 12)) { + + if (second.length == 0) { + goto error; + } + + value = nxt_conf_get_object_member(vldt->conf, &first, NULL); + + if (nxt_slow_path(value == NULL)) { + goto error; + } + + value = nxt_conf_get_object_member(value, &second, NULL); + + if (nxt_slow_path(value == NULL)) { + goto error; + } + + return NXT_OK; + } + +error: + + return nxt_conf_vldt_error(vldt, "Request \"pass\" points to invalid " + "location \"%V\".", &pass); +} + + #if (NXT_TLS) static nxt_int_t -- cgit From a881c31abdb9fda2ba0d2e05c45c2ce890a6cfab Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Wed, 27 Feb 2019 16:50:37 +0300 Subject: Controller: added "routes" configuration. --- src/nxt_conf_validation.c | 186 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) (limited to 'src') diff --git a/src/nxt_conf_validation.c b/src/nxt_conf_validation.c index 3485b4e3..655097fd 100644 --- a/src/nxt_conf_validation.c +++ b/src/nxt_conf_validation.c @@ -56,6 +56,16 @@ static nxt_int_t nxt_conf_vldt_certificate(nxt_conf_validation_t *vldt, #endif static nxt_int_t nxt_conf_vldt_pass(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); +static nxt_int_t nxt_conf_vldt_routes(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value, void *data); +static nxt_int_t nxt_conf_vldt_routes_member(nxt_conf_validation_t *vldt, + nxt_str_t *name, nxt_conf_value_t *value); +static nxt_int_t nxt_conf_vldt_route(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value); +static nxt_int_t nxt_conf_vldt_match_patterns(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value, void *data); +static nxt_int_t nxt_conf_vldt_match_pattern(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value); static nxt_int_t nxt_conf_vldt_app_name(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); static nxt_int_t nxt_conf_vldt_app(nxt_conf_validation_t *vldt, @@ -131,6 +141,11 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_root_members[] = { &nxt_conf_vldt_object_iterator, (void *) &nxt_conf_vldt_listener }, + { nxt_string("routes"), + NXT_CONF_VLDT_ARRAY | NXT_CONF_VLDT_OBJECT, + &nxt_conf_vldt_routes, + NULL }, + { nxt_string("applications"), NXT_CONF_VLDT_OBJECT, &nxt_conf_vldt_object_iterator, @@ -183,6 +198,51 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_listener_members[] = { }; +static nxt_conf_vldt_object_t nxt_conf_vldt_match_members[] = { + { nxt_string("method"), + NXT_CONF_VLDT_STRING | NXT_CONF_VLDT_ARRAY, + &nxt_conf_vldt_match_patterns, + NULL }, + + { nxt_string("host"), + NXT_CONF_VLDT_STRING | NXT_CONF_VLDT_ARRAY, + &nxt_conf_vldt_match_patterns, + NULL }, + + { nxt_string("uri"), + NXT_CONF_VLDT_STRING | NXT_CONF_VLDT_ARRAY, + &nxt_conf_vldt_match_patterns, + NULL }, + + NXT_CONF_VLDT_END +}; + + +static nxt_conf_vldt_object_t nxt_conf_vldt_action_members[] = { + { nxt_string("pass"), + NXT_CONF_VLDT_STRING, + &nxt_conf_vldt_pass, + NULL }, + + NXT_CONF_VLDT_END +}; + + +static nxt_conf_vldt_object_t nxt_conf_vldt_route_members[] = { + { nxt_string("match"), + NXT_CONF_VLDT_OBJECT, + &nxt_conf_vldt_object, + (void *) &nxt_conf_vldt_match_members }, + + { nxt_string("action"), + NXT_CONF_VLDT_OBJECT, + &nxt_conf_vldt_object, + (void *) &nxt_conf_vldt_action_members }, + + NXT_CONF_VLDT_END +}; + + static nxt_conf_vldt_object_t nxt_conf_vldt_app_limits_members[] = { { nxt_string("timeout"), NXT_CONF_VLDT_INTEGER, @@ -550,6 +610,34 @@ nxt_conf_vldt_pass(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, return NXT_OK; } + if (nxt_str_eq(&first, "routes", 6)) { + value = nxt_conf_get_object_member(vldt->conf, &first, NULL); + + if (nxt_slow_path(value == NULL)) { + goto error; + } + + if (second.length == 0) { + if (nxt_conf_type(value) != NXT_CONF_ARRAY) { + goto error; + } + + return NXT_OK; + } + + if (nxt_conf_type(value) != NXT_CONF_OBJECT) { + goto error; + } + + value = nxt_conf_get_object_member(value, &second, NULL); + + if (nxt_slow_path(value == NULL)) { + goto error; + } + + return NXT_OK; + } + error: return nxt_conf_vldt_error(vldt, "Request \"pass\" points to invalid " @@ -557,6 +645,104 @@ error: } +static nxt_int_t +nxt_conf_vldt_routes(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, + void *data) +{ + if (nxt_conf_type(value) == NXT_CONF_ARRAY) { + return nxt_conf_vldt_array_iterator(vldt, value, + &nxt_conf_vldt_route); + } + + /* NXT_CONF_OBJECT */ + + return nxt_conf_vldt_object_iterator(vldt, value, + &nxt_conf_vldt_routes_member); +} + + +static nxt_int_t +nxt_conf_vldt_routes_member(nxt_conf_validation_t *vldt, nxt_str_t *name, + nxt_conf_value_t *value) +{ + nxt_int_t ret; + + ret = nxt_conf_vldt_type(vldt, name, value, NXT_CONF_VLDT_ARRAY); + + if (ret != NXT_OK) { + return ret; + } + + return nxt_conf_vldt_array_iterator(vldt, value, &nxt_conf_vldt_route); +} + + +static nxt_int_t +nxt_conf_vldt_route(nxt_conf_validation_t *vldt, nxt_conf_value_t *value) +{ + if (nxt_conf_type(value) != NXT_CONF_OBJECT) { + return nxt_conf_vldt_error(vldt, "The \"routes\" array must contain " + "only object values."); + } + + return nxt_conf_vldt_object(vldt, value, nxt_conf_vldt_route_members); +} + + +static nxt_int_t +nxt_conf_vldt_match_patterns(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value, void *data) +{ + if (nxt_conf_type(value) == NXT_CONF_ARRAY) { + return nxt_conf_vldt_array_iterator(vldt, value, + &nxt_conf_vldt_match_pattern); + } + + /* NXT_CONF_STRING */ + + return nxt_conf_vldt_match_pattern(vldt, value); +} + + +static nxt_int_t +nxt_conf_vldt_match_pattern(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value) +{ + u_char ch; + nxt_str_t pattern; + nxt_uint_t i, first, last; + + if (nxt_conf_type(value) != NXT_CONF_STRING) { + return nxt_conf_vldt_error(vldt, + "The \"match\" patterns must be strings."); + } + + nxt_conf_get_string(value, &pattern); + + if (pattern.length == 0) { + return NXT_OK; + } + + first = (pattern.start[0] == '!'); + last = pattern.length - 1; + + for (i = first; i != pattern.length; i++) { + ch = pattern.start[i]; + + if (ch != '*') { + continue; + } + + if (i != first && i != last) { + return nxt_conf_vldt_error(vldt, "The \"match\" patterns can only " + "contain \"*\" markers at the sides."); + } + } + + return NXT_OK; +} + + #if (NXT_TLS) static nxt_int_t -- cgit From e929d08201aae949db25ae8c1051a91e96ba3011 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Wed, 27 Feb 2019 17:25:07 +0300 Subject: Fixed processing of SERVER_NAME after 77aad2c142a0. Previously, the nxt_router_prepare_msg() function expected server host among other headers unmodified. It's not true anymore since normalization of the Host header has been introduced in 77aad2c142a0. The nxt_unit_split_host() function was removed. It didn't work correctly with IPv6 literals. Anyway, after 77aad2c142a0 the port splitting is done in router while Host header processing. --- src/go/unit/nxt_cgo_lib.c | 7 ++---- src/nxt_application.h | 2 +- src/nxt_http_request.c | 9 ++++++-- src/nxt_php_sapi.c | 31 ++++++-------------------- src/nxt_python_wsgi.c | 29 +++++++----------------- src/nxt_router.c | 24 +++++++++++--------- src/nxt_unit.c | 57 ----------------------------------------------- src/nxt_unit_field.h | 1 - src/nxt_unit_request.h | 3 ++- src/perl/nxt_perl_psgi.c | 30 ++++++------------------- src/ruby/nxt_ruby.c | 28 ++++++----------------- 11 files changed, 54 insertions(+), 167 deletions(-) (limited to 'src') diff --git a/src/go/unit/nxt_cgo_lib.c b/src/go/unit/nxt_cgo_lib.c index 172bef88..98a23482 100644 --- a/src/go/unit/nxt_cgo_lib.c +++ b/src/go/unit/nxt_cgo_lib.c @@ -75,14 +75,11 @@ nxt_cgo_request_handler(nxt_unit_request_info_t *req) nxt_go_request_add_header(go_req, nxt_cgo_str_init(&name, &f->name, f->name_length), nxt_cgo_str_init(&value, &f->value, f->value_length)); - - if (f->hash == NXT_UNIT_HASH_HOST) { - host = value; - } } nxt_go_request_set_content_length(go_req, r->content_length); - nxt_go_request_set_host(go_req, &host); + nxt_go_request_set_host(go_req, + nxt_cgo_str_init(&host, &r->server_name, r->server_name_length)); nxt_go_request_set_remote_addr(go_req, nxt_cgo_str_init(&remote_addr, &r->remote, r->remote_length)); diff --git a/src/nxt_application.h b/src/nxt_application.h index 10f5a922..fe8113c6 100644 --- a/src/nxt_application.h +++ b/src/nxt_application.h @@ -95,13 +95,13 @@ typedef struct { nxt_str_t version; nxt_str_t path; nxt_str_t query; + nxt_str_t server_name; nxt_list_t *fields; nxt_str_t cookie; nxt_str_t content_length; nxt_str_t content_type; - nxt_str_t host; off_t parsed_content_length; nxt_bool_t done; diff --git a/src/nxt_http_request.c b/src/nxt_http_request.c index f6c14df9..724b0808 100644 --- a/src/nxt_http_request.c +++ b/src/nxt_http_request.c @@ -367,6 +367,13 @@ nxt_http_request_application(nxt_task_t *task, nxt_http_request_t *r, ar->r.header.method = *r->method; } + if (r->host.length != 0) { + ar->r.header.server_name = r->host; + + } else { + nxt_str_set(&ar->r.header.server_name, "localhost"); + } + ar->r.header.target = r->target; if (r->path != NULL) { @@ -377,8 +384,6 @@ nxt_http_request_application(nxt_task_t *task, nxt_http_request_t *r, ar->r.header.query = *r->args; } - ar->r.header.host = r->host; - if (r->content_type != NULL) { ar->r.header.content_type.length = r->content_type->value_length; ar->r.header.content_type.start = r->content_type->value; diff --git a/src/nxt_php_sapi.c b/src/nxt_php_sapi.c index 8c25f82a..559a3e37 100644 --- a/src/nxt_php_sapi.c +++ b/src/nxt_php_sapi.c @@ -62,7 +62,7 @@ static void nxt_php_set_sptr(nxt_unit_request_info_t *req, const char *name, 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); + const char *str, uint32_t len, zval *track_vars_array TSRMLS_DC); 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); @@ -846,8 +846,6 @@ nxt_php_read_cookies(TSRMLS_D) static void nxt_php_register_variables(zval *track_vars_array TSRMLS_DC) { - char *host_start, *port_start; - uint32_t host_length, port_length; const char *name; nxt_unit_field_t *f, *f_end; nxt_php_run_ctx_t *ctx; @@ -928,6 +926,10 @@ nxt_php_register_variables(zval *track_vars_array TSRMLS_DC) nxt_php_set_sptr(req, "SERVER_ADDR", &r->local, r->local_length, track_vars_array TSRMLS_CC); + nxt_php_set_sptr(req, "SERVER_NAME", &r->server_name, r->server_name_length, + track_vars_array TSRMLS_CC); + nxt_php_set_cstr(req, "SERVER_PORT", "80", 2, track_vars_array TSRMLS_CC); + f_end = r->fields + r->fields_count; for (f = r->fields; f < f_end; f++) { name = nxt_unit_sptr_get(&f->name); @@ -949,25 +951,6 @@ nxt_php_register_variables(zval *track_vars_array TSRMLS_DC) nxt_php_set_sptr(req, "CONTENT_TYPE", &f->value, f->value_length, track_vars_array TSRMLS_CC); } - - if (r->host_field != NXT_UNIT_NONE_FIELD) { - f = r->fields + r->host_field; - - host_start = nxt_unit_sptr_get(&f->value); - host_length = f->value_length; - - } else { - host_start = NULL; - host_length = 0; - } - - nxt_unit_split_host(host_start, host_length, &host_start, &host_length, - &port_start, &port_length); - - nxt_php_set_cstr(req, "SERVER_NAME", host_start, host_length, - track_vars_array TSRMLS_CC); - nxt_php_set_cstr(req, "SERVER_PORT", port_start, port_length, - track_vars_array TSRMLS_CC); } @@ -997,7 +980,7 @@ nxt_php_set_str(nxt_unit_request_info_t *req, const char *name, static void nxt_php_set_cstr(nxt_unit_request_info_t *req, const char *name, - char *cstr, uint32_t len, zval *track_vars_array TSRMLS_DC) + const char *cstr, uint32_t len, zval *track_vars_array TSRMLS_DC) { if (nxt_slow_path(cstr == NULL)) { return; @@ -1005,7 +988,7 @@ nxt_php_set_cstr(nxt_unit_request_info_t *req, const char *name, nxt_unit_req_debug(req, "php: register %s='%.*s'", name, (int) len, cstr); - php_register_variable_safe((char *) name, cstr, len, + php_register_variable_safe((char *) name, (char *) cstr, len, track_vars_array TSRMLS_CC); } diff --git a/src/nxt_python_wsgi.c b/src/nxt_python_wsgi.c index bd3a2cb2..871c8e91 100644 --- a/src/nxt_python_wsgi.c +++ b/src/nxt_python_wsgi.c @@ -72,7 +72,7 @@ static PyObject *nxt_python_get_environ(nxt_python_run_ctx_t *ctx); static int nxt_python_add_sptr(nxt_python_run_ctx_t *ctx, const char *name, nxt_unit_sptr_t *sptr, uint32_t size); static int nxt_python_add_str(nxt_python_run_ctx_t *ctx, const char *name, - char *str, uint32_t size); + const char *str, uint32_t size); static PyObject *nxt_py_start_resp(PyObject *self, PyObject *args); static int nxt_python_response_add_field(nxt_python_run_ctx_t *ctx, @@ -690,8 +690,8 @@ static PyObject * nxt_python_get_environ(nxt_python_run_ctx_t *ctx) { int rc; - char *name, *host_start, *port_start; - uint32_t i, host_length, port_length; + char *name; + uint32_t i; PyObject *environ; nxt_unit_field_t *f; nxt_unit_request_t *r; @@ -732,6 +732,10 @@ nxt_python_get_environ(nxt_python_run_ctx_t *ctx) RC(nxt_python_add_sptr(ctx, "SERVER_PROTOCOL", &r->version, r->version_length)); + RC(nxt_python_add_sptr(ctx, "SERVER_NAME", &r->server_name, + r->server_name_length)); + RC(nxt_python_add_str(ctx, "SERVER_PORT", "80", 2)); + for (i = 0; i < r->fields_count; i++) { f = r->fields + i; name = nxt_unit_sptr_get(&f->name); @@ -753,23 +757,6 @@ nxt_python_get_environ(nxt_python_run_ctx_t *ctx) f->value_length)); } - if (r->host_field != NXT_UNIT_NONE_FIELD) { - f = r->fields + r->host_field; - - host_start = nxt_unit_sptr_get(&f->value); - host_length = f->value_length; - - } else { - host_start = NULL; - host_length = 0; - } - - nxt_unit_split_host(host_start, host_length, &host_start, &host_length, - &port_start, &port_length); - - RC(nxt_python_add_str(ctx, "SERVER_NAME", host_start, host_length)); - RC(nxt_python_add_str(ctx, "SERVER_PORT", port_start, port_length)); - #undef RC return environ; @@ -818,7 +805,7 @@ nxt_python_add_sptr(nxt_python_run_ctx_t *ctx, const char *name, static int nxt_python_add_str(nxt_python_run_ctx_t *ctx, const char *name, - char *str, uint32_t size) + const char *str, uint32_t size) { PyObject *value; diff --git a/src/nxt_router.c b/src/nxt_router.c index e81a2f96..7cd28416 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -4662,12 +4662,13 @@ nxt_router_prepare_msg(nxt_task_t *task, nxt_app_request_t *r, h = &r->header; req_size = sizeof(nxt_unit_request_t) - + h->method.length + 1 - + h->version.length + 1 - + r->remote.length + 1 - + r->local.length + 1 - + h->target.length + 1 - + (h->path.start != h->target.start ? h->path.length + 1 : 0); + + h->method.length + 1 + + h->version.length + 1 + + r->remote.length + 1 + + r->local.length + 1 + + h->server_name.length + 1 + + h->target.length + 1 + + (h->path.start != h->target.start ? h->path.length + 1 : 0); fields_count = 0; @@ -4722,6 +4723,11 @@ nxt_router_prepare_msg(nxt_task_t *task, nxt_app_request_t *r, p = nxt_cpymem(p, r->local.start, r->local.length); *p++ = '\0'; + req->server_name_length = h->server_name.length; + nxt_unit_sptr_set(&req->server_name, p); + p = nxt_cpymem(p, h->server_name.start, h->server_name.length); + *p++ = '\0'; + target_pos = p; req->target_length = h->target.length; nxt_unit_sptr_set(&req->target, p); @@ -4749,7 +4755,6 @@ nxt_router_prepare_msg(nxt_task_t *task, nxt_app_request_t *r, req->query.offset = 0; } - req->host_field = NXT_UNIT_NONE_FIELD; req->content_length_field = NXT_UNIT_NONE_FIELD; req->content_type_field = NXT_UNIT_NONE_FIELD; req->cookie_field = NXT_UNIT_NONE_FIELD; @@ -4769,10 +4774,7 @@ nxt_router_prepare_msg(nxt_task_t *task, nxt_app_request_t *r, dst_field->name_length = field->name_length + prefix->length; dst_field->value_length = field->value_length; - if (field->value == h->host.start) { - req->host_field = dst_field - req->fields; - - } else if (field->value == h->content_length.start) { + if (field->value == h->content_length.start) { req->content_length_field = dst_field - req->fields; } else if (field->value == h->content_type.start) { diff --git a/src/nxt_unit.c b/src/nxt_unit.c index fa1fb1d5..ec8ba641 100644 --- a/src/nxt_unit.c +++ b/src/nxt_unit.c @@ -889,59 +889,6 @@ nxt_unit_field_hash(const char *name, size_t name_length) } -void -nxt_unit_split_host(char *host, uint32_t host_length, - char **name, uint32_t *name_length, char **port, uint32_t *port_length) -{ - char *cpos; - - static char default_host[] = "localhost"; - static char default_port[] = "80"; - - if (nxt_slow_path(host == NULL || host_length == 0)) { - *name = default_host; - *name_length = nxt_length(default_host); - - *port = default_port; - *port_length = nxt_length(default_port); - - return; - } - - cpos = memchr(host, ':', host_length); - - if (nxt_slow_path(cpos == NULL)) { - *name = host; - *name_length = host_length; - - *port = default_port; - *port_length = nxt_length(default_port); - - return; - } - - if (nxt_slow_path(cpos == host)) { - *name = default_host; - *name_length = nxt_length(default_host); - - } else { - *name = host; - *name_length = cpos - host; - } - - cpos++; - - if (nxt_slow_path(host + host_length == cpos)) { - *port = default_port; - *port_length = nxt_length(default_port); - - } else { - *port = cpos; - *port_length = host_length - (cpos - host); - } -} - - void nxt_unit_request_group_dup_fields(nxt_unit_request_info_t *req) { @@ -957,10 +904,6 @@ nxt_unit_request_group_dup_fields(nxt_unit_request_info_t *req) for (i = 0; i < r->fields_count; i++) { switch (fields[i].hash) { - case NXT_UNIT_HASH_HOST: - r->host_field = i; - break; - case NXT_UNIT_HASH_CONTENT_LENGTH: r->content_length_field = i; break; diff --git a/src/nxt_unit_field.h b/src/nxt_unit_field.h index 0d490f31..d19db0f0 100644 --- a/src/nxt_unit_field.h +++ b/src/nxt_unit_field.h @@ -12,7 +12,6 @@ #include "nxt_unit_sptr.h" enum { - NXT_UNIT_HASH_HOST = 0xE6EB, NXT_UNIT_HASH_CONTENT_LENGTH = 0x1EA0, NXT_UNIT_HASH_CONTENT_TYPE = 0x5F7D, NXT_UNIT_HASH_COOKIE = 0x23F2, diff --git a/src/nxt_unit_request.h b/src/nxt_unit_request.h index af5c29a1..88d569a6 100644 --- a/src/nxt_unit_request.h +++ b/src/nxt_unit_request.h @@ -19,12 +19,12 @@ struct nxt_unit_request_s { uint8_t version_length; uint8_t remote_length; uint8_t local_length; + uint32_t server_name_length; uint32_t target_length; uint32_t path_length; uint32_t query_length; uint32_t fields_count; - uint32_t host_field; uint32_t content_length_field; uint32_t content_type_field; uint32_t cookie_field; @@ -35,6 +35,7 @@ struct nxt_unit_request_s { nxt_unit_sptr_t version; nxt_unit_sptr_t remote; nxt_unit_sptr_t local; + nxt_unit_sptr_t server_name; nxt_unit_sptr_t target; nxt_unit_sptr_t path; nxt_unit_sptr_t query; diff --git a/src/perl/nxt_perl_psgi.c b/src/perl/nxt_perl_psgi.c index da4a3864..a86fd0de 100644 --- a/src/perl/nxt_perl_psgi.c +++ b/src/perl/nxt_perl_psgi.c @@ -68,7 +68,7 @@ static SV *nxt_perl_psgi_env_create(PerlInterpreter *my_perl, nxt_inline int nxt_perl_psgi_add_sptr(PerlInterpreter *my_perl, HV *hash_env, const char *name, uint32_t name_len, nxt_unit_sptr_t *sptr, uint32_t len); nxt_inline int nxt_perl_psgi_add_str(PerlInterpreter *my_perl, HV *hash_env, - const char *name, uint32_t name_len, char *str, uint32_t len); + const char *name, uint32_t name_len, const char *str, uint32_t len); nxt_inline int nxt_perl_psgi_add_value(PerlInterpreter *my_perl, HV *hash_env, const char *name, uint32_t name_len, void *value); @@ -450,8 +450,7 @@ nxt_perl_psgi_env_create(PerlInterpreter *my_perl, { HV *hash_env; AV *array_version; - char *host_start, *port_start; - uint32_t i, host_length, port_length; + uint32_t i; nxt_unit_field_t *f; nxt_unit_request_t *r; @@ -519,6 +518,10 @@ nxt_perl_psgi_env_create(PerlInterpreter *my_perl, RC(nxt_perl_psgi_add_sptr(my_perl, hash_env, NL("SERVER_ADDR"), &r->local, r->local_length)); + RC(nxt_perl_psgi_add_sptr(my_perl, hash_env, NL("SERVER_NAME"), + &r->server_name, r->server_name_length)); + RC(nxt_perl_psgi_add_str(my_perl, hash_env, NL("SERVER_PORT"), "80", 2)); + for (i = 0; i < r->fields_count; i++) { f = r->fields + i; @@ -541,25 +544,6 @@ nxt_perl_psgi_env_create(PerlInterpreter *my_perl, &f->value, f->value_length)); } - if (r->host_field != NXT_UNIT_NONE_FIELD) { - f = r->fields + r->host_field; - - host_start = nxt_unit_sptr_get(&f->value); - host_length = f->value_length; - - } else { - host_start = NULL; - host_length = 0; - } - - nxt_unit_split_host(host_start, host_length, &host_start, &host_length, - &port_start, &port_length); - - RC(nxt_perl_psgi_add_str(my_perl, hash_env, NL("SERVER_NAME"), - host_start, host_length)); - RC(nxt_perl_psgi_add_str(my_perl, hash_env, NL("SERVER_PORT"), - port_start, port_length)); - #undef NL #undef RC @@ -584,7 +568,7 @@ nxt_perl_psgi_add_sptr(PerlInterpreter *my_perl, HV *hash_env, nxt_inline int nxt_perl_psgi_add_str(PerlInterpreter *my_perl, HV *hash_env, - const char *name, uint32_t name_len, char *str, uint32_t len) + const char *name, uint32_t name_len, const char *str, uint32_t len) { SV **ha; diff --git a/src/ruby/nxt_ruby.c b/src/ruby/nxt_ruby.c index a08b8189..d099a338 100644 --- a/src/ruby/nxt_ruby.c +++ b/src/ruby/nxt_ruby.c @@ -45,7 +45,7 @@ static int nxt_ruby_read_request(VALUE hash_env); nxt_inline void nxt_ruby_add_sptr(VALUE hash_env, const char *name, uint32_t name_len, nxt_unit_sptr_t *sptr, uint32_t len); nxt_inline void nxt_ruby_add_str(VALUE hash_env, - const char *name, uint32_t name_len, char *str, uint32_t len); + const char *name, uint32_t name_len, const char *str, uint32_t len); static nxt_int_t nxt_ruby_rack_result_status(VALUE result); static int nxt_ruby_rack_result_headers(VALUE result, nxt_int_t status); static int nxt_ruby_hash_info(VALUE r_key, VALUE r_value, VALUE arg); @@ -428,8 +428,7 @@ fail: static int nxt_ruby_read_request(VALUE hash_env) { - char *host_start, *port_start; - uint32_t i, host_length, port_length; + uint32_t i; nxt_unit_field_t *f; nxt_unit_request_t *r; @@ -452,6 +451,10 @@ nxt_ruby_read_request(VALUE hash_env) r->remote_length); nxt_ruby_add_sptr(hash_env, NL("SERVER_ADDR"), &r->local, r->local_length); + nxt_ruby_add_sptr(hash_env, NL("SERVER_NAME"), &r->server_name, + r->server_name_length); + nxt_ruby_add_str(hash_env, NL("SERVER_PORT"), "80", 2); + for (i = 0; i < r->fields_count; i++) { f = r->fields + i; @@ -473,23 +476,6 @@ nxt_ruby_read_request(VALUE hash_env) &f->value, f->value_length); } - if (r->host_field != NXT_UNIT_NONE_FIELD) { - f = r->fields + r->host_field; - - host_start = nxt_unit_sptr_get(&f->value); - host_length = f->value_length; - - } else { - host_start = NULL; - host_length = 0; - } - - nxt_unit_split_host(host_start, host_length, &host_start, &host_length, - &port_start, &port_length); - - nxt_ruby_add_str(hash_env, NL("SERVER_NAME"), host_start, host_length); - nxt_ruby_add_str(hash_env, NL("SERVER_PORT"), port_start, port_length); - #undef NL return NXT_UNIT_OK; @@ -510,7 +496,7 @@ nxt_ruby_add_sptr(VALUE hash_env, nxt_inline void nxt_ruby_add_str(VALUE hash_env, - const char *name, uint32_t name_len, char *str, uint32_t len) + const char *name, uint32_t name_len, const char *str, uint32_t len) { rb_hash_aset(hash_env, rb_str_new(name, name_len), rb_str_new(str, len)); } -- cgit From 379e4c75fdf99439e481f739324d9c6d6d1b18ab Mon Sep 17 00:00:00 2001 From: Alexander Borisov Date: Wed, 27 Feb 2019 17:27:41 +0300 Subject: Perl: added processing for IO:Handle-like object. The application can return the body as an IO:Handle-like object without file descriptor. --- src/perl/nxt_perl_psgi.c | 112 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 109 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/perl/nxt_perl_psgi.c b/src/perl/nxt_perl_psgi.c index a86fd0de..275ed913 100644 --- a/src/perl/nxt_perl_psgi.c +++ b/src/perl/nxt_perl_psgi.c @@ -51,6 +51,8 @@ static void nxt_perl_psgi_xs_init(pTHX); static SV *nxt_perl_psgi_call_var_application(PerlInterpreter *my_perl, SV *env, SV *app, nxt_unit_request_info_t *req); +static SV *nxt_perl_psgi_call_method(PerlInterpreter *my_perl, SV *obj, + const char *method, nxt_unit_request_info_t *req); /* For currect load XS modules */ EXTERN_C void boot_DynaLoader(pTHX_ CV *cv); @@ -84,8 +86,10 @@ static int nxt_perl_psgi_result_body(PerlInterpreter *my_perl, SV *result, nxt_unit_request_info_t *req); static int nxt_perl_psgi_result_body_ref(PerlInterpreter *my_perl, SV *sv_body, nxt_unit_request_info_t *req); -static ssize_t nxt_perl_psgi_io_read(nxt_unit_read_info_t *read_info, - void *dst, size_t size); +static int nxt_perl_psgi_result_body_fh(PerlInterpreter *my_perl, SV *sv_body, + nxt_unit_request_info_t *req); +static ssize_t nxt_perl_psgi_io_read(nxt_unit_read_info_t *read_info, void *dst, + size_t size); static int nxt_perl_psgi_result_array(PerlInterpreter *my_perl, SV *result, nxt_unit_request_info_t *req); @@ -251,6 +255,42 @@ nxt_perl_psgi_call_var_application(PerlInterpreter *my_perl, } +static SV * +nxt_perl_psgi_call_method(PerlInterpreter *my_perl, SV *obj, const char *method, + nxt_unit_request_info_t *req) +{ + SV *result; + + dSP; + + ENTER; + SAVETMPS; + + PUSHMARK(sp); + XPUSHs(obj); + PUTBACK; + + call_method(method, G_EVAL|G_SCALAR); + + SPAGAIN; + + if (SvTRUE(ERRSV)) { + nxt_unit_req_error(req, "PSGI: Failed to call method '%s':\n%s", + method, SvPV_nolen(ERRSV)); + result = NULL; + + } else { + result = SvREFCNT_inc(POPs); + } + + PUTBACK; + FREETMPS; + LEAVE; + + return result; +} + + static u_char * nxt_perl_psgi_module_create(nxt_task_t *task, const char *script) { @@ -749,6 +789,68 @@ nxt_perl_psgi_result_body(PerlInterpreter *my_perl, SV *sv_body, } +static int +nxt_perl_psgi_result_body_ref(PerlInterpreter *my_perl, SV *sv_body, + nxt_unit_request_info_t *req) +{ + SV *data, *old_rs, *old_perl_rs; + int rc; + size_t len; + const char *body; + + /* + * Servers should set the $/ special variable to the buffer size + * when reading content from $body using the getline method. + * This is done by setting $/ with a reference to an integer ($/ = \8192). + */ + + old_rs = PL_rs; + old_perl_rs = get_sv("/", GV_ADD); + + PL_rs = sv_2mortal(newRV_noinc(newSViv(nxt_unit_buf_min()))); + + sv_setsv(old_perl_rs, PL_rs); + + rc = NXT_UNIT_OK; + + for ( ;; ) { + data = nxt_perl_psgi_call_method(my_perl, sv_body, "getline", req); + if (nxt_slow_path(data == NULL)) { + rc = NXT_UNIT_ERROR; + break; + } + + body = SvPV(data, len); + + if (len == 0) { + SvREFCNT_dec(data); + + data = nxt_perl_psgi_call_method(my_perl, sv_body, "close", req); + if (nxt_fast_path(data != NULL)) { + SvREFCNT_dec(data); + } + + break; + } + + rc = nxt_unit_response_write(req, body, len); + + SvREFCNT_dec(data); + + if (nxt_slow_path(rc != NXT_UNIT_OK)) { + nxt_unit_req_error(req, "PSGI: Failed to write content from " + "Perl Application"); + break; + } + }; + + PL_rs = old_rs; + sv_setsv(get_sv("/", GV_ADD), old_perl_rs); + + return rc; +} + + typedef struct { PerlInterpreter *my_perl; PerlIO *fp; @@ -756,7 +858,7 @@ typedef struct { static int -nxt_perl_psgi_result_body_ref(PerlInterpreter *my_perl, SV *sv_body, +nxt_perl_psgi_result_body_fh(PerlInterpreter *my_perl, SV *sv_body, nxt_unit_request_info_t *req) { IO *io; @@ -866,6 +968,10 @@ nxt_perl_psgi_result_array(PerlInterpreter *my_perl, SV *result, return nxt_perl_psgi_result_body(my_perl, *sv_temp, req); } + if (SvTYPE(SvRV(*sv_temp)) == SVt_PVGV) { + return nxt_perl_psgi_result_body_fh(my_perl, *sv_temp, req); + } + return nxt_perl_psgi_result_body_ref(my_perl, *sv_temp, req); } -- cgit From 0c917b4d348cc9ce78a6d4ad7bd4bddde739f402 Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Thu, 28 Feb 2019 13:39:31 +0300 Subject: Reusing fragmented message buffers. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fragmented message non-mmap buffer chain not freed nor reused before this fix. This closes #206 on GitHub. Thanks to 洪志道 (Hong Zhi Dao). --- src/nxt_port_socket.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nxt_port_socket.c b/src/nxt_port_socket.c index a545013f..aed3a292 100644 --- a/src/nxt_port_socket.c +++ b/src/nxt_port_socket.c @@ -799,6 +799,14 @@ nxt_port_read_msg_process(nxt_task_t *task, nxt_port_t *port, msg->buf = fmsg->buf; msg->fd = fmsg->fd; + + /* + * To disable instant completion or buffer re-usage, + * handler should reset 'msg.buf'. + */ + if (!msg->port_msg.mmap && msg->buf == b) { + nxt_port_buf_free(port, b); + } } } @@ -895,7 +903,7 @@ nxt_port_buf_alloc(nxt_port_t *port) static void nxt_port_buf_free(nxt_port_t *port, nxt_buf_t *b) { - b->next = port->free_bufs; + nxt_buf_chain_add(&b, port->free_bufs); port->free_bufs = b; } -- cgit From b92dc6036cceeecf36d34a3792487adade92d8b6 Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Thu, 28 Feb 2019 14:37:39 +0300 Subject: Removing app data debug message. This message produces too many noise in log and complicates analysis. --- src/nxt_router.c | 4 ---- 1 file changed, 4 deletions(-) (limited to 'src') diff --git a/src/nxt_router.c b/src/nxt_router.c index 7cd28416..51c84df9 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -3393,10 +3393,6 @@ nxt_router_response_ready_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, dump_size = 300; } - nxt_debug(task, "%srouter app data (%uz): %*s", - msg->port_msg.last ? "last " : "", msg->size, dump_size, - b->mem.pos); - if (msg->size == 0) { b = NULL; } -- cgit From ec7319d32c9c41597a99a9422ff324c97a92bb21 Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Thu, 28 Feb 2019 15:52:36 +0300 Subject: Various libunit fixes. This patch contains various logging improvements and bugfixes found during Java module development. --- src/nxt_unit.c | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/nxt_unit.c b/src/nxt_unit.c index ec8ba641..6339aec5 100644 --- a/src/nxt_unit.c +++ b/src/nxt_unit.c @@ -1047,8 +1047,11 @@ nxt_unit_response_realloc(nxt_unit_request_info_t *req, + max_fields_count * sizeof(nxt_unit_field_t) + max_fields_size; + nxt_unit_req_debug(req, "realloc %"PRIu32"", buf_size); + buf = nxt_unit_response_buf_alloc(req, buf_size); if (nxt_slow_path(buf == NULL)) { + nxt_unit_req_warn(req, "realloc: new buf allocation failed"); return NXT_UNIT_ERROR; } @@ -1063,23 +1066,29 @@ nxt_unit_response_realloc(nxt_unit_request_info_t *req, f = resp->fields; for (i = 0; i < req->response->fields_count; i++) { - src = req->request->fields + i; + src = req->response->fields + i; if (nxt_slow_path(src->skip != 0)) { continue; } - if (nxt_slow_path(src->name_length + src->value_length + if (nxt_slow_path(src->name_length + src->value_length + 2 > (uint32_t) (buf->end - p))) { + nxt_unit_req_warn(req, "realloc: not enough space for field" + " #%"PRIu32" (%p), (%"PRIu32" + %"PRIu32") required", + i, src, src->name_length, src->value_length); + goto fail; } nxt_unit_sptr_set(&f->name, p); p = nxt_cpymem(p, nxt_unit_sptr_get(&src->name), src->name_length); + *p++ = '\0'; nxt_unit_sptr_set(&f->value, p); p = nxt_cpymem(p, nxt_unit_sptr_get(&src->value), src->value_length); + *p++ = '\0'; f->hash = src->hash; f->skip = 0; @@ -1094,6 +1103,10 @@ nxt_unit_response_realloc(nxt_unit_request_info_t *req, if (nxt_slow_path(req->response->piggyback_content_length > (uint32_t) (buf->end - p))) { + nxt_unit_req_warn(req, "realloc: not enought space for content" + " #%"PRIu32", %"PRIu32" required", + i, req->response->piggyback_content_length); + goto fail; } @@ -1162,7 +1175,7 @@ nxt_unit_response_add_field(nxt_unit_request_info_t *req, buf = req->response_buf; - if (nxt_slow_path(name_length + value_length + if (nxt_slow_path(name_length + value_length + 2 > (uint32_t) (buf->end - buf->free))) { nxt_unit_req_warn(req, "add_field: response buffer overflow"); @@ -1179,9 +1192,11 @@ nxt_unit_response_add_field(nxt_unit_request_info_t *req, nxt_unit_sptr_set(&f->name, buf->free); buf->free = nxt_cpymem(buf->free, name, name_length); + *buf->free++ = '\0'; nxt_unit_sptr_set(&f->value, buf->free); buf->free = nxt_cpymem(buf->free, value, value_length); + *buf->free++ = '\0'; f->hash = nxt_unit_field_hash(name, name_length); f->skip = 0; @@ -1565,7 +1580,9 @@ nxt_unit_buf_next(nxt_unit_buf_t *buf) lnk = &mmap_buf->link; - if (lnk == nxt_queue_last(&req_impl->incoming_buf)) { + if (lnk == nxt_queue_last(&req_impl->incoming_buf) + || lnk == nxt_queue_last(&req_impl->outgoing_buf)) + { return NULL; } @@ -1710,6 +1727,9 @@ nxt_unit_response_write_cb(nxt_unit_request_info_t *req, } while (!read_info->eof) { + nxt_unit_req_debug(req, "write_cb, alloc %"PRIu32"", + read_info->buf_size); + buf = nxt_unit_response_buf_alloc(req, nxt_min(read_info->buf_size, PORT_MMAP_DATA_SIZE)); if (nxt_slow_path(buf == NULL)) { @@ -2449,14 +2469,12 @@ nxt_unit_mmap_read(nxt_unit_ctx_t *ctx, nxt_unit_recv_msg_t *recv_msg, b->buf.end = b->buf.start + size; b->hdr = hdr; - nxt_unit_debug(ctx, "#%"PRIu32": mmap_read: [%p,%d] %d->%d,(%d,%d,%d)\n" - "%.*s", + nxt_unit_debug(ctx, "#%"PRIu32": mmap_read: [%p,%d] %d->%d,(%d,%d,%d)", recv_msg->port_msg.stream, start, (int) size, (int) hdr->src_pid, (int) hdr->dst_pid, (int) hdr->id, (int) mmap_msg->chunk_id, - (int) mmap_msg->size, - (int) size, (char *) start); + (int) mmap_msg->size); } pthread_mutex_unlock(&process->incoming.mutex); -- cgit From 5bfdebb9e4161a689113d73775498949a09d7fb5 Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Thu, 28 Feb 2019 18:02:42 +0300 Subject: Introducing Java Servlet Container beta. --- src/java/nginx/unit/Context.java | 3502 +++++++++++++++++++++ src/java/nginx/unit/DynamicDispatcherRequest.java | 8 + src/java/nginx/unit/DynamicPathRequest.java | 15 + src/java/nginx/unit/ForwardRequestWrapper.java | 150 + src/java/nginx/unit/HeaderNamesEnumeration.java | 42 + src/java/nginx/unit/HeadersEnumeration.java | 40 + src/java/nginx/unit/IncludeRequestWrapper.java | 88 + src/java/nginx/unit/IncludeResponseWrapper.java | 117 + src/java/nginx/unit/InitParams.java | 7 + src/java/nginx/unit/InputStream.java | 90 + src/java/nginx/unit/JspPropertyGroup.java | 169 + src/java/nginx/unit/OutputStream.java | 68 + src/java/nginx/unit/Request.java | 1123 +++++++ src/java/nginx/unit/RequestAttrProxy.java | 40 + src/java/nginx/unit/Response.java | 817 +++++ src/java/nginx/unit/Session.java | 251 ++ src/java/nginx/unit/SessionAttrProxy.java | 40 + src/java/nginx/unit/Taglib.java | 44 + src/java/nginx/unit/UnitSessionCookieConfig.java | 110 + src/java/nxt_jni.c | 175 + src/java/nxt_jni.h | 55 + src/java/nxt_jni_Context.c | 164 + src/java/nxt_jni_Context.h | 23 + src/java/nxt_jni_HeaderNamesEnumeration.c | 153 + src/java/nxt_jni_HeaderNamesEnumeration.h | 19 + src/java/nxt_jni_HeadersEnumeration.c | 148 + src/java/nxt_jni_HeadersEnumeration.h | 19 + src/java/nxt_jni_InputStream.c | 230 ++ src/java/nxt_jni_InputStream.h | 15 + src/java/nxt_jni_OutputStream.c | 236 ++ src/java/nxt_jni_OutputStream.h | 17 + src/java/nxt_jni_Request.c | 658 ++++ src/java/nxt_jni_Request.h | 18 + src/java/nxt_jni_Response.c | 1105 +++++++ src/java/nxt_jni_Response.h | 18 + src/java/nxt_jni_Thread.c | 94 + src/java/nxt_jni_Thread.h | 20 + src/java/nxt_jni_URLClassLoader.c | 187 ++ src/java/nxt_jni_URLClassLoader.h | 27 + src/nxt_application.c | 14 + src/nxt_application.h | 12 + src/nxt_conf.h | 2 +- src/nxt_conf_validation.c | 71 + src/nxt_external.c | 1 + src/nxt_java.c | 462 +++ src/nxt_main_process.c | 26 + src/nxt_php_sapi.c | 1 + src/nxt_python_wsgi.c | 1 + src/nxt_router.c | 1 + src/perl/nxt_perl_psgi.c | 1 + src/ruby/nxt_ruby.c | 1 + 51 files changed, 10694 insertions(+), 1 deletion(-) create mode 100644 src/java/nginx/unit/Context.java create mode 100644 src/java/nginx/unit/DynamicDispatcherRequest.java create mode 100644 src/java/nginx/unit/DynamicPathRequest.java create mode 100644 src/java/nginx/unit/ForwardRequestWrapper.java create mode 100644 src/java/nginx/unit/HeaderNamesEnumeration.java create mode 100644 src/java/nginx/unit/HeadersEnumeration.java create mode 100644 src/java/nginx/unit/IncludeRequestWrapper.java create mode 100644 src/java/nginx/unit/IncludeResponseWrapper.java create mode 100644 src/java/nginx/unit/InitParams.java create mode 100644 src/java/nginx/unit/InputStream.java create mode 100644 src/java/nginx/unit/JspPropertyGroup.java create mode 100644 src/java/nginx/unit/OutputStream.java create mode 100644 src/java/nginx/unit/Request.java create mode 100644 src/java/nginx/unit/RequestAttrProxy.java create mode 100644 src/java/nginx/unit/Response.java create mode 100644 src/java/nginx/unit/Session.java create mode 100644 src/java/nginx/unit/SessionAttrProxy.java create mode 100644 src/java/nginx/unit/Taglib.java create mode 100644 src/java/nginx/unit/UnitSessionCookieConfig.java create mode 100644 src/java/nxt_jni.c create mode 100644 src/java/nxt_jni.h create mode 100644 src/java/nxt_jni_Context.c create mode 100644 src/java/nxt_jni_Context.h create mode 100644 src/java/nxt_jni_HeaderNamesEnumeration.c create mode 100644 src/java/nxt_jni_HeaderNamesEnumeration.h create mode 100644 src/java/nxt_jni_HeadersEnumeration.c create mode 100644 src/java/nxt_jni_HeadersEnumeration.h create mode 100644 src/java/nxt_jni_InputStream.c create mode 100644 src/java/nxt_jni_InputStream.h create mode 100644 src/java/nxt_jni_OutputStream.c create mode 100644 src/java/nxt_jni_OutputStream.h create mode 100644 src/java/nxt_jni_Request.c create mode 100644 src/java/nxt_jni_Request.h create mode 100644 src/java/nxt_jni_Response.c create mode 100644 src/java/nxt_jni_Response.h create mode 100644 src/java/nxt_jni_Thread.c create mode 100644 src/java/nxt_jni_Thread.h create mode 100644 src/java/nxt_jni_URLClassLoader.c create mode 100644 src/java/nxt_jni_URLClassLoader.h create mode 100644 src/nxt_java.c (limited to 'src') diff --git a/src/java/nginx/unit/Context.java b/src/java/nginx/unit/Context.java new file mode 100644 index 00000000..643a336b --- /dev/null +++ b/src/java/nginx/unit/Context.java @@ -0,0 +1,3502 @@ +package nginx.unit; + +import io.github.classgraph.ClassGraph; +import io.github.classgraph.ClassInfo; +import io.github.classgraph.ClassInfoList; +import io.github.classgraph.ScanResult; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; + +import java.lang.ClassLoader; +import java.lang.ClassNotFoundException; +import java.lang.IllegalArgumentException; +import java.lang.IllegalStateException; +import java.lang.reflect.Constructor; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLClassLoader; + +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.BasicFileAttributes; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Enumeration; +import java.util.EventListener; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.Set; +import java.util.UUID; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarInputStream; +import java.util.zip.ZipEntry; + +import javax.servlet.DispatcherType; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.FilterRegistration.Dynamic; +import javax.servlet.FilterRegistration; +import javax.servlet.MultipartConfigElement; +import javax.servlet.Registration; +import javax.servlet.RequestDispatcher; +import javax.servlet.Servlet; +import javax.servlet.ServletConfig; +import javax.servlet.ServletContainerInitializer; +import javax.servlet.ServletContext; +import javax.servlet.ServletContextAttributeEvent; +import javax.servlet.ServletContextAttributeListener; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.ServletRegistration; +import javax.servlet.ServletResponse; +import javax.servlet.ServletRequest; +import javax.servlet.ServletRequestAttributeEvent; +import javax.servlet.ServletRequestAttributeListener; +import javax.servlet.ServletRequestEvent; +import javax.servlet.ServletRequestListener; +import javax.servlet.ServletSecurityElement; +import javax.servlet.SessionCookieConfig; +import javax.servlet.SessionTrackingMode; +import javax.servlet.annotation.HandlesTypes; +import javax.servlet.annotation.WebInitParam; +import javax.servlet.annotation.WebServlet; +import javax.servlet.annotation.WebFilter; +import javax.servlet.annotation.WebListener; +import javax.servlet.descriptor.JspConfigDescriptor; +import javax.servlet.descriptor.JspPropertyGroupDescriptor; +import javax.servlet.descriptor.TaglibDescriptor; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSessionAttributeListener; +import javax.servlet.http.HttpSessionBindingEvent; +import javax.servlet.http.HttpSessionEvent; +import javax.servlet.http.HttpSessionIdListener; +import javax.servlet.http.HttpSessionListener; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.eclipse.jetty.http.MimeTypes; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import org.apache.jasper.servlet.JspServlet; +import org.apache.jasper.servlet.JasperInitializer; + + +public class Context implements ServletContext, InitParams +{ + public final static int SERVLET_MAJOR_VERSION = 3; + public final static int SERVLET_MINOR_VERSION = 1; + + private String context_path_ = ""; + private String server_info_ = "unit"; + private String app_version_ = ""; + private MimeTypes mime_types_; + private boolean metadata_complete_ = false; + private boolean welcome_files_list_found_ = false; + private boolean ctx_initialized_ = false; + + private ClassLoader loader_; + private File webapp_; + private File extracted_dir_; + private File temp_dir_; + + private final Map init_params_ = new HashMap<>(); + private final Map attributes_ = new HashMap<>(); + + private final Map parsed_patterns_ = new HashMap<>(); + + private final List filters_ = new ArrayList<>(); + private final Map name2filter_ = new HashMap<>(); + private final List filter_maps_ = new ArrayList<>(); + + private final List servlets_ = new ArrayList<>(); + private final Map name2servlet_ = new HashMap<>(); + private final Map pattern2servlet_ = new HashMap<>(); + private final Map exact2servlet_ = new HashMap<>(); + private final List prefix_patterns_ = new ArrayList<>(); + private final Map suffix2servlet_ = new HashMap<>(); + private ServletReg default_servlet_; + private ServletReg system_default_servlet_; + + private final List welcome_files_ = new ArrayList<>(); + + private final Map exception2location_ = new HashMap<>(); + private final Map error2location_ = new HashMap<>(); + + public static final Class[] LISTENER_TYPES = new Class[] { + ServletContextListener.class, + ServletContextAttributeListener.class, + ServletRequestListener.class, + ServletRequestAttributeListener.class, + HttpSessionAttributeListener.class, + HttpSessionIdListener.class, + HttpSessionListener.class + }; + + private final List pending_listener_classnames_ = new ArrayList<>(); + private final Set listener_classnames_ = new HashSet<>(); + + private final List ctx_listeners_ = new ArrayList<>(); + private final List destroy_listeners_ = new ArrayList<>(); + private final List ctx_attr_listeners_ = new ArrayList<>(); + private final List req_init_listeners_ = new ArrayList<>(); + private final List req_destroy_listeners_ = new ArrayList<>(); + private final List req_attr_listeners_ = new ArrayList<>(); + + private ServletRequestAttributeListener req_attr_proxy_ = null; + + private final List sess_attr_listeners_ = new ArrayList<>(); + private final List sess_id_listeners_ = new ArrayList<>(); + private final List sess_listeners_ = new ArrayList<>(); + + private HttpSessionAttributeListener sess_attr_proxy_ = null; + + private final SessionCookieConfig session_cookie_config_ = new UnitSessionCookieConfig(); + private final Set default_session_tracking_modes_ = new HashSet<>(); + private Set session_tracking_modes_ = default_session_tracking_modes_; + private int session_timeout_ = 60; + + private final Map sessions_ = new HashMap<>(); + + private static final String WEB_INF = "WEB-INF/"; + private static final String WEB_INF_CLASSES = WEB_INF + "classes/"; + private static final String WEB_INF_LIB = WEB_INF + "lib/"; + + private class PrefixPattern implements Comparable + { + public final String pattern; + public final ServletReg servlet; + + public PrefixPattern(String p, ServletReg s) + { + pattern = p; + servlet = s; + } + + public boolean match(String url) + { + return url.startsWith(pattern) && ( + url.length() == pattern.length() + || url.charAt(pattern.length()) == '/'); + } + + @Override + public int compareTo(PrefixPattern p) + { + return p.pattern.length() - pattern.length(); + } + } + + private class StaticServlet extends HttpServlet + { + @Override + public void doPost(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + doGet(request, response); + } + + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + String path = null; + + if (request.getDispatcherType() == DispatcherType.INCLUDE) { + path = (String) request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH); + } + + if (path == null) { + path = request.getServletPath(); + } + + /* + 10.6 Web Application Archive File + ... + This directory [META-INF] must not be directly served as + content by the container in response to a Web client's request, + though its contents are visible to servlet code via the + getResource and getResourceAsStream calls on the + ServletContext. Also, any requests to access the resources in + META-INF directory must be returned with a SC_NOT_FOUND(404) + response. + */ + if (request.getDispatcherType() == DispatcherType.REQUEST + && (path.equals("/WEB-INF") || path.startsWith("/WEB-INF/") + || path.equals("/META-INF") || path.startsWith("/META-INF/"))) + { + response.sendError(response.SC_NOT_FOUND); + return; + } + + if (path.startsWith("/")) { + path = path.substring(1); + } + + File f = new File(webapp_, path); + if (!f.exists()) { + if (request.getDispatcherType() == DispatcherType.INCLUDE) { + /* + 9.3 The Include Method + ... + If the default servlet is the target of a + RequestDispatch.include() and the requested resource + does not exist, then the default servlet MUST throw + FileNotFoundException. + */ + + throw new FileNotFoundException(); + } + + response.sendError(response.SC_NOT_FOUND); + return; + } + + long ims = request.getDateHeader("If-Modified-Since"); + long lm = f.lastModified(); + + if (lm < ims) { + response.sendError(response.SC_NOT_MODIFIED); + return; + } + + response.setDateHeader("Last-Modified", f.lastModified()); + + if (f.isDirectory()) { + String url = request.getRequestURL().toString(); + if (!url.endsWith("/")) { + response.setHeader("Location", url + "/"); + response.sendError(response.SC_FOUND); + return; + } + + String[] ls = f.list(); + + PrintWriter writer = response.getWriter(); + + for (String n : ls) { + writer.println("" + n + "
"); + } + + writer.close(); + + } else { + response.setContentLengthLong(f.length()); + + InputStream is = new FileInputStream(f); + byte[] buffer = new byte[response.getBufferSize()]; + ServletOutputStream os = response.getOutputStream(); + while (true) { + int read = is.read(buffer); + if (read == -1) { + break; + } + os.write(buffer, 0, read); + } + + os.close(); + } + } + } + + public static Context start(String webapp, URL[] classpaths) + throws Exception + { + Context ctx = new Context(); + + ctx.loadApp(webapp, classpaths); + ctx.initialized(); + + return ctx; + } + + public Context() + { + default_session_tracking_modes_.add(SessionTrackingMode.COOKIE); + + context_path_ = System.getProperty("nginx.unit.context.path", "").trim(); + + if (context_path_.endsWith("/")) { + context_path_ = context_path_.substring(0, context_path_.length() - 1); + } + + if (!context_path_.isEmpty() && !context_path_.startsWith("/")) { + context_path_ = "/" + context_path_; + } + + if (context_path_.isEmpty()) { + session_cookie_config_.setPath("/"); + } else { + session_cookie_config_.setPath(context_path_); + } + } + + public void loadApp(String webapp, URL[] classpaths) + throws Exception + { + File root = new File(webapp); + if (!root.exists()) { + throw new FileNotFoundException( + "Unable to determine code source archive from " + root); + } + + ArrayList url_list = new ArrayList<>(); + + for (URL u : classpaths) { + url_list.add(u); + } + + if (!root.isDirectory()) { + root = extractWar(root); + extracted_dir_ = root; + } + + webapp_ = root; + + Path tmpDir = Files.createTempDirectory("webapp"); + temp_dir_ = tmpDir.toFile(); + setAttribute(ServletContext.TEMPDIR, temp_dir_); + + File web_inf_classes = new File(root, WEB_INF_CLASSES); + if (web_inf_classes.exists() && web_inf_classes.isDirectory()) { + url_list.add(new URL("file:" + root.getAbsolutePath() + "/" + WEB_INF_CLASSES)); + } + + File lib = new File(root, WEB_INF_LIB); + File[] libs = lib.listFiles(); + + if (libs != null) { + for (File l : libs) { + url_list.add(new URL("file:" + l.getAbsolutePath())); + } + } + + URL[] urls = new URL[url_list.size()]; + + for (int i = 0; i < url_list.size(); i++) { + urls[i] = url_list.get(i); + trace("archives: " + urls[i]); + } + + String custom_listener = System.getProperty("nginx.unit.context.listener", "").trim(); + if (!custom_listener.isEmpty()) { + pending_listener_classnames_.add(custom_listener); + } + + processWebXml(root); + + loader_ = new AppClassLoader(urls, + Context.class.getClassLoader().getParent()); + + ClassLoader old = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(loader_); + + try { + for (String listener_classname : pending_listener_classnames_) { + addListener(listener_classname); + } + + ScanResult scan_res = null; + + if (!metadata_complete_) { + ClassGraph classgraph = new ClassGraph() + //.verbose() + .overrideClassLoaders(loader_) + .ignoreParentClassLoaders() + .enableClassInfo() + .enableAnnotationInfo() + //.enableSystemPackages() + .whitelistModules("javax.*") + //.enableAllInfo() + ; + + String verbose = System.getProperty("nginx.unit.context.classgraph.verbose", "").trim(); + + if (verbose.equals("true")) { + classgraph.verbose(); + } + + scan_res = classgraph.scan(); + + loadInitializers(scan_res); + } + + if (!metadata_complete_) { + scanClasses(scan_res); + } + + /* + 8.1.6 Other annotations / conventions + ... + By default all applications will have index.htm(l) and index.jsp + in the list of welcome-file-list. The descriptor may to be used + to override these default settings. + */ + if (!welcome_files_list_found_) { + welcome_files_.add("index.htm"); + welcome_files_.add("index.html"); + welcome_files_.add("index.jsp"); + } + + ServletReg jsp_servlet = name2servlet_.get("jsp"); + if (jsp_servlet == null) { + jsp_servlet = new ServletReg("jsp", JspServlet.class); + jsp_servlet.system_jsp_servlet_ = true; + servlets_.add(jsp_servlet); + name2servlet_.put("jsp", jsp_servlet); + } + + if (jsp_servlet.getClassName() == null) { + jsp_servlet.setClass(JspServlet.class); + jsp_servlet.system_jsp_servlet_ = true; + } + + if (jsp_servlet.patterns_.isEmpty()) { + parseURLPattern("*.jsp", jsp_servlet); + parseURLPattern("*.jspx", jsp_servlet); + } + + ServletReg def_servlet = name2servlet_.get("default"); + if (def_servlet == null) { + def_servlet = new ServletReg("default", new StaticServlet()); + def_servlet.servlet_ = new StaticServlet(); + servlets_.add(def_servlet); + name2servlet_.put("default", def_servlet); + } + + if (def_servlet.getClassName() == null) { + def_servlet.setClass(StaticServlet.class); + def_servlet.servlet_ = new StaticServlet(); + } + + system_default_servlet_ = def_servlet; + + for (PrefixPattern p : prefix_patterns_) { + /* + Optimization: add prefix patterns to exact2servlet_ map. + This should not affect matching result because full path + is the longest matched prefix. + */ + if (!exact2servlet_.containsKey(p.pattern)) { + trace("adding prefix pattern " + p.pattern + " to exact patterns map"); + exact2servlet_.put(p.pattern, p.servlet); + } + } + + Collections.sort(prefix_patterns_); + } finally { + Thread.currentThread().setContextClassLoader(old); + } + } + + private static class AppClassLoader extends URLClassLoader + { + static { + ClassLoader.registerAsParallelCapable(); + } + + private final static String[] system_prefix = + { + "java/", // Java SE classes (per servlet spec v2.5 / SRV.9.7.2) + "javax/", // Java SE classes (per servlet spec v2.5 / SRV.9.7.2) + "org/w3c/", // needed by javax.xml + "org/xml/", // needed by javax.xml + }; + + private ClassLoader system_loader; + + public AppClassLoader(URL[] urls, ClassLoader parent) + { + super(urls, parent); + + ClassLoader j = String.class.getClassLoader(); + if (j == null) { + j = getSystemClassLoader(); + while (j.getParent() != null) { + j = j.getParent(); + } + } + system_loader = j; + } + + private boolean isSystemPath(String path) + { + int i = Arrays.binarySearch(system_prefix, path); + + if (i >= 0) { + return true; + } + + i = -i - 1; + + if (i > 0) { + return path.startsWith(system_prefix[i - 1]); + } + + return false; + } + + @Override + public URL getResource(String name) + { + URL res; + + String s = "getResource: " + name; + trace(0, s, s.length()); + + /* + This is a required for compatibility with Tomcat which + stores all resources prefixed with '/' and application code + may try to get resource with leading '/' (like Jira). Jetty + also has such workaround in WebAppClassLoader.getResource(). + */ + if (name.startsWith("/")) { + name = name.substring(1); + } + + if (isSystemPath(name)) { + return super.getResource(name); + } + + res = system_loader.getResource(name); + if (res != null) { + return res; + } + + res = findResource(name); + if (res != null) { + return res; + } + + return super.getResource(name); + } + + @Override + protected Class loadClass(String name, boolean resolve) + throws ClassNotFoundException + { + synchronized (this) { + Class res = findLoadedClass(name); + if (res != null) { + return res; + } + + try { + res = system_loader.loadClass(name); + + if (resolve) { + resolveClass(res); + } + + return res; + } catch (ClassNotFoundException e) { + } + + String path = name.replace('.', '/').concat(".class"); + + if (isSystemPath(path)) { + return super.loadClass(name, resolve); + } + + URL url = findResource(path); + + if (url != null) { + res = super.findClass(name); + + if (resolve) { + resolveClass(res); + } + + return res; + } + + return super.loadClass(name, resolve); + } + + } + } + + private File extractWar(File war) throws IOException + { + Path tmpDir = Files.createTempDirectory("webapp"); + + JarFile jf = new JarFile(war); + + for (Enumeration en = jf.entries(); en.hasMoreElements();) { + JarEntry e = en.nextElement(); + long mod_time = e.getTime(); + Path ep = tmpDir.resolve(e.getName()); + Path p; + if (e.isDirectory()) { + p = ep; + } else { + p = ep.getParent(); + } + + if (!p.toFile().isDirectory()) { + Files.createDirectories(p); + } + + if (!e.isDirectory()) { + Files.copy(jf.getInputStream(e), ep, + StandardCopyOption.REPLACE_EXISTING); + } + + if (mod_time > 0) { + ep.toFile().setLastModified(mod_time); + } + } + + return tmpDir.toFile(); + } + + private class CtxFilterChain implements FilterChain + { + private int filter_index_ = 0; + private final ServletReg servlet_; + private final List filters_; + + CtxFilterChain(ServletReg servlet, String path, DispatcherType dtype) + { + servlet_ = servlet; + + List filters = new ArrayList<>(); + + for (FilterMap m : filter_maps_) { + if (filters.indexOf(m.filter_) != -1) { + continue; + } + + if (!m.dtypes_.contains(dtype)) { + continue; + } + + if (m.pattern_.match(path)) { + filters.add(m.filter_); + + trace("add filter (matched): " + m.filter_.getName()); + } + } + + for (FilterMap m : servlet.filters_) { + if (filters.indexOf(m.filter_) != -1) { + continue; + } + + if (!m.dtypes_.contains(dtype)) { + continue; + } + + filters.add(m.filter_); + + trace("add filter (servlet): " + m.filter_.getName()); + } + + filters_ = filters; + } + + @Override + public void doFilter (ServletRequest request, ServletResponse response) + throws IOException, ServletException + { + if (filter_index_ < filters_.size()) { + filters_.get(filter_index_++).filter_.doFilter(request, response, this); + + return; + } + + servlet_.service(request, response); + } + } + + private ServletReg findServlet(String path, DynamicPathRequest req) + { + /* + 12.1 Use of URL Paths + ... + 1. The container will try to find an exact match of the path of the + request to the path of the servlet. A successful match selects + the servlet. + */ + ServletReg servlet = exact2servlet_.get(path); + if (servlet != null) { + trace("findServlet: '" + path + "' exact matched pattern"); + req.setServletPath(path, null); + return servlet; + } + + /* + 2. The container will recursively try to match the longest + path-prefix. This is done by stepping down the path tree a + directory at a time, using the '/' character as a path separator. + The longest match determines the servlet selected. + */ + for (PrefixPattern p : prefix_patterns_) { + if (p.match(path)) { + trace("findServlet: '" + path + "' matched prefix pattern '" + p.pattern + "'"); + if (p.pattern.length() == path.length()) { + log("findServlet: WARNING: it is expected '" + path + "' exactly matches " + p.pattern); + req.setServletPath(path, p.pattern, null); + } else { + req.setServletPath(path, p.pattern, path.substring(p.pattern.length())); + } + return p.servlet; + } + } + + /* + 3. If the last segment in the URL path contains an extension + (e.g. .jsp), the servlet container will try to match a servlet + that handles requests for the extension. An extension is defined + as the part of the last segment after the last '.' character. + */ + int suffix_start = path.lastIndexOf('.'); + if (suffix_start != -1) { + String suffix = path.substring(suffix_start); + servlet = suffix2servlet_.get(suffix); + if (servlet != null) { + trace("findServlet: '" + path + "' matched suffix pattern"); + req.setServletPath(path, null); + return servlet; + } + } + + /* + 4. If neither of the previous three rules result in a servlet match, + the container will attempt to serve content appropriate for the + resource requested. If a "default" servlet is defined for the + application, it will be used. ... + */ + if (default_servlet_ != null) { + trace("findServlet: '" + path + "' matched default servlet"); + req.setServletPath(path, null); + return default_servlet_; + } + + trace("findServlet: '" + path + "' no servlet found"); + + /* + 10.10 Welcome Files + ... + If a Web container receives a valid partial request, the Web + container must examine the welcome file list defined in the + deployment descriptor. + ... + */ + if (path.endsWith("/")) { + + /* + The Web server must append each welcome file in the order + specified in the deployment descriptor to the partial request + and check whether a static resource in the WAR is mapped to + that request URI. + */ + for (String wf : welcome_files_) { + String wpath = path + wf; + + File f = new File(webapp_, wpath.substring(1)); + if (!f.exists()) { + continue; + } + + trace("findServlet: '" + path + "' found static welcome " + + "file '" + wf + "'"); + + /* + Even if static file found, we should try to find matching + servlet for JSP serving etc. + */ + servlet = findWelcomeServlet(wpath, true, req); + if (servlet != null) { + return servlet; + } + + req.setServletPath(wpath, null); + + return system_default_servlet_; + } + + /* + If no match is found, the Web server MUST again append each + welcome file in the order specified in the deployment + descriptor to the partial request and check if a servlet is + mapped to that request URI. The Web container must send the + request to the first resource in the WAR that matches. + */ + for (String wf : welcome_files_) { + String wpath = path + wf; + + servlet = findWelcomeServlet(wpath, false, req); + if (servlet != null) { + return servlet; + } + } + } + + trace("findServlet: '" + path + "' fallback to system default servlet"); + req.setServletPath(path, null); + + return system_default_servlet_; + } + + private ServletReg findWelcomeServlet(String path, boolean exists, + DynamicPathRequest req) + { + ServletReg servlet = exact2servlet_.get(path); + if (servlet != null) { + trace("findWelcomeServlet: '" + path + "' exact matched pattern"); + req.setServletPath(path, null); + + return servlet; + } + + int suffix_start = path.lastIndexOf('.'); + if (suffix_start == -1) { + return null; + } + + String suffix = path.substring(suffix_start); + servlet = suffix2servlet_.get(suffix); + if (servlet == null) { + return null; + } + + trace("findWelcomeServlet: '" + path + "' matched suffix pattern"); + + /* + If we want to show the directory content when + index.jsp is absent, then we have to check file + presence here. Otherwise user will get 404. + */ + + if (servlet.system_jsp_servlet_ && !exists) { + trace("findWelcomeServlet: '" + path + "' not exists"); + return null; + } + + req.setServletPath(path, null); + + return servlet; + } + + public void service(Request req, Response resp) + throws ServletException, IOException + { + ClassLoader old = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(loader_); + + ServletRequestEvent sre = null; + + try { + if (!req_init_listeners_.isEmpty()) { + sre = new ServletRequestEvent(this, req); + + for (ServletRequestListener l : req_init_listeners_) { + l.requestInitialized(sre); + } + } + + URI uri = new URI(req.getRequestURI()); + String path = uri.getPath(); + + if (!path.startsWith(context_path_) + || (path.length() > context_path_.length() + && path.charAt(context_path_.length()) != '/')) + { + trace("service: '" + path + "' not started with '" + context_path_ + "'"); + + resp.sendError(resp.SC_NOT_FOUND); + return; + } + + if (path.equals(context_path_)) { + String url = req.getRequestURL().toString(); + if (!url.endsWith("/")) { + resp.setHeader("Location", url + "/"); + resp.sendError(resp.SC_FOUND); + return; + } + } + + path = path.substring(context_path_.length()); + + ServletReg servlet = findServlet(path, req); + + FilterChain fc = new CtxFilterChain(servlet, req.getFilterPath(), DispatcherType.REQUEST); + + fc.doFilter(req, resp); + + Object code = req.getAttribute(RequestDispatcher.ERROR_STATUS_CODE); + if (code != null && code instanceof Integer) { + handleStatusCode((Integer) code, req, resp); + } + } catch (Throwable e) { + trace("service: caught " + e); + + try { + if (!resp.isCommitted() && !exception2location_.isEmpty()) { + handleException(e, req, resp); + } + + if (!resp.isCommitted()) { + resp.reset(); + resp.setStatus(resp.SC_INTERNAL_SERVER_ERROR); + resp.setContentType("text/plain"); + + PrintWriter w = resp.getWriter(); + w.println("Unhandled exception: " + e); + e.printStackTrace(w); + + w.close(); + } + } finally { + throw new ServletException(e); + } + } finally { + resp.flushBuffer(); + + try { + if (!req_destroy_listeners_.isEmpty()) { + for (ServletRequestListener l : req_destroy_listeners_) { + l.requestDestroyed(sre); + } + } + } finally { + Thread.currentThread().setContextClassLoader(old); + } + } + } + + private void handleException(Throwable e, Request req, Response resp) + throws ServletException, IOException + { + String location; + + Class cls = e.getClass(); + while (cls != null && !cls.equals(Throwable.class)) { + location = exception2location_.get(cls.getName()); + + if (location != null) { + trace("Exception " + e + " matched. Error page location: " + location); + + req.setAttribute_(RequestDispatcher.ERROR_EXCEPTION, e); + req.setAttribute_(RequestDispatcher.ERROR_EXCEPTION_TYPE, e.getClass()); + req.setAttribute_(RequestDispatcher.ERROR_REQUEST_URI, req.getRequestURI()); + req.setAttribute_(RequestDispatcher.ERROR_STATUS_CODE, resp.SC_INTERNAL_SERVER_ERROR); + + handleError(location, req, resp); + + return; + } + + cls = cls.getSuperclass(); + } + + if (ServletException.class.isAssignableFrom(e.getClass())) { + ServletException se = (ServletException) e; + + handleException(se.getRootCause(), req, resp); + } + } + + private void handleStatusCode(int code, Request req, Response resp) + throws ServletException, IOException + { + String location; + + location = error2location_.get(code); + + if (location != null) { + trace("Status " + code + " matched. Error page location: " + location); + + req.setAttribute_(RequestDispatcher.ERROR_REQUEST_URI, req.getRequestURI()); + + handleError(location, req, resp); + } + } + + public void handleError(String location, Request req, Response resp) + throws ServletException, IOException + { + try { + log("handleError: " + location); + + String filter_path = req.getFilterPath(); + String servlet_path = req.getServletPath(); + String path_info = req.getPathInfo(); + String req_uri = req.getRequestURI(); + DispatcherType dtype = req.getDispatcherType(); + + URI uri; + + if (location.startsWith("/")) { + uri = new URI(context_path_ + location); + } else { + uri = new URI(req_uri).resolve(location); + } + + req.setRequestURI(uri.getRawPath()); + req.setDispatcherType(DispatcherType.ERROR); + + String path = uri.getPath().substring(context_path_.length()); + + ServletReg servlet = findServlet(path, req); + + FilterChain fc = new CtxFilterChain(servlet, req.getFilterPath(), DispatcherType.ERROR); + + fc.doFilter(req, resp); + + req.setServletPath(filter_path, servlet_path, path_info); + req.setRequestURI(req_uri); + req.setDispatcherType(dtype); + } catch (URISyntaxException e) { + throw new ServletException(e); + } + } + + private void processWebXml(File root) throws Exception + { + if (root.isDirectory()) { + File web_xml = new File(root, "WEB-INF/web.xml"); + if (web_xml.exists()) { + trace("start: web.xml file found"); + + InputStream is = new FileInputStream(web_xml); + + processWebXml(is); + + is.close(); + } + } else { + JarFile jf = new JarFile(root); + ZipEntry ze = jf.getEntry("WEB-INF/web.xml"); + + if (ze == null) { + trace("start: web.xml entry NOT found"); + } else { + trace("start: web.xml entry found"); + + processWebXml(jf.getInputStream(ze)); + } + + jf.close(); + } + } + + private void processWebXml(InputStream is) + throws ParserConfigurationException, SAXException, IOException + { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + + Document doc = builder.parse(is); + + Element doc_elem = doc.getDocumentElement(); + String doc_elem_name = doc_elem.getNodeName(); + if (!doc_elem_name.equals("web-app")) { + throw new RuntimeException("Invalid web.xml: 'web-app' element expected, not '" + doc_elem_name + "'"); + } + + metadata_complete_ = doc_elem.getAttribute("metadata-complete").equals("true"); + app_version_ = doc_elem.getAttribute("version"); + + NodeList welcome_file_lists = doc_elem.getElementsByTagName("welcome-file-list"); + + if (welcome_file_lists.getLength() > 0) { + welcome_files_list_found_ = true; + } + + for (int i = 0; i < welcome_file_lists.getLength(); i++) { + Element list_el = (Element) welcome_file_lists.item(i); + NodeList files = list_el.getElementsByTagName("welcome-file"); + for (int j = 0; j < files.getLength(); j++) { + Node node = files.item(j); + String wf = node.getTextContent().trim(); + + /* + 10.10 Welcome Files + ... + The welcome file list is an ordered list of partial URLs + with no trailing or leading /. + */ + + if (wf.startsWith("/") || wf.endsWith("/")) { + log("invalid welcome file: " + wf); + continue; + } + + welcome_files_.add(wf); + } + } + + NodeList context_params = doc_elem.getElementsByTagName("context-param"); + for (int i = 0; i < context_params.getLength(); i++) { + processXmlInitParam(this, (Element) context_params.item(i)); + } + + NodeList filters = doc_elem.getElementsByTagName("filter"); + + for (int i = 0; i < filters.getLength(); i++) { + Element filter_el = (Element) filters.item(i); + NodeList names = filter_el.getElementsByTagName("filter-name"); + if (names == null || names.getLength() != 1) { + throw new RuntimeException("Invalid web.xml: 'filter-name' tag not found"); + } + + String filter_name = names.item(0).getTextContent().trim(); + trace("filter-name=" + filter_name); + + FilterReg reg = new FilterReg(filter_name); + + NodeList child_nodes = filter_el.getChildNodes(); + for(int j = 0; j < child_nodes.getLength(); j++) { + Node child_node = child_nodes.item(j); + String tag_name = child_node.getNodeName(); + + if (tag_name.equals("filter-class")) { + reg.setClassName(child_node.getTextContent().trim()); + continue; + } + + if (tag_name.equals("async-supported")) { + reg.setAsyncSupported(child_node.getTextContent().trim() + .equals("true")); + continue; + } + + if (tag_name.equals("init-param")) { + processXmlInitParam(reg, (Element) child_node); + continue; + } + } + + filters_.add(reg); + name2filter_.put(filter_name, reg); + } + + NodeList filter_mappings = doc_elem.getElementsByTagName("filter-mapping"); + + for(int i = 0; i < filter_mappings.getLength(); i++) { + Element mapping_el = (Element) filter_mappings.item(i); + NodeList names = mapping_el.getElementsByTagName("filter-name"); + if (names == null || names.getLength() != 1) { + throw new RuntimeException("Invalid web.xml: 'filter-name' tag not found"); + } + + String filter_name = names.item(0).getTextContent().trim(); + trace("filter-name=" + filter_name); + + FilterReg reg = name2filter_.get(filter_name); + if (reg == null) { + throw new RuntimeException("Invalid web.xml: filter '" + filter_name + "' not found"); + } + + EnumSet dtypes = EnumSet.noneOf(DispatcherType.class); + NodeList dispatchers = mapping_el.getElementsByTagName("dispatcher"); + for (int j = 0; j < dispatchers.getLength(); j++) { + Node child_node = dispatchers.item(j); + dtypes.add(DispatcherType.valueOf(child_node.getTextContent().trim())); + } + + if (dtypes.isEmpty()) { + dtypes.add(DispatcherType.REQUEST); + } + + boolean match_after = false; + + NodeList child_nodes = mapping_el.getChildNodes(); + for (int j = 0; j < child_nodes.getLength(); j++) { + Node child_node = child_nodes.item(j); + String tag_name = child_node.getNodeName(); + + if (tag_name.equals("url-pattern")) { + reg.addMappingForUrlPatterns(dtypes, match_after, child_node.getTextContent().trim()); + continue; + } + + if (tag_name.equals("servlet-name")) { + reg.addMappingForServletNames(dtypes, match_after, child_node.getTextContent().trim()); + continue; + } + } + } + + NodeList servlets = doc_elem.getElementsByTagName("servlet"); + + for (int i = 0; i < servlets.getLength(); i++) { + Element servlet_el = (Element) servlets.item(i); + NodeList names = servlet_el.getElementsByTagName("servlet-name"); + if (names == null || names.getLength() != 1) { + throw new RuntimeException("Invalid web.xml: 'servlet-name' tag not found"); + } + + String servlet_name = names.item(0).getTextContent().trim(); + trace("servlet-name=" + servlet_name); + + ServletReg reg = new ServletReg(servlet_name); + + NodeList child_nodes = servlet_el.getChildNodes(); + for(int j = 0; j < child_nodes.getLength(); j++) { + Node child_node = child_nodes.item(j); + String tag_name = child_node.getNodeName(); + + if (tag_name.equals("servlet-class")) { + reg.setClassName(child_node.getTextContent().trim()); + continue; + } + + if (tag_name.equals("async-supported")) { + reg.setAsyncSupported(child_node.getTextContent().trim() + .equals("true")); + continue; + } + + if (tag_name.equals("init-param")) { + processXmlInitParam(reg, (Element) child_node); + continue; + } + + if (tag_name.equals("load-on-startup")) { + reg.setLoadOnStartup(Integer.parseInt(child_node.getTextContent().trim())); + continue; + } + } + + servlets_.add(reg); + name2servlet_.put(servlet_name, reg); + } + + NodeList servlet_mappings = doc_elem.getElementsByTagName("servlet-mapping"); + + for(int i = 0; i < servlet_mappings.getLength(); i++) { + Element mapping_el = (Element) servlet_mappings.item(i); + NodeList names = mapping_el.getElementsByTagName("servlet-name"); + if (names == null || names.getLength() != 1) { + throw new RuntimeException("Invalid web.xml: 'servlet-name' tag not found"); + } + + String servlet_name = names.item(0).getTextContent().trim(); + trace("servlet-name=" + servlet_name); + + ServletReg reg = name2servlet_.get(servlet_name); + if (reg == null) { + throw new RuntimeException("Invalid web.xml: servlet '" + servlet_name + "' not found"); + } + + NodeList child_nodes = mapping_el.getElementsByTagName("url-pattern"); + String patterns[] = new String[child_nodes.getLength()]; + for(int j = 0; j < child_nodes.getLength(); j++) { + Node child_node = child_nodes.item(j); + patterns[j] = child_node.getTextContent().trim(); + } + + reg.addMapping(patterns); + } + + NodeList listeners = doc_elem.getElementsByTagName("listener"); + + for (int i = 0; i < listeners.getLength(); i++) { + Element listener_el = (Element) listeners.item(i); + NodeList classes = listener_el.getElementsByTagName("listener-class"); + if (classes == null || classes.getLength() != 1) { + throw new RuntimeException("Invalid web.xml: 'listener-class' tag not found"); + } + + String class_name = classes.item(0).getTextContent().trim(); + trace("listener-class=" + class_name); + + pending_listener_classnames_.add(class_name); + } + + NodeList error_pages = doc_elem.getElementsByTagName("error-page"); + + for (int i = 0; i < error_pages.getLength(); i++) { + Element error_page_el = (Element) error_pages.item(i); + NodeList locations = error_page_el.getElementsByTagName("location"); + if (locations == null || locations.getLength() != 1) { + throw new RuntimeException("Invalid web.xml: 'location' tag not found"); + } + + String location = locations.item(0).getTextContent().trim(); + + NodeList child_nodes = error_page_el.getChildNodes(); + for(int j = 0; j < child_nodes.getLength(); j++) { + Node child_node = child_nodes.item(j); + String tag_name = child_node.getNodeName(); + + if (tag_name.equals("exception-type")) { + String ex = child_node.getTextContent().trim(); + + exception2location_.put(ex, location); + trace("error-page: exception " + ex + " -> " + location); + continue; + } + + if (tag_name.equals("error-code")) { + Integer code = Integer.parseInt(child_node.getTextContent().trim()); + + error2location_.put(code, location); + trace("error-page: code " + code + " -> " + location); + continue; + } + } + } + + NodeList session_config = doc_elem.getElementsByTagName("session-config"); + + for (int i = 0; i < session_config.getLength(); i++) { + Element session_config_el = (Element) session_config.item(i); + NodeList session_timeout = session_config_el.getElementsByTagName("session-timeout"); + if (session_timeout != null) { + String timeout = session_timeout.item(0).getTextContent().trim(); + + trace("session_timeout: " + timeout); + session_timeout_ = Integer.parseInt(timeout); + break; + } + } + + NodeList jsp_configs = doc_elem.getElementsByTagName("jsp-config"); + + for (int i = 0; i < jsp_configs.getLength(); i++) { + Element jsp_config_el = (Element) jsp_configs.item(i); + + NodeList jsp_nodes = jsp_config_el.getChildNodes(); + + for(int j = 0; j < jsp_nodes.getLength(); j++) { + Node jsp_node = jsp_nodes.item(j); + String tag_name = jsp_node.getNodeName(); + + if (tag_name.equals("taglib")) { + NodeList tl_nodes = ((Element) jsp_node).getChildNodes(); + Taglib tl = new Taglib(tl_nodes); + + trace("add taglib"); + + taglibs_.add(tl); + continue; + } + + if (tag_name.equals("jsp-property-group")) { + NodeList jpg_nodes = ((Element) jsp_node).getChildNodes(); + JspPropertyGroup conf = new JspPropertyGroup(jpg_nodes); + + trace("add prop group"); + + prop_groups_.add(conf); + continue; + } + } + } + } + + private static int compareVersion(String ver1, String ver2) + { + String[] varr1 = ver1.split("\\."); + String[] varr2 = ver2.split("\\."); + + int max_len = varr1.length > varr2.length ? varr1.length : varr2.length; + for (int i = 0; i < max_len; i++) { + int l = i < varr1.length ? Integer.parseInt(varr1[i]) : 0; + int r = i < varr2.length ? Integer.parseInt(varr2[i]) : 0; + + int res = l - r; + + if (res != 0) { + return res; + } + } + + return 0; + } + + private void processXmlInitParam(InitParams params, Element elem) + throws RuntimeException + { + NodeList n = elem.getElementsByTagName("param-name"); + if (n == null || n.getLength() != 1) { + throw new RuntimeException("Invalid web.xml: 'param-name' tag not found"); + } + + NodeList v = elem.getElementsByTagName("param-value"); + if (v == null || v.getLength() != 1) { + throw new RuntimeException("Invalid web.xml: 'param-value' tag not found"); + } + params.setInitParameter(n.item(0).getTextContent().trim(), + v.item(0).getTextContent().trim()); + } + + private void loadInitializers(ScanResult scan_res) + { + trace("load initializer(s)"); + + ServiceLoader initializers = + ServiceLoader.load(ServletContainerInitializer.class, loader_); + + for (ServletContainerInitializer sci : initializers) { + + trace("loadInitializers: initializer: " + sci.getClass().getName()); + + HandlesTypes ann = sci.getClass().getAnnotation(HandlesTypes.class); + if (ann == null) { + trace("loadInitializers: no HandlesTypes annotation"); + continue; + } + + Class[] classes = ann.value(); + if (classes == null) { + trace("loadInitializers: no handles classes"); + continue; + } + + Set> handles_classes = new HashSet<>(); + + for (Class c : classes) { + trace("loadInitializers: find handles: " + c.getName()); + + ClassInfoList handles = c.isInterface() + ? scan_res.getClassesImplementing(c.getName()) + : scan_res.getSubclasses(c.getName()); + + for (ClassInfo ci : handles) { + if (ci.isInterface() + || ci.isAnnotation() + || ci.isAbstract()) + { + continue; + } + + trace("loadInitializers: handles class: " + ci.getName()); + handles_classes.add(ci.loadClass()); + } + } + + if (handles_classes.isEmpty()) { + trace("loadInitializers: no handles implementations"); + continue; + } + + try { + sci.onStartup(handles_classes, this); + metadata_complete_ = true; + } catch(Exception e) { + System.err.println("loadInitializers: exception caught: " + e.toString()); + } + } + } + + private void scanClasses(ScanResult scan_res) + throws ReflectiveOperationException + { + ClassInfoList filters = scan_res.getClassesWithAnnotation(WebFilter.class.getName()); + + for (ClassInfo ci : filters) { + if (ci.isInterface() + || ci.isAnnotation() + || ci.isAbstract() + || !ci.implementsInterface(Filter.class.getName())) + { + trace("scanClasses: ignoring Filter impl: " + ci.getName()); + continue; + } + + trace("scanClasses: found Filter class: " + ci.getName()); + + Class cls = ci.loadClass(); + if (!Filter.class.isAssignableFrom(cls)) { + trace("scanClasses: " + ci.getName() + " cannot be assigned to Filter"); + continue; + } + + WebFilter ann = cls.getAnnotation(WebFilter.class); + + if (ann == null) { + trace("scanClasses: no WebFilter annotation for " + ci.getName()); + continue; + } + + String filter_name = ann.filterName(); + + if (filter_name.isEmpty()) { + filter_name = ci.getName(); + } + + FilterReg reg = name2filter_.get(filter_name); + + if (reg == null) { + reg = new FilterReg(filter_name, cls); + filters_.add(reg); + name2filter_.put(filter_name, reg); + } else { + reg.setClass(cls); + } + + EnumSet dtypes = EnumSet.noneOf(DispatcherType.class); + DispatcherType[] dispatchers = ann.dispatcherTypes(); + for (DispatcherType d : dispatchers) { + dtypes.add(d); + } + + if (dtypes.isEmpty()) { + dtypes.add(DispatcherType.REQUEST); + } + + boolean match_after = false; + + reg.addMappingForUrlPatterns(dtypes, match_after, ann.value()); + reg.addMappingForUrlPatterns(dtypes, match_after, ann.urlPatterns()); + reg.addMappingForServletNames(dtypes, match_after, ann.servletNames()); + + for (WebInitParam p : ann.initParams()) { + reg.setInitParameter(p.name(), p.value()); + } + + reg.setAsyncSupported(ann.asyncSupported()); + } + + ClassInfoList servlets = scan_res.getClassesWithAnnotation(WebServlet.class.getName()); + + for (ClassInfo ci : servlets) { + if (ci.isInterface() + || ci.isAnnotation() + || ci.isAbstract() + || !ci.extendsSuperclass(HttpServlet.class.getName())) + { + trace("scanClasses: ignoring HttpServlet subclass: " + ci.getName()); + continue; + } + + trace("scanClasses: found HttpServlet class: " + ci.getName()); + + Class cls = ci.loadClass(); + if (!HttpServlet.class.isAssignableFrom(cls)) { + trace("scanClasses: " + ci.getName() + " cannot be assigned to HttpFilter"); + continue; + } + + WebServlet ann = cls.getAnnotation(WebServlet.class); + + if (ann == null) { + trace("scanClasses: no WebServlet annotation"); + continue; + } + + String servlet_name = ann.name(); + + if (servlet_name.isEmpty()) { + servlet_name = ci.getName(); + } + + ServletReg reg = name2servlet_.get(servlet_name); + + if (reg == null) { + reg = new ServletReg(servlet_name, cls); + servlets_.add(reg); + name2servlet_.put(servlet_name, reg); + } else { + reg.setClass(cls); + } + + reg.addMapping(ann.value()); + reg.addMapping(ann.urlPatterns()); + + for (WebInitParam p : ann.initParams()) { + reg.setInitParameter(p.name(), p.value()); + } + + reg.setAsyncSupported(ann.asyncSupported()); + } + + + ClassInfoList lstnrs = scan_res.getClassesWithAnnotation(WebListener.class.getName()); + + for (ClassInfo ci : lstnrs) { + if (ci.isInterface() + || ci.isAnnotation() + || ci.isAbstract()) + { + trace("scanClasses: listener impl: " + ci.getName()); + continue; + } + + trace("scanClasses: listener class: " + ci.getName()); + + if (listener_classnames_.contains(ci.getName())) { + trace("scanClasses: " + ci.getName() + " already added as listener"); + continue; + } + + Class cls = ci.loadClass(); + Class lclass = null; + for (Class c : LISTENER_TYPES) { + if (c.isAssignableFrom(cls)) { + lclass = c; + break; + } + } + + if (lclass == null) { + log("scanClasses: " + ci.getName() + " implements none of known listener interfaces"); + continue; + } + + WebListener ann = cls.getAnnotation(WebListener.class); + + if (ann == null) { + log("scanClasses: no WebListener annotation"); + continue; + } + + Constructor ctor = cls.getConstructor(); + EventListener listener = (EventListener) ctor.newInstance(); + + addListener(listener); + + listener_classnames_.add(ci.getName()); + } + } + + public void stop() throws IOException + { + ClassLoader old = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(loader_); + + try { + for (ServletReg s : servlets_) { + s.destroy(); + } + + for (FilterReg f : filters_) { + f.destroy(); + } + + if (!destroy_listeners_.isEmpty()) { + ServletContextEvent event = new ServletContextEvent(this); + for (ServletContextListener listener : destroy_listeners_) { + listener.contextDestroyed(event); + } + } + + if (extracted_dir_ != null) { + removeDir(extracted_dir_); + } + + if (temp_dir_ != null) { + removeDir(temp_dir_); + } + } finally { + Thread.currentThread().setContextClassLoader(old); + } + } + + private void removeDir(File dir) throws IOException + { + Files.walkFileTree(dir.toPath(), + new SimpleFileVisitor() { + @Override + public FileVisitResult postVisitDirectory( + Path dir, IOException exc) throws IOException { + Files.delete(dir); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile( + Path file, BasicFileAttributes attrs) + throws IOException { + Files.delete(file); + return FileVisitResult.CONTINUE; + } + }); + } + + private class CtxInitParams implements InitParams + { + private final Map init_params_ = + new HashMap(); + + public boolean setInitParameter(String name, String value) + { + trace("CtxInitParams.setInitParameter " + name + " = " + value); + + return init_params_.putIfAbsent(name, value) == null; + } + + public String getInitParameter(String name) + { + trace("CtxInitParams.getInitParameter for " + name); + + return init_params_.get(name); + } + + public Set setInitParameters(Map initParameters) + { + // illegalStateIfContextStarted(); + Set clash = null; + for (Map.Entry entry : initParameters.entrySet()) + { + if (entry.getKey() == null) { + throw new IllegalArgumentException("init parameter name required"); + } + + if (entry.getValue() == null) { + throw new IllegalArgumentException("non-null value required for init parameter " + entry.getKey()); + } + + if (init_params_.get(entry.getKey()) != null) + { + if (clash == null) + clash = new HashSet(); + clash.add(entry.getKey()); + } + + trace("CtxInitParams.setInitParameters " + entry.getKey() + " = " + entry.getValue()); + } + + if (clash != null) { + return clash; + } + + init_params_.putAll(initParameters); + return Collections.emptySet(); + } + + public Map getInitParameters() + { + trace("CtxInitParams.getInitParameters"); + return init_params_; + } + + public Enumeration getInitParameterNames() + { + return Collections.enumeration(init_params_.keySet()); + } + } + + private class NamedReg extends CtxInitParams + implements Registration + { + private final String name_; + private String class_name_; + + public NamedReg(String name) + { + name_ = name; + } + + public NamedReg(String name, String class_name) + { + name_ = name; + class_name_ = class_name; + } + + @Override + public String getName() + { + return name_; + } + + @Override + public String getClassName() + { + return class_name_; + } + + public void setClassName(String class_name) + { + class_name_ = class_name; + } + } + + private class ServletReg extends NamedReg + implements ServletRegistration.Dynamic, ServletConfig + { + private Class servlet_class_; + private Servlet servlet_; + private String role_; + private boolean async_supported_ = false; + private final List patterns_ = new ArrayList<>(); + private int load_on_startup_ = -1; + private boolean initialized_ = false; + private final List filters_ = new ArrayList<>(); + private boolean system_jsp_servlet_ = false; + + public ServletReg(String name, Class servlet_class) + { + super(name, servlet_class.getName()); + servlet_class_ = servlet_class; + } + + public ServletReg(String name, Servlet servlet) + { + super(name, servlet.getClass().getName()); + servlet_ = servlet; + } + + public ServletReg(String name, String servlet_class_name) + { + super(name, servlet_class_name); + } + + public ServletReg(String name) + { + super(name); + } + + private void init() throws ServletException + { + if (initialized_) { + return; + } + + trace("ServletReg.init(): " + getName()); + + if (system_jsp_servlet_) { + JasperInitializer ji = new JasperInitializer(); + + ji.onStartup(Collections.emptySet(), Context.this); + } + + if (servlet_ == null) { + try { + if (servlet_class_ == null) { + servlet_class_ = loader_.loadClass(getClassName()); + } + + Constructor ctor = servlet_class_.getConstructor(); + servlet_ = (Servlet) ctor.newInstance(); + } catch(Exception e) { + log("ServletReg.init() failed " + e); + throw new ServletException(e); + } + } + + servlet_.init((ServletConfig) this); + + initialized_ = true; + } + + public void startup() throws ServletException + { + if (load_on_startup_ < 0) { + return; + } + + init(); + } + + public void destroy() + { + if (initialized_) { + servlet_.destroy(); + } + } + + public void setClassName(String class_name) throws IllegalStateException + { + if (servlet_ != null + || servlet_class_ != null + || getClassName() != null) + { + throw new IllegalStateException("Class already initialized"); + } + + super.setClassName(class_name); + } + + public void setClass(Class servlet_class) + throws IllegalStateException + { + if (servlet_ != null + || servlet_class_ != null + || getClassName() != null) + { + throw new IllegalStateException("Class already initialized"); + } + + super.setClassName(servlet_class.getName()); + servlet_class_ = servlet_class; + } + + public void service(ServletRequest request, ServletResponse response) + throws ServletException, IOException + { + init(); + + servlet_.service(request, response); + } + + public void addFilter(FilterMap fmap) + { + filters_.add(fmap); + } + + @Override + public Set addMapping(String... urlPatterns) + { + checkContextState(); + + Set clash = null; + for (String pattern : urlPatterns) { + trace("ServletReg.addMapping: " + pattern); + + if (pattern2servlet_.containsKey(pattern)) { + if (clash == null) { + clash = new HashSet(); + } + clash.add(pattern); + } + } + + /* if there were any clashes amongst the urls, return them */ + if (clash != null) { + return clash; + } + + for (String pattern : urlPatterns) { + patterns_.add(pattern); + pattern2servlet_.put(pattern, this); + parseURLPattern(pattern, this); + } + + return Collections.emptySet(); + } + + @Override + public Collection getMappings() + { + trace("ServletReg.getMappings"); + return patterns_; + } + + @Override + public String getRunAsRole() + { + return role_; + } + + @Override + public void setLoadOnStartup(int loadOnStartup) + { + checkContextState(); + + trace("ServletReg.setLoadOnStartup: " + loadOnStartup); + load_on_startup_ = loadOnStartup; + } + + @Override + public Set setServletSecurity(ServletSecurityElement constraint) + { + log("ServletReg.setServletSecurity"); + return Collections.emptySet(); + } + + @Override + public void setMultipartConfig( + MultipartConfigElement multipartConfig) + { + log("ServletReg.setMultipartConfig"); + } + + @Override + public void setRunAsRole(String roleName) + { + log("ServletReg.setRunAsRole: " + roleName); + role_ = roleName; + } + + @Override + public void setAsyncSupported(boolean isAsyncSupported) + { + log("ServletReg.setAsyncSupported: " + isAsyncSupported); + async_supported_ = isAsyncSupported; + } + + @Override + public String getServletName() + { + return getName(); + } + + @Override + public ServletContext getServletContext() + { + return (ServletContext) Context.this; + } + } + + public void checkContextState() throws IllegalStateException + { + if (ctx_initialized_) { + throw new IllegalStateException("Context already initialized"); + } + } + + public void parseURLPattern(String p, ServletReg servlet) + throws IllegalArgumentException + { + URLPattern pattern = parseURLPattern(p); + + switch (pattern.type_) { + case PREFIX: + prefix_patterns_.add(new PrefixPattern(pattern.pattern_, servlet)); + return; + + case SUFFIX: + suffix2servlet_.put(pattern.pattern_, servlet); + return; + + case EXACT: + exact2servlet_.put(pattern.pattern_, servlet); + return; + + case DEFAULT: + default_servlet_ = servlet; + return; + } + + /* TODO process other cases, throw IllegalArgumentException */ + } + + public URLPattern parseURLPattern(String p) + throws IllegalArgumentException + { + URLPattern pattern = parsed_patterns_.get(p); + if (pattern == null) { + pattern = new URLPattern(p); + parsed_patterns_.put(p, pattern); + } + + return pattern; + } + + private static enum URLPatternType { + PREFIX, + SUFFIX, + DEFAULT, + EXACT, + }; + + private class URLPattern + { + private final String pattern_; + private final URLPatternType type_; + + public URLPattern(String p) + throws IllegalArgumentException + { + /* + 12.2 Specification of Mappings + ... + A string beginning with a '/' character and ending with a '/*' + suffix is used for path mapping. + */ + if (p.startsWith("/") && p.endsWith("/*")) { + trace("URLPattern: '" + p + "' is a prefix pattern"); + pattern_ = p.substring(0, p.length() - 2); + type_ = URLPatternType.PREFIX; + return; + } + + /* + A string beginning with a '*.' prefix is used as an extension + mapping. + */ + if (p.startsWith("*.")) { + trace("URLPattern: '" + p + "' is a suffix pattern"); + pattern_ = p.substring(1, p.length()); + type_ = URLPatternType.SUFFIX; + return; + } + + /* + The empty string ("") is a special URL pattern that exactly maps to + the application's context root, i.e., requests of the form + http://host:port//. In this case the path info is '/' + and the servlet path and context path is empty string (""). + */ + if (p.isEmpty()) { + trace("URLPattern: '" + p + "' is a root"); + pattern_ = "/"; + type_ = URLPatternType.EXACT; + return; + } + + /* + A string containing only the '/' character indicates the "default" + servlet of the application. In this case the servlet path is the + request URI minus the context path and the path info is null. + */ + if (p.equals("/")) { + trace("URLPattern: '" + p + "' is a default"); + pattern_ = p; + type_ = URLPatternType.DEFAULT; + return; + } + + /* + All other strings are used for exact matches only. + */ + trace("URLPattern: '" + p + "' is an exact pattern"); + pattern_ = p; + type_ = URLPatternType.EXACT; + + /* TODO process other cases, throw IllegalArgumentException */ + } + + public boolean match(String url) + { + switch (type_) { + case PREFIX: + return url.startsWith(pattern_) && ( + url.length() == pattern_.length() + || url.charAt(pattern_.length()) == '/'); + + case SUFFIX: + return url.endsWith(pattern_); + + case EXACT: + return url.equals(pattern_); + + case DEFAULT: + return true; + } + + return false; + } + } + + private class FilterReg extends NamedReg + implements FilterRegistration.Dynamic, FilterConfig + { + private Class filter_class_; + private Filter filter_; + private boolean async_supported_ = false; + private boolean initialized_ = false; + + public FilterReg(String name, Class filter_class) + { + super(name, filter_class.getName()); + filter_class_ = filter_class; + } + + public FilterReg(String name, Filter filter) + { + super(name, filter.getClass().getName()); + filter_ = filter; + } + + public FilterReg(String name, String filter_class_name) + { + super(name, filter_class_name); + } + + public FilterReg(String name) + { + super(name); + } + + public void setClassName(String class_name) throws IllegalStateException + { + if (filter_ != null + || filter_class_ != null + || getClassName() != null) + { + throw new IllegalStateException("Class already initialized"); + } + + super.setClassName(class_name); + } + + public void setClass(Class filter_class) throws IllegalStateException + { + if (filter_ != null + || filter_class_ != null + || getClassName() != null) + { + throw new IllegalStateException("Class already initialized"); + } + + super.setClassName(filter_class.getName()); + filter_class_ = filter_class; + } + + public void init() throws ServletException + { + if (filter_ == null) { + try { + if (filter_class_ == null) { + filter_class_ = loader_.loadClass(getClassName()); + } + + Constructor ctor = filter_class_.getConstructor(); + filter_ = (Filter) ctor.newInstance(); + } catch(Exception e) { + log("FilterReg.init() failed " + e); + throw new ServletException(e); + } + } + + filter_.init((FilterConfig) this); + + initialized_ = true; + } + + public void destroy() + { + if (initialized_) { + filter_.destroy(); + } + } + + @Override + public void addMappingForServletNames( + EnumSet dispatcherTypes, boolean isMatchAfter, + String... servletNames) + { + checkContextState(); + + for (String n : servletNames) { + trace("FilterReg.addMappingForServletNames: ... " + n); + + ServletReg sreg = name2servlet_.get(n); + if (sreg == null) { + sreg = new ServletReg(n); + servlets_.add(sreg); + name2servlet_.put(n, sreg); + } + + FilterMap map = new FilterMap(this, sreg, dispatcherTypes, + isMatchAfter); + + sreg.addFilter(map); + } + } + + @Override + public Collection getServletNameMappings() + { + checkContextState(); + + log("FilterReg.getServletNameMappings"); + return Collections.emptySet(); + } + + @Override + public void addMappingForUrlPatterns( + EnumSet dispatcherTypes, boolean isMatchAfter, + String... urlPatterns) + { + checkContextState(); + + for (String u : urlPatterns) { + trace("FilterReg.addMappingForUrlPatterns: ... " + u); + + URLPattern p = parseURLPattern(u); + FilterMap map = new FilterMap(this, p, dispatcherTypes, + isMatchAfter); + + filter_maps_.add(map); + } + } + + @Override + public Collection getUrlPatternMappings() + { + log("FilterReg.getUrlPatternMappings"); + return Collections.emptySet(); + } + + @Override + public void setAsyncSupported(boolean isAsyncSupported) + { + log("FilterReg.setAsyncSupported: " + isAsyncSupported); + async_supported_ = isAsyncSupported; + } + + @Override + public String getFilterName() + { + return getName(); + } + + @Override + public ServletContext getServletContext() + { + return (ServletContext) Context.this; + } + } + + private class FilterMap + { + private final FilterReg filter_; + private final ServletReg servlet_; + private final URLPattern pattern_; + private final EnumSet dtypes_; + private final boolean match_after_; + + public FilterMap(FilterReg filter, ServletReg servlet, + EnumSet dtypes, boolean match_after) + { + filter_ = filter; + servlet_ = servlet; + pattern_ = null; + dtypes_ = dtypes; + match_after_ = match_after; + } + + public FilterMap(FilterReg filter, URLPattern pattern, + EnumSet dtypes, boolean match_after) + { + filter_ = filter; + servlet_ = null; + pattern_ = pattern; + dtypes_ = dtypes; + match_after_ = match_after; + } + } + + private void initialized() + { + if (!sess_attr_listeners_.isEmpty()) { + sess_attr_proxy_ = new SessionAttrProxy(sess_attr_listeners_); + } + + if (!req_attr_listeners_.isEmpty()) { + req_attr_proxy_ = new RequestAttrProxy(req_attr_listeners_); + } + + ClassLoader old = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(loader_); + + try { + // Call context listeners + destroy_listeners_.clear(); + if (!ctx_listeners_.isEmpty()) { + ServletContextEvent event = new ServletContextEvent(this); + for (ServletContextListener listener : ctx_listeners_) + { + try { + listener.contextInitialized(event); + } catch(AbstractMethodError e) { + log("initialized: AbstractMethodError exception caught: " + e); + } + destroy_listeners_.add(0, listener); + } + } + + for (ServletReg sr : servlets_) { + try { + sr.startup(); + } catch(ServletException e) { + log("initialized: exception caught: " + e); + } + } + + for (FilterReg fr : filters_) { + try { + fr.init(); + } catch(ServletException e) { + log("initialized: exception caught: " + e); + } + } + + ctx_initialized_ = true; + } finally { + Thread.currentThread().setContextClassLoader(old); + } + } + + @Override + public ServletContext getContext(String uripath) + { + trace("getContext for " + uripath); + return this; + } + + @Override + public int getMajorVersion() + { + trace("getMajorVersion"); + return SERVLET_MAJOR_VERSION; + } + + @Override + public String getMimeType(String file) + { + log("getMimeType for " + file); + if (mime_types_ == null) { + mime_types_ = new MimeTypes(); + } + return mime_types_.getMimeByExtension(file); + } + + @Override + public int getMinorVersion() + { + trace("getMinorVersion"); + return SERVLET_MINOR_VERSION; + } + + private class URIRequestDispatcher implements RequestDispatcher + { + private final URI uri_; + + public URIRequestDispatcher(URI uri) + { + uri_ = uri; + } + + public URIRequestDispatcher(String uri) + throws URISyntaxException + { + uri_ = new URI(uri); + } + + @Override + public void forward(ServletRequest request, ServletResponse response) + throws ServletException, IOException + { + /* + 9.4 The Forward Method + ... + If the response has been committed, an IllegalStateException + must be thrown. + */ + if (response.isCommitted()) { + throw new IllegalStateException("Response already committed"); + } + + ForwardRequestWrapper req = new ForwardRequestWrapper(request); + + try { + trace("URIRequestDispatcher.forward"); + + String path = uri_.getPath().substring(context_path_.length()); + + ServletReg servlet = findServlet(path, req); + + req.setRequestURI(uri_.getRawPath()); + req.setQueryString(uri_.getRawQuery()); + req.setDispatcherType(DispatcherType.FORWARD); + + /* + 9.4 The Forward Method + ... + If output data exists in the response buffer that has not + been committed, the content must be cleared before the + target servlet's service method is called. + */ + response.resetBuffer(); + + FilterChain fc = new CtxFilterChain(servlet, req.getFilterPath(), DispatcherType.FORWARD); + + fc.doFilter(request, response); + + /* + 9.4 The Forward Method + ... + Before the forward method of the RequestDispatcher interface + returns without exception, the response content must be sent + and committed, and closed by the servlet container, unless + the request was put into the asynchronous mode. If an error + occurs in the target of the RequestDispatcher.forward() the + exception may be propagated back through all the calling + filters and servlets and eventually back to the container + */ + if (!request.isAsyncStarted()) { + response.flushBuffer(); + } + + /* + 9.5 Error Handling + + If the servlet that is the target of a request dispatcher + throws a runtime exception or a checked exception of type + ServletException or IOException, it should be propagated + to the calling servlet. All other exceptions should be + wrapped as ServletExceptions and the root cause of the + exception set to the original exception, as it should + not be propagated. + */ + } catch (ServletException e) { + throw e; + } catch (IOException e) { + throw e; + } catch (Exception e) { + throw new ServletException(e); + } finally { + req.close(); + + trace("URIRequestDispatcher.forward done"); + } + } + + @Override + public void include(ServletRequest request, ServletResponse response) + throws ServletException, IOException + { + IncludeRequestWrapper req = new IncludeRequestWrapper(request); + + try { + trace("URIRequestDispatcher.include"); + + String path = uri_.getPath().substring(context_path_.length()); + + ServletReg servlet = findServlet(path, req); + + req.setRequestURI(uri_.getRawPath()); + req.setQueryString(uri_.getRawQuery()); + req.setDispatcherType(DispatcherType.INCLUDE); + + FilterChain fc = new CtxFilterChain(servlet, req.getFilterPath(), DispatcherType.INCLUDE); + + fc.doFilter(request, new IncludeResponseWrapper(response)); + + } catch (ServletException e) { + throw e; + } catch (IOException e) { + throw e; + } catch (Exception e) { + throw new ServletException(e); + } finally { + req.close(); + + trace("URIRequestDispatcher.include done"); + } + } + } + + private class ServletDispatcher implements RequestDispatcher + { + private final ServletReg servlet_; + + public ServletDispatcher(ServletReg servlet) + { + servlet_ = servlet; + } + + @Override + public void forward(ServletRequest request, ServletResponse response) + throws ServletException, IOException + { + /* + 9.4 The Forward Method + ... + If the response has been committed, an IllegalStateException + must be thrown. + */ + if (response.isCommitted()) { + throw new IllegalStateException("Response already committed"); + } + + trace("ServletDispatcher.forward"); + + DispatcherType dtype = request.getDispatcherType(); + + Request req; + if (request instanceof Request) { + req = (Request) request; + } else { + req = (Request) request.getAttribute(Request.BARE); + } + + try { + req.setDispatcherType(DispatcherType.FORWARD); + + /* + 9.4 The Forward Method + ... + If output data exists in the response buffer that has not + been committed, the content must be cleared before the + target servlet's service method is called. + */ + response.resetBuffer(); + + servlet_.service(request, response); + + /* + 9.4 The Forward Method + ... + Before the forward method of the RequestDispatcher interface + returns without exception, the response content must be sent + and committed, and closed by the servlet container, unless + the request was put into the asynchronous mode. If an error + occurs in the target of the RequestDispatcher.forward() the + exception may be propagated back through all the calling + filters and servlets and eventually back to the container + */ + if (!request.isAsyncStarted()) { + response.flushBuffer(); + } + + /* + 9.5 Error Handling + + If the servlet that is the target of a request dispatcher + throws a runtime exception or a checked exception of type + ServletException or IOException, it should be propagated + to the calling servlet. All other exceptions should be + wrapped as ServletExceptions and the root cause of the + exception set to the original exception, as it should + not be propagated. + */ + } catch (ServletException e) { + throw e; + } catch (IOException e) { + throw e; + } catch (Exception e) { + throw new ServletException(e); + } finally { + req.setDispatcherType(dtype); + + trace("ServletDispatcher.forward done"); + } + } + + @Override + public void include(ServletRequest request, ServletResponse response) + throws ServletException, IOException + { + trace("ServletDispatcher.include"); + + DispatcherType dtype = request.getDispatcherType(); + + Request req; + if (request instanceof Request) { + req = (Request) request; + } else { + req = (Request) request.getAttribute(Request.BARE); + } + + try { + req.setDispatcherType(DispatcherType.INCLUDE); + + servlet_.service(request, new IncludeResponseWrapper(response)); + + } catch (ServletException e) { + throw e; + } catch (IOException e) { + throw e; + } catch (Exception e) { + throw new ServletException(e); + } finally { + req.setDispatcherType(dtype); + + trace("ServletDispatcher.include done"); + } + } + } + + @Override + public RequestDispatcher getNamedDispatcher(String name) + { + trace("getNamedDispatcher for " + name); + + ServletReg servlet = name2servlet_.get(name); + if (servlet != null) { + return new ServletDispatcher(servlet); + } + + return null; + } + + @Override + public RequestDispatcher getRequestDispatcher(String uriInContext) + { + trace("getRequestDispatcher for " + uriInContext); + try { + return new URIRequestDispatcher(context_path_ + uriInContext); + } catch (URISyntaxException e) { + log("getRequestDispatcher: failed to create dispatcher: " + e); + } + + return null; + } + + public RequestDispatcher getRequestDispatcher(URI uri) + { + trace("getRequestDispatcher for " + uri.getRawPath()); + return new URIRequestDispatcher(uri); + } + + @Override + public String getRealPath(String path) + { + trace("getRealPath for " + path); + + File f = new File(webapp_, path.substring(1)); + + return f.getAbsolutePath(); + } + + @Override + public URL getResource(String path) throws MalformedURLException + { + trace("getResource for " + path); + + File f = new File(webapp_, path.substring(1)); + + if (f.exists()) { + return new URL("file:" + f.getAbsolutePath()); + } + + return null; + } + + @Override + public InputStream getResourceAsStream(String path) + { + trace("getResourceAsStream for " + path); + + try { + File f = new File(webapp_, path.substring(1)); + + return new FileInputStream(f); + } catch (FileNotFoundException e) { + log("getResourceAsStream: failed " + e); + + return null; + } + } + + @Override + public Set getResourcePaths(String path) + { + trace("getResourcePaths for " + path); + + File dir = new File(webapp_, path.substring(1)); + File[] list = dir.listFiles(); + + if (list == null) { + return null; + } + + Set res = new HashSet<>(); + Path root = webapp_.toPath(); + + for (File f : list) { + String r = "/" + root.relativize(f.toPath()); + if (f.isDirectory()) { + r += "/"; + } + + trace("getResourcePaths: " + r); + + res.add(r); + } + + return res; + } + + @Override + public String getServerInfo() + { + trace("getServerInfo: " + server_info_); + return server_info_; + } + + @Override + @Deprecated + public Servlet getServlet(String name) throws ServletException + { + log("getServlet for " + name); + return null; + } + + @SuppressWarnings("unchecked") + @Override + @Deprecated + public Enumeration getServletNames() + { + log("getServletNames"); + return Collections.enumeration(Collections.EMPTY_LIST); + } + + @SuppressWarnings("unchecked") + @Override + @Deprecated + public Enumeration getServlets() + { + log("getServlets"); + return Collections.enumeration(Collections.EMPTY_LIST); + } + + @Override + @Deprecated + public void log(Exception exception, String msg) + { + log(msg, exception); + } + + @Override + public void log(String msg) + { + msg = "Context." + msg; + log(0, msg, msg.length()); + } + + @Override + public void log(String message, Throwable throwable) + { + log(message); + } + + private static native void log(long ctx_ptr, String msg, int msg_len); + + + public static void trace(String msg) + { + msg = "Context." + msg; + trace(0, msg, msg.length()); + } + + private static native void trace(long ctx_ptr, String msg, int msg_len); + + @Override + public String getInitParameter(String name) + { + trace("getInitParameter for " + name); + return init_params_.get(name); + } + + @SuppressWarnings("unchecked") + @Override + public Enumeration getInitParameterNames() + { + trace("getInitParameterNames"); + return Collections.enumeration(Collections.EMPTY_LIST); + } + + @Override + public String getServletContextName() + { + log("getServletContextName"); + return "No Context"; + } + + @Override + public String getContextPath() + { + trace("getContextPath"); + return context_path_; + } + + @Override + public boolean setInitParameter(String name, String value) + { + trace("setInitParameter " + name + " = " + value); + return init_params_.putIfAbsent(name, value) == null; + } + + @Override + public Object getAttribute(String name) + { + trace("getAttribute " + name); + + return attributes_.get(name); + } + + @Override + public Enumeration getAttributeNames() + { + trace("getAttributeNames"); + + Set names = attributes_.keySet(); + return Collections.enumeration(names); + } + + @Override + public void setAttribute(String name, Object object) + { + trace("setAttribute " + name); + + Object prev = attributes_.put(name, object); + + if (ctx_attr_listeners_.isEmpty()) { + return; + } + + ServletContextAttributeEvent scae = new ServletContextAttributeEvent( + this, name, prev == null ? object : prev); + + for (ServletContextAttributeListener l : ctx_attr_listeners_) { + if (prev == null) { + l.attributeAdded(scae); + } else { + l.attributeReplaced(scae); + } + } + } + + @Override + public void removeAttribute(String name) + { + trace("removeAttribute " + name); + + Object value = attributes_.remove(name); + + if (ctx_attr_listeners_.isEmpty()) { + return; + } + + ServletContextAttributeEvent scae = new ServletContextAttributeEvent( + this, name, value); + + for (ServletContextAttributeListener l : ctx_attr_listeners_) { + l.attributeRemoved(scae); + } + } + + @Override + public FilterRegistration.Dynamic addFilter(String name, + Class filterClass) + { + log("addFilter " + name + ", " + filterClass.getName()); + + checkContextState(); + + FilterReg reg = new FilterReg(name, filterClass); + filters_.add(reg); + name2filter_.put(name, reg); + return reg; + } + + @Override + public FilterRegistration.Dynamic addFilter(String name, Filter filter) + { + log("addFilter " + name); + + checkContextState(); + + FilterReg reg = new FilterReg(name, filter); + filters_.add(reg); + name2filter_.put(name, reg); + return reg; + } + + @Override + public FilterRegistration.Dynamic addFilter(String name, String className) + { + log("addFilter " + name + ", " + className); + + checkContextState(); + + FilterReg reg = new FilterReg(name, className); + filters_.add(reg); + name2filter_.put(name, reg); + return reg; + } + + @Override + public ServletRegistration.Dynamic addServlet(String name, + Class servletClass) + { + log("addServlet " + name + ", " + servletClass.getName()); + + checkContextState(); + + ServletReg reg = null; + try { + reg = new ServletReg(name, servletClass); + servlets_.add(reg); + name2servlet_.put(name, reg); + } catch(Exception e) { + System.err.println("addServlet: exception caught: " + e.toString()); + } + + return reg; + } + + @Override + public ServletRegistration.Dynamic addServlet(String name, Servlet servlet) + { + log("addServlet " + name); + + checkContextState(); + + ServletReg reg = null; + try { + reg = new ServletReg(name, servlet); + servlets_.add(reg); + name2servlet_.put(name, reg); + } catch(Exception e) { + System.err.println("addServlet: exception caught: " + e.toString()); + } + + return reg; + } + + @Override + public ServletRegistration.Dynamic addServlet(String name, String className) + { + log("addServlet " + name + ", " + className); + + checkContextState(); + + ServletReg reg = null; + try { + reg = new ServletReg(name, className); + servlets_.add(reg); + name2servlet_.put(name, reg); + } catch(Exception e) { + System.err.println("addServlet: exception caught: " + e.toString()); + } + + return reg; + } + + @Override + public ServletRegistration.Dynamic addJspFile(String jspName, String jspFile) + { + log("addJspFile: " + jspName + " " + jspFile); + + return null; + } + + @Override + public T createFilter(Class c) throws ServletException + { + log("createFilter " + c.getName()); + + checkContextState(); + + try { + Constructor ctor = c.getConstructor(); + T filter = ctor.newInstance(); + return filter; + } catch (Exception e) { + log("createFilter() failed " + e); + + throw new ServletException(e); + } + } + + @Override + public T createServlet(Class c) throws ServletException + { + log("createServlet " + c.getName()); + + checkContextState(); + + try { + Constructor ctor = c.getConstructor(); + T servlet = ctor.newInstance(); + return servlet; + } catch (Exception e) { + log("createServlet() failed " + e); + + throw new ServletException(e); + } + } + + @Override + public Set getDefaultSessionTrackingModes() + { + log("getDefaultSessionTrackingModes"); + + return default_session_tracking_modes_; + } + + @Override + public Set getEffectiveSessionTrackingModes() + { + log("getEffectiveSessionTrackingModes"); + + return session_tracking_modes_; + } + + public boolean isSessionIdValid(String id) + { + synchronized (sessions_) { + return sessions_.containsKey(id); + } + } + + public Session getSession(String id) + { + synchronized (sessions_) { + Session s = sessions_.get(id); + + if (s != null) { + s.accessed(); + + if (s.checkTimeOut()) { + s.invalidate(); + return null; + } + } + + return s; + } + } + + public Session createSession() + { + Session session = new Session(this, generateSessionId(), + sess_attr_proxy_, session_timeout_ * 60); + + if (!sess_listeners_.isEmpty()) + { + HttpSessionEvent event = new HttpSessionEvent(session); + + for (HttpSessionListener l : sess_listeners_) + { + l.sessionCreated(event); + } + } + + synchronized (sessions_) { + sessions_.put(session.getId(), session); + + return session; + } + } + + public void invalidateSession(Session session) + { + synchronized (sessions_) { + sessions_.remove(session.getId()); + } + + if (!sess_listeners_.isEmpty()) + { + HttpSessionEvent event = new HttpSessionEvent(session); + + for (int i = sess_listeners_.size() - 1; i >= 0; i--) + { + sess_listeners_.get(i).sessionDestroyed(event); + } + } + } + + public void changeSessionId(Session session) + { + String old_id; + + synchronized (sessions_) { + old_id = session.getId(); + sessions_.remove(old_id); + + session.setId(generateSessionId()); + + sessions_.put(session.getId(), session); + } + + if (!sess_id_listeners_.isEmpty()) + { + HttpSessionEvent event = new HttpSessionEvent(session); + for (HttpSessionIdListener l : sess_id_listeners_) + { + l.sessionIdChanged(event, old_id); + } + } + } + + private String generateSessionId() + { + return UUID.randomUUID().toString(); + } + + @Override + public FilterRegistration getFilterRegistration(String filterName) + { + log("getFilterRegistration " + filterName); + return name2filter_.get(filterName); + } + + @Override + public Map getFilterRegistrations() + { + log("getFilterRegistrations"); + return name2filter_; + } + + @Override + public ServletRegistration getServletRegistration(String servletName) + { + log("getServletRegistration " + servletName); + return name2servlet_.get(servletName); + } + + @Override + public Map getServletRegistrations() + { + log("getServletRegistrations"); + return name2servlet_; + } + + @Override + public SessionCookieConfig getSessionCookieConfig() + { + log("getSessionCookieConfig"); + + return session_cookie_config_; + } + + @Override + public void setSessionTrackingModes(Set modes) + { + log("setSessionTrackingModes"); + + session_tracking_modes_ = modes; + } + + @Override + public void addListener(String className) + { + trace("addListener " + className); + + checkContextState(); + + if (listener_classnames_.contains(className)) { + log("addListener " + className + " already added as listener"); + return; + } + + try { + Class cls = loader_.loadClass(className); + + Constructor ctor = cls.getConstructor(); + EventListener listener = (EventListener) ctor.newInstance(); + + addListener(listener); + + listener_classnames_.add(className); + } catch (Exception e) { + log("addListener: exception caught: " + e.toString()); + } + } + + @Override + public void addListener(T t) + { + trace("addListener " + t.getClass().getName()); + + checkContextState(); + + for (int i = 0; i < LISTENER_TYPES.length; i++) { + Class c = LISTENER_TYPES[i]; + if (c.isAssignableFrom(t.getClass())) { + trace("addListener: assignable to " + c.getName()); + } + } + + if (t instanceof ServletContextListener) { + ctx_listeners_.add((ServletContextListener) t); + } + + if (t instanceof ServletContextAttributeListener) { + ctx_attr_listeners_.add((ServletContextAttributeListener) t); + } + + if (t instanceof ServletRequestListener) { + req_init_listeners_.add((ServletRequestListener) t); + req_destroy_listeners_.add(0, (ServletRequestListener) t); + } + + if (t instanceof ServletRequestAttributeListener) { + req_attr_listeners_.add((ServletRequestAttributeListener) t); + } + + if (t instanceof HttpSessionAttributeListener) { + sess_attr_listeners_.add((HttpSessionAttributeListener) t); + } + + if (t instanceof HttpSessionIdListener) { + sess_id_listeners_.add((HttpSessionIdListener) t); + } + + if (t instanceof HttpSessionListener) { + sess_listeners_.add((HttpSessionListener) t); + } + } + + @Override + public void addListener(Class listenerClass) + { + String className = listenerClass.getName(); + trace("addListener " + className); + + checkContextState(); + + if (listener_classnames_.contains(className)) { + log("addListener " + className + " already added as listener"); + return; + } + + try { + Constructor ctor = listenerClass.getConstructor(); + EventListener listener = (EventListener) ctor.newInstance(); + + addListener(listener); + + listener_classnames_.add(className); + } catch (Exception e) { + log("addListener: exception caught: " + e.toString()); + } + } + + @Override + public T createListener(Class clazz) + throws ServletException + { + trace("createListener " + clazz.getName()); + + checkContextState(); + + try + { + return clazz.getDeclaredConstructor().newInstance(); + } + catch (Exception e) + { + throw new ServletException(e); + } + } + + @Override + public ClassLoader getClassLoader() + { + trace("getClassLoader"); + return loader_; + } + + @Override + public int getEffectiveMajorVersion() + { + log("getEffectiveMajorVersion"); + return SERVLET_MAJOR_VERSION; + } + + @Override + public int getEffectiveMinorVersion() + { + log("getEffectiveMinorVersion"); + return SERVLET_MINOR_VERSION; + } + + private final List taglibs_ = new ArrayList<>(); + private final List prop_groups_ = new ArrayList<>(); + + private class JspConfig implements JspConfigDescriptor + { + @Override + public Collection getTaglibs() + { + trace("getTaglibs"); + return taglibs_; + } + + @Override + public Collection getJspPropertyGroups() + { + trace("getJspPropertyGroups"); + return prop_groups_; + } + } + + private final JspConfig jsp_config_ = new JspConfig(); + + @Override + public JspConfigDescriptor getJspConfigDescriptor() + { + trace("getJspConfigDescriptor"); + + return jsp_config_; + } + + @Override + public void declareRoles(String... roleNames) + { + log("declareRoles"); + //LOG.warn(__unimplmented); + } + + @Override + public String getVirtualServerName() + { + log("getVirtualServerName"); + return null; + } + + @Override + public int getSessionTimeout() + { + trace("getSessionTimeout"); + + return session_timeout_; + } + + @Override + public void setSessionTimeout(int sessionTimeout) + { + trace("setSessionTimeout: " + sessionTimeout); + + session_timeout_ = sessionTimeout; + } + + @Override + public String getRequestCharacterEncoding() + { + log("getRequestCharacterEncoding"); + + return null; + } + + @Override + public void setRequestCharacterEncoding(String encoding) + { + log("setRequestCharacterEncoding: " + encoding); + } + + @Override + public String getResponseCharacterEncoding() + { + log("getResponseCharacterEncoding"); + + return null; + } + + @Override + public void setResponseCharacterEncoding(String encoding) + { + log("setResponseCharacterEncoding: " + encoding); + } + + public ServletRequestAttributeListener getRequestAttributeListener() + { + return req_attr_proxy_; + } +} diff --git a/src/java/nginx/unit/DynamicDispatcherRequest.java b/src/java/nginx/unit/DynamicDispatcherRequest.java new file mode 100644 index 00000000..af0747eb --- /dev/null +++ b/src/java/nginx/unit/DynamicDispatcherRequest.java @@ -0,0 +1,8 @@ +package nginx.unit; + +import javax.servlet.DispatcherType; + +public interface DynamicDispatcherRequest +{ + public void setDispatcherType(DispatcherType type); +} diff --git a/src/java/nginx/unit/DynamicPathRequest.java b/src/java/nginx/unit/DynamicPathRequest.java new file mode 100644 index 00000000..efc1bcd1 --- /dev/null +++ b/src/java/nginx/unit/DynamicPathRequest.java @@ -0,0 +1,15 @@ +package nginx.unit; + +public interface DynamicPathRequest + extends DynamicDispatcherRequest +{ + public void setServletPath(String servlet_path, String path_info); + + public void setServletPath(String filter_path, String servlet_path, String path_info); + + public void setRequestURI(String uri); + + public void setQueryString(String query); + + public String getFilterPath(); +} diff --git a/src/java/nginx/unit/ForwardRequestWrapper.java b/src/java/nginx/unit/ForwardRequestWrapper.java new file mode 100644 index 00000000..f88b6aef --- /dev/null +++ b/src/java/nginx/unit/ForwardRequestWrapper.java @@ -0,0 +1,150 @@ +package nginx.unit; + +import java.util.List; +import java.util.Map; + +import javax.servlet.DispatcherType; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServletRequest; + +import org.eclipse.jetty.util.MultiMap; +import org.eclipse.jetty.util.UrlEncoded; + +public class ForwardRequestWrapper implements DynamicPathRequest +{ + private final Request request_; + + private final boolean keep_attrs; + + private final String orig_filter_path; + private final String orig_servlet_path; + private final String orig_path_info; + private final String orig_uri; + private final String orig_context_path; + private final String orig_query; + + private final DispatcherType orig_dtype; + + private MultiMap orig_parameters; + + public ForwardRequestWrapper(ServletRequest request) + { + if (request instanceof Request) { + request_ = (Request) request; + } else { + request_ = (Request) request.getAttribute(Request.BARE); + } + + keep_attrs = request_.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI) != null; + + orig_dtype = request_.getDispatcherType(); + + orig_filter_path = request_.getFilterPath(); + orig_servlet_path = request_.getServletPath(); + orig_path_info = request_.getPathInfo(); + orig_uri = request_.getRequestURI(); + orig_context_path = request_.getContextPath(); + orig_query = request_.getQueryString(); + } + + @Override + public void setDispatcherType(DispatcherType type) + { + request_.setDispatcherType(type); + + /* + 9.4.2 Forwarded Request Parameters + ... + Note that these attributes must always reflect the information in + the original request even under the situation that multiple + forwards and subsequent includes are called. + */ + + if (keep_attrs) { + return; + } + + /* + 9.4.2 Forwarded Request Parameters + ... + The values of these attributes must be equal to the return values + of the HttpServletRequest methods getRequestURI, getContextPath, + getServletPath, getPathInfo, getQueryString respectively, invoked + on the request object passed to the first servlet object in the + call chain that received the request from the client. + */ + + request_.setAttribute_(RequestDispatcher.FORWARD_SERVLET_PATH, orig_servlet_path); + request_.setAttribute_(RequestDispatcher.FORWARD_PATH_INFO, orig_path_info); + request_.setAttribute_(RequestDispatcher.FORWARD_REQUEST_URI, orig_uri); + request_.setAttribute_(RequestDispatcher.FORWARD_CONTEXT_PATH, orig_context_path); + request_.setAttribute_(RequestDispatcher.FORWARD_QUERY_STRING, orig_query); + } + + @Override + public void setServletPath(String servlet_path, String path_info) + { + request_.setServletPath(servlet_path, path_info); + } + + @Override + public void setServletPath(String filter_path, String servlet_path, String path_info) + { + request_.setServletPath(filter_path, servlet_path, path_info); + } + + @Override + public void setRequestURI(String uri) + { + request_.setRequestURI(uri); + } + + @Override + public void setQueryString(String query) + { + if (query != null) { + orig_parameters = request_.getParameters(); + + MultiMap parameters = new MultiMap<>(); + UrlEncoded.decodeUtf8To(query, parameters); + + for (Map.Entry> e: orig_parameters.entrySet()) { + parameters.addValues(e.getKey(), e.getValue()); + } + + request_.setParameters(parameters); + + request_.setQueryString(query); + } + } + + @Override + public String getFilterPath() + { + return request_.getFilterPath(); + } + + public void close() + { + request_.setDispatcherType(orig_dtype); + + request_.setRequestURI(orig_uri); + request_.setServletPath(orig_filter_path, orig_servlet_path, orig_path_info); + request_.setQueryString(orig_query); + + if (orig_parameters != null) { + request_.setParameters(orig_parameters); + } + + if (keep_attrs) { + return; + } + + request_.setAttribute_(RequestDispatcher.FORWARD_SERVLET_PATH, null); + request_.setAttribute_(RequestDispatcher.FORWARD_PATH_INFO, null); + request_.setAttribute_(RequestDispatcher.FORWARD_REQUEST_URI, null); + request_.setAttribute_(RequestDispatcher.FORWARD_CONTEXT_PATH, null); + request_.setAttribute_(RequestDispatcher.FORWARD_QUERY_STRING, null); + } +} diff --git a/src/java/nginx/unit/HeaderNamesEnumeration.java b/src/java/nginx/unit/HeaderNamesEnumeration.java new file mode 100644 index 00000000..d81b8778 --- /dev/null +++ b/src/java/nginx/unit/HeaderNamesEnumeration.java @@ -0,0 +1,42 @@ +package nginx.unit; + +import java.lang.String; +import java.util.Enumeration; +import java.util.NoSuchElementException; + +public class HeaderNamesEnumeration implements Enumeration { + + private long headers_ptr; + private long size; + private long pos = 0; + + public HeaderNamesEnumeration(long _headers_ptr, long _size) { + headers_ptr = _headers_ptr; + size = _size; + } + + @Override + public boolean hasMoreElements() + { + if (pos >= size) { + return false; + } + + pos = nextElementPos(headers_ptr, size, pos); + return pos < size; + } + + static private native long nextElementPos(long headers_ptr, long size, long pos); + + @Override + public String nextElement() + { + if (pos >= size) { + throw new NoSuchElementException(); + } + + return nextElement(headers_ptr, size, pos++); + } + + static private native String nextElement(long headers_ptr, long size, long pos); +} diff --git a/src/java/nginx/unit/HeadersEnumeration.java b/src/java/nginx/unit/HeadersEnumeration.java new file mode 100644 index 00000000..31b5ae24 --- /dev/null +++ b/src/java/nginx/unit/HeadersEnumeration.java @@ -0,0 +1,40 @@ +package nginx.unit; + +import java.lang.String; +import java.util.Enumeration; + +public class HeadersEnumeration implements Enumeration { + + private long headers_ptr; + private long size; + private long initial_pos; + private long pos; + + public HeadersEnumeration(long _headers_ptr, long _size, long _initial_pos) { + headers_ptr = _headers_ptr; + size = _size; + initial_pos = _initial_pos; + pos = _initial_pos; + } + + @Override + public boolean hasMoreElements() + { + if (pos >= size) { + return false; + } + + pos = nextElementPos(headers_ptr, size, initial_pos, pos); + return pos < size; + } + + static private native long nextElementPos(long headers_ptr, long size, long initial_pos, long pos); + + @Override + public String nextElement() + { + return nextElement(headers_ptr, size, initial_pos, pos++); + } + + static private native String nextElement(long headers_ptr, long size, long initial_pos, long pos); +} diff --git a/src/java/nginx/unit/IncludeRequestWrapper.java b/src/java/nginx/unit/IncludeRequestWrapper.java new file mode 100644 index 00000000..67a51b24 --- /dev/null +++ b/src/java/nginx/unit/IncludeRequestWrapper.java @@ -0,0 +1,88 @@ +package nginx.unit; + +import javax.servlet.DispatcherType; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletRequest; + +public class IncludeRequestWrapper implements DynamicPathRequest +{ + private final Request request_; + + private final Object orig_servlet_path_attr; + private final Object orig_path_info_attr; + private final Object orig_uri_attr; + private final Object orig_context_path_attr; + private final Object orig_query_string_attr; + + private final DispatcherType orig_dtype; + + private String filter_path_; + + public IncludeRequestWrapper(ServletRequest request) + { + if (request instanceof Request) { + request_ = (Request) request; + } else { + request_ = (Request) request.getAttribute(Request.BARE); + } + + orig_servlet_path_attr = request_.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH); + orig_path_info_attr = request_.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO); + orig_uri_attr = request_.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI); + orig_context_path_attr = request_.getAttribute(RequestDispatcher.INCLUDE_CONTEXT_PATH); + orig_query_string_attr = request_.getAttribute(RequestDispatcher.INCLUDE_QUERY_STRING); + + orig_dtype = request_.getDispatcherType(); + + request_.setAttribute_(RequestDispatcher.INCLUDE_CONTEXT_PATH, request_.getContextPath()); + } + + @Override + public void setDispatcherType(DispatcherType type) + { + request_.setDispatcherType(type); + } + + @Override + public void setServletPath(String servlet_path, String path_info) + { + setServletPath(servlet_path, servlet_path, path_info); + } + + @Override + public void setServletPath(String filter_path, String servlet_path, String path_info) + { + request_.setAttribute_(RequestDispatcher.INCLUDE_SERVLET_PATH, servlet_path); + request_.setAttribute_(RequestDispatcher.INCLUDE_PATH_INFO, path_info); + filter_path_ = filter_path; + } + + @Override + public void setRequestURI(String uri) + { + request_.setAttribute_(RequestDispatcher.INCLUDE_REQUEST_URI, uri); + } + + @Override + public void setQueryString(String query) + { + request_.setAttribute_(RequestDispatcher.INCLUDE_QUERY_STRING, query); + } + + @Override + public String getFilterPath() + { + return filter_path_; + } + + public void close() + { + request_.setDispatcherType(orig_dtype); + + request_.setAttribute_(RequestDispatcher.INCLUDE_SERVLET_PATH, orig_servlet_path_attr); + request_.setAttribute_(RequestDispatcher.INCLUDE_PATH_INFO, orig_path_info_attr); + request_.setAttribute_(RequestDispatcher.INCLUDE_REQUEST_URI, orig_uri_attr); + request_.setAttribute_(RequestDispatcher.INCLUDE_CONTEXT_PATH, orig_context_path_attr); + request_.setAttribute_(RequestDispatcher.INCLUDE_QUERY_STRING, orig_query_string_attr); + } +} diff --git a/src/java/nginx/unit/IncludeResponseWrapper.java b/src/java/nginx/unit/IncludeResponseWrapper.java new file mode 100644 index 00000000..4537114a --- /dev/null +++ b/src/java/nginx/unit/IncludeResponseWrapper.java @@ -0,0 +1,117 @@ +package nginx.unit; + +import java.io.IOException; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +import javax.servlet.ServletResponse; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; + +public class IncludeResponseWrapper extends HttpServletResponseWrapper { + private static final Charset UTF_8 = StandardCharsets.UTF_8; + + public IncludeResponseWrapper(ServletResponse response) + { + super((HttpServletResponse) response); + } + + @Override + public void addCookie(Cookie cookie) + { + trace("addCookie: " + cookie.getName() + "=" + cookie.getValue()); + } + + @Override + public void addDateHeader(String name, long date) + { + trace("addDateHeader: " + name + ": " + date); + } + + @Override + public void addHeader(String name, String value) + { + trace("addHeader: " + name + ": " + value); + } + + @Override + public void addIntHeader(String name, int value) + { + trace("addIntHeader: " + name + ": " + value); + } + + @Override + public void sendRedirect(String location) throws IOException + { + trace("sendRedirect: " + location); + } + + @Override + public void setDateHeader(String name, long date) + { + trace("setDateHeader: " + name + ": " + date); + } + + @Override + public void setHeader(String name, String value) + { + trace("setHeader: " + name + ": " + value); + } + + @Override + public void setIntHeader(String name, int value) + { + trace("setIntHeader: " + name + ": " + value); + } + + @Override + public void setStatus(int sc) + { + trace("setStatus: " + sc); + } + + @Override + @Deprecated + public void setStatus(int sc, String sm) + { + trace("setStatus: " + sc + "; " + sm); + } + + @Override + public void reset() + { + trace("reset"); + } + + @Override + public void setCharacterEncoding(String charset) + { + trace("setCharacterEncoding " + charset); + } + + @Override + public void setContentLength(int len) + { + trace("setContentLength: " + len); + } + + @Override + public void setContentLengthLong(long len) + { + trace("setContentLengthLong: " + len); + } + + @Override + public void setContentType(String type) + { + trace("setContentType: " + type); + } + + private void trace(String msg) + { + msg = "IncludeResponse." + msg; + Response.trace(0, msg.getBytes(UTF_8)); + } +} diff --git a/src/java/nginx/unit/InitParams.java b/src/java/nginx/unit/InitParams.java new file mode 100644 index 00000000..2f5dcbf9 --- /dev/null +++ b/src/java/nginx/unit/InitParams.java @@ -0,0 +1,7 @@ +package nginx.unit; + +public interface InitParams { + public boolean setInitParameter(String name, String value); + + public String getInitParameter(String name); +} diff --git a/src/java/nginx/unit/InputStream.java b/src/java/nginx/unit/InputStream.java new file mode 100644 index 00000000..6fe72ace --- /dev/null +++ b/src/java/nginx/unit/InputStream.java @@ -0,0 +1,90 @@ +package nginx.unit; + +import java.io.IOException; + +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; + +public class InputStream extends ServletInputStream { + + private long req_info_ptr; + + public InputStream(long ptr) + { + req_info_ptr = ptr; + } + + @Override + public int readLine(byte[] b, int off, int len) throws IOException { + + if (len <= 0) { + return 0; + } + return readLine(req_info_ptr, b, off, len); + } + + private static native int readLine(long req_info_ptr, byte[] b, int off, int len); + + + @Override + public boolean isFinished() + { + return isFinished(req_info_ptr); + } + + private static native boolean isFinished(long req_info_ptr); + + + @Override + public boolean isReady() + { + return true; + } + + + @Override + public void setReadListener(ReadListener listener) + { + } + + + @Override + public int read() throws IOException + { + return read(req_info_ptr); + } + + private static native int read(long req_info_ptr); + + + @Override + public int read(byte b[], int off, int len) throws IOException { + if (b == null) { + throw new NullPointerException(); + } else if (off < 0 || len < 0 || len > b.length - off) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return 0; + } + + return read(req_info_ptr, b, off, len); + } + + private static native int read(long req_info_ptr, byte b[], int off, int len); + + + @Override + public long skip(long n) throws IOException { + return skip(req_info_ptr, n); + } + + private static native long skip(long req_info_ptr, long n); + + + @Override + public int available() throws IOException { + return available(req_info_ptr); + } + + private static native int available(long req_info_ptr); +} diff --git a/src/java/nginx/unit/JspPropertyGroup.java b/src/java/nginx/unit/JspPropertyGroup.java new file mode 100644 index 00000000..2df27718 --- /dev/null +++ b/src/java/nginx/unit/JspPropertyGroup.java @@ -0,0 +1,169 @@ +package nginx.unit; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import javax.servlet.descriptor.JspPropertyGroupDescriptor; + +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +public class JspPropertyGroup implements JspPropertyGroupDescriptor +{ + private final List url_patterns_ = new ArrayList<>(); + private String el_ignored_ = null; + private String page_encoding_ = null; + private String scripting_invalid_ = null; + private String is_xml_ = null; + private final List include_preludes_ = new ArrayList<>(); + private final List include_codas_ = new ArrayList<>(); + + private String deffered_syntax_allowed_as_literal_ = null; + private String trim_directive_whitespaces_ = null; + private String default_content_type_ = null; + private String buffer_ = null; + private String error_on_undeclared_namespace_ = null; + + public JspPropertyGroup(NodeList nodes) + { + for (int i = 0; i < nodes.getLength(); i++) { + Node node = nodes.item(i); + String tag_name = node.getNodeName(); + + if (tag_name.equals("url-pattern")) { + url_patterns_.add(node.getTextContent().trim()); + continue; + } + + if (tag_name.equals("el-ignored")) { + el_ignored_ = node.getTextContent().trim(); + continue; + } + + if (tag_name.equals("page-encoding")) { + page_encoding_ = node.getTextContent().trim(); + continue; + } + + if (tag_name.equals("scripting-invalid")) { + scripting_invalid_ = node.getTextContent().trim(); + continue; + } + + if (tag_name.equals("is-xml")) { + is_xml_ = node.getTextContent().trim(); + continue; + } + + if (tag_name.equals("include-prelude")) { + include_preludes_.add(node.getTextContent().trim()); + continue; + } + + if (tag_name.equals("include-coda")) { + include_codas_.add(node.getTextContent().trim()); + continue; + } + + if (tag_name.equals("deferred-syntax-allowed-as-literal")) { + deffered_syntax_allowed_as_literal_ = node.getTextContent().trim(); + continue; + } + + if (tag_name.equals("trim-directive-whitespaces")) { + trim_directive_whitespaces_ = node.getTextContent().trim(); + continue; + } + + if (tag_name.equals("default-content-type")) { + default_content_type_ = node.getTextContent().trim(); + continue; + } + + if (tag_name.equals("buffer")) { + buffer_ = node.getTextContent().trim(); + continue; + } + + if (tag_name.equals("error-on-undeclared-namespace")) { + error_on_undeclared_namespace_ = node.getTextContent().trim(); + continue; + } + } + + } + + @Override + public Collection getUrlPatterns() + { + return new ArrayList<>(url_patterns_); + } + + @Override + public String getElIgnored() + { + return el_ignored_; + } + + @Override + public String getPageEncoding() + { + return page_encoding_; + } + + @Override + public String getScriptingInvalid() + { + return scripting_invalid_; + } + + @Override + public String getIsXml() + { + return is_xml_; + } + + @Override + public Collection getIncludePreludes() + { + return new ArrayList<>(include_preludes_); + } + + @Override + public Collection getIncludeCodas() + { + return new ArrayList<>(include_codas_); + } + + @Override + public String getDeferredSyntaxAllowedAsLiteral() + { + return deffered_syntax_allowed_as_literal_; + } + + @Override + public String getTrimDirectiveWhitespaces() + { + return trim_directive_whitespaces_; + } + + @Override + public String getDefaultContentType() + { + return default_content_type_; + } + + @Override + public String getBuffer() + { + return buffer_; + } + + @Override + public String getErrorOnUndeclaredNamespace() + { + return error_on_undeclared_namespace_; + } +} + diff --git a/src/java/nginx/unit/OutputStream.java b/src/java/nginx/unit/OutputStream.java new file mode 100644 index 00000000..68af3423 --- /dev/null +++ b/src/java/nginx/unit/OutputStream.java @@ -0,0 +1,68 @@ +package nginx.unit; + +import java.io.IOException; + +import javax.servlet.ServletOutputStream; +import javax.servlet.WriteListener; + +public class OutputStream extends ServletOutputStream { + + private long req_info_ptr; + + public OutputStream(long ptr) { + req_info_ptr = ptr; + } + + @Override + public void write(int b) throws IOException + { + write(req_info_ptr, b); + } + + private static native void write(long req_info_ptr, int b); + + + @Override + public void write(byte b[], int off, int len) throws IOException + { + if (b == null) { + throw new NullPointerException(); + } else if ((off < 0) || (off > b.length) || (len < 0) || + ((off + len) > b.length) || ((off + len) < 0)) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return; + } + + write(req_info_ptr, b, off, len); + } + + private static native void write(long req_info_ptr, byte b[], int off, int len); + + @Override + public void flush() + { + flush(req_info_ptr); + } + + private static native void flush(long req_info_ptr); + + @Override + public void close() + { + close(req_info_ptr); + } + + private static native void close(long req_info_ptr); + + @Override + public boolean isReady() + { + return true; + } + + @Override + public void setWriteListener(WriteListener listener) + { + } +} diff --git a/src/java/nginx/unit/Request.java b/src/java/nginx/unit/Request.java new file mode 100644 index 00000000..dc73c656 --- /dev/null +++ b/src/java/nginx/unit/Request.java @@ -0,0 +1,1123 @@ +package nginx.unit; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.IOException; +import java.io.UnsupportedEncodingException; + +import java.lang.IllegalArgumentException; +import java.lang.IllegalStateException; +import java.lang.Object; +import java.lang.String; +import java.lang.StringBuffer; + +import java.net.URI; +import java.net.URISyntaxException; + +import java.text.ParseException; +import java.text.SimpleDateFormat; + +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import java.security.Principal; + +import javax.servlet.AsyncContext; +import javax.servlet.DispatcherType; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletInputStream; +import javax.servlet.ServletRequest; +import javax.servlet.ServletRequestAttributeEvent; +import javax.servlet.ServletRequestAttributeListener; +import javax.servlet.ServletResponse; +import javax.servlet.SessionTrackingMode; +import javax.servlet.SessionCookieConfig; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpUpgradeHandler; +import javax.servlet.http.Part; + +import org.eclipse.jetty.util.MultiMap; +import org.eclipse.jetty.util.UrlEncoded; +import org.eclipse.jetty.util.StringUtil; + +import org.eclipse.jetty.server.CookieCutter; +import org.eclipse.jetty.http.MimeTypes; + +public class Request implements HttpServletRequest, DynamicPathRequest +{ + private final Context context; + private final long req_info_ptr; + private final long req_ptr; + + protected String authType = null; + + protected boolean cookiesParsed = false; + + protected CookieCutter cookies = null; + + private final Map attributes = new HashMap<>(); + + private MultiMap parameters = null; + + private final String context_path; + private String filter_path = null; + private String servlet_path = null; + private String path_info = null; + private String request_uri = null; + private String query_string = null; + private boolean query_string_valid = false; + + private DispatcherType dispatcher_type = DispatcherType.REQUEST; + + private String characterEncoding = null; + + /** + * The only date format permitted when generating HTTP headers. + */ + public static final String RFC1123_DATE = + "EEE, dd MMM yyyy HH:mm:ss zzz"; + + private static final SimpleDateFormat formats[] = { + new SimpleDateFormat(RFC1123_DATE, Locale.US), + new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US), + new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US) + }; + + private InputStream inputStream = null; + private BufferedReader reader = null; + + private boolean request_session_id_parsed = false; + private String request_session_id = null; + private boolean request_session_id_from_cookie = false; + private boolean request_session_id_from_url = false; + private Session session = null; + + private final ServletRequestAttributeListener attr_listener; + + public static final String BARE = "nginx.unit.request.bare"; + + public Request(Context ctx, long req_info, long req) { + context = ctx; + req_info_ptr = req_info; + req_ptr = req; + + attr_listener = context.getRequestAttributeListener(); + context_path = context.getContextPath(); + } + + @Override + public boolean authenticate(HttpServletResponse response) + throws IOException, ServletException + { + log("authenticate"); + + if (response.isCommitted()) { + throw new IllegalStateException(); + } + + return false; + } + + @Override + public String getAuthType() + { + log("getAuthType"); + + return authType; + } + + @Override + public String getContextPath() + { + trace("getContextPath: " + context_path); + + return context_path; + } + + @Override + public Cookie[] getCookies() + { + trace("getCookies"); + + if (!cookiesParsed) { + parseCookies(); + } + + //Javadoc for Request.getCookies() stipulates null for no cookies + if (cookies == null || cookies.getCookies().length == 0) { + return null; + } + + return cookies.getCookies(); + } + + protected void parseCookies() + { + cookiesParsed = true; + + cookies = new CookieCutter(); + + Enumeration cookie_headers = getHeaders("Cookie"); + + while (cookie_headers.hasMoreElements()) { + cookies.addCookieField(cookie_headers.nextElement()); + } + } + + @Override + public long getDateHeader(String name) + { + trace("getDateHeader: " + name); + + String value = getHeader(name); + if (value == null) { + return -1L; + } + + long res = parseDate(value); + if (res == -1L) { + throw new IllegalArgumentException(value); + } + + return res; + } + + protected long parseDate(String value) + { + Date date = null; + for (int i = 0; (date == null) && (i < formats.length); i++) { + try { + date = formats[i].parse(value); + } catch (ParseException e) { + // Ignore + } + } + if (date == null) { + return -1L; + } + return date.getTime(); + } + + @Override + public String getHeader(String name) + { + String res = getHeader(req_ptr, name, name.length()); + + trace("getHeader: " + name + " = '" + res + "'"); + + return res; + } + + private static native String getHeader(long req_ptr, String name, int name_len); + + + @Override + public Enumeration getHeaderNames() + { + trace("getHeaderNames"); + + return getHeaderNames(req_ptr); + } + + private static native Enumeration getHeaderNames(long req_ptr); + + + @Override + public Enumeration getHeaders(String name) + { + trace("getHeaders: " + name); + + return getHeaders(req_ptr, name, name.length()); + } + + private static native Enumeration getHeaders(long req_ptr, String name, int name_len); + + + @Override + public int getIntHeader(String name) + { + trace("getIntHeader: " + name); + + return getIntHeader(req_ptr, name, name.length()); + } + + private static native int getIntHeader(long req_ptr, String name, int name_len); + + + @Override + public String getMethod() + { + trace("getMethod"); + + return getMethod(req_ptr); + } + + private static native String getMethod(long req_ptr); + + + @Override + public Part getPart(String name) throws IOException, ServletException + { + log("getPart: " + name); + + return null; + } + + @Override + public Collection getParts() throws IOException, ServletException + { + log("getParts"); + + return Collections.emptyList(); + } + + @Override + public String getPathInfo() + { + trace("getPathInfo: " + path_info); + + return path_info; + } + + @Override + public String getPathTranslated() + { + trace("getPathTranslated"); + + if (path_info == null) { + return null; + } + + return context.getRealPath(path_info); + } + + @Override + public String getQueryString() + { + if (!query_string_valid) { + query_string = getQueryString(req_ptr); + query_string_valid = true; + } + + trace("getQueryString: " + query_string); + + return query_string; + } + + private static native String getQueryString(long req_ptr); + + @Override + public void setQueryString(String query) + { + trace("setQueryString: " + query); + + query_string = query; + query_string_valid = true; + } + + @Override + public String getRemoteUser() + { + log("getRemoteUser"); + + /* TODO */ + return null; + } + + @Override + public String getRequestedSessionId() + { + trace("getRequestedSessionId"); + + if (!request_session_id_parsed) { + parseRequestSessionId(); + } + + return request_session_id; + } + + private void parseRequestSessionId() + { + request_session_id_parsed = true; + + Cookie[] cookies = getCookies(); + if (cookies == null) { + return; + } + + if (context.getEffectiveSessionTrackingModes().contains( + SessionTrackingMode.COOKIE)) + { + final String name = context.getSessionCookieConfig().getName(); + + for (Cookie c : cookies) { + if (c.getName().equals(name)) { + request_session_id = c.getValue(); + request_session_id_from_cookie = true; + + return; + } + } + } + } + + @Override + public String getRequestURI() + { + if (request_uri == null) { + request_uri = getRequestURI(req_ptr); + } + + trace("getRequestURI: " + request_uri); + + return request_uri; + } + + private static native String getRequestURI(long req_ptr); + + + @Override + public void setRequestURI(String uri) + { + trace("setRequestURI: " + uri); + + request_uri = uri; + } + + + @Override + public StringBuffer getRequestURL() + { + String host = getHeader("Host"); + String uri = getRequestURI(); + StringBuffer res = new StringBuffer("http://" + host + uri); + + trace("getRequestURL: " + res); + + return res; + } + + @Override + public String getServletPath() + { + trace("getServletPath: " + servlet_path); + + return servlet_path; + } + + @Override + public void setServletPath(String servlet_path, String path_info) + { + trace("setServletPath: " + servlet_path); + + this.filter_path = servlet_path; + this.servlet_path = servlet_path; + this.path_info = path_info; + } + + @Override + public void setServletPath(String filter_path, String servlet_path, String path_info) + { + trace("setServletPath: " + filter_path + ", " + servlet_path); + + this.filter_path = filter_path; + this.servlet_path = servlet_path; + this.path_info = path_info; + } + + @Override + public String getFilterPath() + { + return filter_path; + } + + @Override + public HttpSession getSession() + { + return getSession(true); + } + + @Override + public HttpSession getSession(boolean create) + { + if (session != null) { + if (context.isSessionIdValid(session.getId())) { + trace("getSession(" + create + "): " + session.getId()); + + return session; + } + + session = null; + } + + if (!request_session_id_parsed) { + parseRequestSessionId(); + + session = context.getSession(request_session_id); + } + + if (session != null || !create) { + trace("getSession(" + create + "): " + (session != null ? session.getId() : "null")); + + return session; + } + + session = context.createSession(); + + if (context.getEffectiveSessionTrackingModes().contains( + SessionTrackingMode.COOKIE)) + { + setSessionIdCookie(); + } + + trace("getSession(" + create + "): " + session.getId()); + + return session; + } + + private void setSessionIdCookie() + { + SessionCookieConfig config = context.getSessionCookieConfig(); + + Cookie c = new Cookie(config.getName(), session.getId()); + + c.setComment(config.getComment()); + if (!StringUtil.isBlank(config.getDomain())) { + c.setDomain(config.getDomain()); + } + + c.setHttpOnly(config.isHttpOnly()); + if (!StringUtil.isBlank(config.getPath())) { + c.setPath(config.getPath()); + } + + c.setMaxAge(config.getMaxAge()); + + getResponse(req_info_ptr).addSessionIdCookie(c); + } + + @Override + public Principal getUserPrincipal() + { + log("getUserPrincipal"); + + return null; + } + + @Override + public boolean isRequestedSessionIdFromCookie() + { + trace("isRequestedSessionIdFromCookie"); + + if (!request_session_id_parsed) { + parseRequestSessionId(); + } + + return request_session_id_from_cookie; + } + + @Override + @Deprecated + public boolean isRequestedSessionIdFromUrl() + { + trace("isRequestedSessionIdFromUrl"); + + if (!request_session_id_parsed) { + parseRequestSessionId(); + } + + return request_session_id_from_url; + } + + @Override + public boolean isRequestedSessionIdFromURL() + { + trace("isRequestedSessionIdFromURL"); + + if (!request_session_id_parsed) { + parseRequestSessionId(); + } + + return request_session_id_from_url; + } + + @Override + public boolean isRequestedSessionIdValid() + { + trace("isRequestedSessionIdValid"); + + if (!request_session_id_parsed) { + parseRequestSessionId(); + } + + return context.isSessionIdValid(request_session_id); + } + + @Override + public boolean isUserInRole(String role) + { + log("isUserInRole: " + role); + + return false; + } + + @Override + public void login(String username, String password) throws ServletException + { + log("login: " + username + "," + password); + } + + @Override + public void logout() throws ServletException + { + log("logout"); + } + + + @Override + public AsyncContext getAsyncContext() + { + log("getAsyncContext"); + + return null; + } + + @Override + public Object getAttribute(String name) + { + if (BARE.equals(name)) { + return this; + } + + Object o = attributes.get(name); + + trace("getAttribute: " + name + " = " + o); + + return o; + } + + @Override + public Enumeration getAttributeNames() + { + trace("getAttributeNames"); + + Set names = attributes.keySet(); + return Collections.enumeration(names); + } + + @Override + public String getCharacterEncoding() + { + trace("getCharacterEncoding"); + + if (characterEncoding != null) { + return characterEncoding; + } + + getContentType(); + + return characterEncoding; + } + + @Override + public int getContentLength() + { + trace("getContentLength"); + + return (int) getContentLength(req_ptr); + } + + private static native long getContentLength(long req_ptr); + + @Override + public long getContentLengthLong() + { + trace("getContentLengthLong"); + + return getContentLength(req_ptr); + } + + @Override + public String getContentType() + { + trace("getContentType"); + + String content_type = getContentType(req_ptr); + + if (characterEncoding == null && content_type != null) { + MimeTypes.Type mime = MimeTypes.CACHE.get(content_type); + String charset = (mime == null || mime.getCharset() == null) ? MimeTypes.getCharsetFromContentType(content_type) : mime.getCharset().toString(); + if (charset != null) { + characterEncoding = charset; + } + } + + return content_type; + } + + private static native String getContentType(long req_ptr); + + + @Override + public DispatcherType getDispatcherType() + { + trace("getDispatcherType: " + dispatcher_type); + + return dispatcher_type; + } + + @Override + public void setDispatcherType(DispatcherType type) + { + trace("setDispatcherType: " + type); + + dispatcher_type = type; + } + + @Override + public ServletInputStream getInputStream() throws IOException + { + trace("getInputStream"); + + if (reader != null) { + throw new IllegalStateException("getInputStream: getReader() already used"); + } + + if (inputStream == null) { + inputStream = new InputStream(req_info_ptr); + } + + return inputStream; + } + + @Override + public String getLocalAddr() + { + trace("getLocalAddr"); + + return getLocalAddr(req_ptr); + } + + private static native String getLocalAddr(long req_ptr); + + + @Override + public Locale getLocale() + { + log("getLocale"); + + return Locale.getDefault(); + } + + @Override + public Enumeration getLocales() + { + log("getLocales"); + + return Collections.emptyEnumeration(); + } + + @Override + public String getLocalName() + { + trace("getLocalName"); + + return getLocalName(req_ptr); + } + + private static native String getLocalName(long req_ptr); + + + @Override + public int getLocalPort() + { + trace("getLocalPort"); + + return getLocalPort(req_ptr); + } + + private static native int getLocalPort(long req_ptr); + + + public MultiMap getParameters() + { + if (parameters != null) { + return parameters; + } + + parameters = new MultiMap<>(); + + String query = getQueryString(); + + if (query != null) { + UrlEncoded.decodeUtf8To(query, parameters); + } + + if (getContentLength() > 0 && + getMethod().equals("POST") && + getContentType().startsWith("application/x-www-form-urlencoded")) + { + try { + UrlEncoded.decodeUtf8To(new InputStream(req_info_ptr), + parameters, getContentLength(), -1); + } catch (IOException e) { + log("Unhandled IOException: " + e); + } + } + + return parameters; + } + + public void setParameters(MultiMap p) + { + parameters = p; + } + + @Override + public String getParameter(String name) + { + trace("getParameter: " + name); + + return getParameters().getValue(name, 0); + } + + @Override + public Map getParameterMap() + { + trace("getParameterMap"); + + return Collections.unmodifiableMap(getParameters().toStringArrayMap()); + } + + @Override + public Enumeration getParameterNames() + { + trace("getParameterNames"); + + return Collections.enumeration(getParameters().keySet()); + } + + @Override + public String[] getParameterValues(String name) + { + trace("getParameterValues: " + name); + + List vals = getParameters().getValues(name); + if (vals == null) + return null; + return vals.toArray(new String[vals.size()]); + } + + @Override + public String getProtocol() + { + trace("getProtocol"); + + return getProtocol(req_ptr); + } + + private static native String getProtocol(long req_ptr); + + @Override + public BufferedReader getReader() throws IOException + { + trace("getReader"); + + if (inputStream != null) { + throw new IllegalStateException("getReader: getInputStream() already used"); + } + + if (reader == null) { + reader = new BufferedReader(new InputStreamReader(new InputStream(req_info_ptr))); + } + + return reader; + } + + @Override + @Deprecated + public String getRealPath(String path) + { + trace("getRealPath: " + path); + + return context.getRealPath(path); + } + + @Override + public String getRemoteAddr() + { + String res = getRemoteAddr(req_ptr); + + trace("getRemoteAddr: " + res); + + return res; + } + + private static native String getRemoteAddr(long req_ptr); + + + @Override + public String getRemoteHost() + { + String res = getRemoteHost(req_ptr); + + trace("getRemoteHost: " + res); + + return res; + } + + private static native String getRemoteHost(long req_ptr); + + + @Override + public int getRemotePort() + { + int res = getRemotePort(req_ptr); + + trace("getRemotePort: " + res); + + return res; + } + + private static native int getRemotePort(long req_ptr); + + + @Override + public RequestDispatcher getRequestDispatcher(String path) + { + trace("getRequestDispatcher: " + path); + + if (path.startsWith("/")) { + return context.getRequestDispatcher(path); + } + + try { + URI uri = new URI(getRequestURI()); + uri = uri.resolve(path); + + return context.getRequestDispatcher(uri); + } catch (URISyntaxException e) { + log("getRequestDispatcher: failed to create dispatcher: " + e); + } + + return null; + } + + + @Override + public String getScheme() + { + log("getScheme"); + + return getScheme(req_ptr); + } + + private static native String getScheme(long req_ptr); + + + @Override + public String getServerName() + { + String res = getServerName(req_ptr); + + trace("getServerName: " + res); + + return res; + } + + private static native String getServerName(long req_ptr); + + + @Override + public int getServerPort() + { + int res = getServerPort(req_ptr); + + trace("getServerPort: " + res); + + return res; + } + + private static native int getServerPort(long req_ptr); + + @Override + public ServletContext getServletContext() + { + trace("getServletContext"); + + return context; + } + + @Override + public boolean isAsyncStarted() + { + log("isAsyncStarted"); + + return false; + } + + @Override + public boolean isAsyncSupported() + { + log("isAsyncSupported"); + + return false; + } + + @Override + public boolean isSecure() + { + log("isSecure"); + + return false; + } + + @Override + public void removeAttribute(String name) + { + trace("removeAttribute: " + name); + + Object prev = attributes.remove(name); + + if (attr_listener == null || prev == null) { + return; + } + + attr_listener.attributeRemoved( + new ServletRequestAttributeEvent(context, this, name, prev)); + } + + @Override + public void setAttribute(String name, Object o) + { + trace("setAttribute: " + name + ", " + o); + + Object prev; + + if (o != null) { + prev = attributes.put(name, o); + } else { + prev = attributes.remove(name); + } + + if (attr_listener == null) { + return; + } + + if (prev == null) { + if (o == null) { + return; + } + + attr_listener.attributeAdded(new ServletRequestAttributeEvent( + context, this, name, o)); + } else { + if (o != null) { + attr_listener.attributeReplaced( + new ServletRequestAttributeEvent(context, this, name, prev)); + } else { + attr_listener.attributeRemoved( + new ServletRequestAttributeEvent(context, this, name, prev)); + } + } + } + + public void setAttribute_(String name, Object o) + { + trace("setAttribute_: " + name + ", " + o); + + if (o != null) { + attributes.put(name, o); + } else { + attributes.remove(name); + } + } + + @Override + public void setCharacterEncoding(String env) throws UnsupportedEncodingException + { + trace("setCharacterEncoding: " + env); + + characterEncoding = env; + } + + @Override + public AsyncContext startAsync() throws IllegalStateException + { + log("startAsync"); + + return null; + } + + @Override + public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException + { + log("startAsync(Req, resp)"); + + return null; + } + + @Override + public T upgrade( + Class httpUpgradeHandlerClass) throws java.io.IOException, ServletException + { + log("upgrade: " + httpUpgradeHandlerClass.getName()); + + return null; + } + + @Override + public String changeSessionId() + { + trace("changeSessionId"); + + getSession(false); + + if (session == null) { + return null; + } + + context.changeSessionId(session); + + if (context.getEffectiveSessionTrackingModes().contains( + SessionTrackingMode.COOKIE)) + { + setSessionIdCookie(); + } + + return session.getId(); + } + + private void log(String msg) + { + msg = "Request." + msg; + log(req_info_ptr, msg, msg.length()); + } + + public static native void log(long req_info_ptr, String msg, int msg_len); + + + private void trace(String msg) + { + msg = "Request." + msg; + trace(req_info_ptr, msg, msg.length()); + } + + public static native void trace(long req_info_ptr, String msg, int msg_len); + + private static native Response getResponse(long req_info_ptr); +} + diff --git a/src/java/nginx/unit/RequestAttrProxy.java b/src/java/nginx/unit/RequestAttrProxy.java new file mode 100644 index 00000000..daedd01a --- /dev/null +++ b/src/java/nginx/unit/RequestAttrProxy.java @@ -0,0 +1,40 @@ +package nginx.unit; + +import java.util.List; + +import javax.servlet.ServletRequestAttributeEvent; +import javax.servlet.ServletRequestAttributeListener; + +public class RequestAttrProxy implements ServletRequestAttributeListener +{ + private final List listeners_; + + public RequestAttrProxy(List listeners) + { + listeners_ = listeners; + } + + @Override + public void attributeAdded(ServletRequestAttributeEvent srae) + { + for (ServletRequestAttributeListener l : listeners_) { + l.attributeAdded(srae); + } + } + + @Override + public void attributeReplaced(ServletRequestAttributeEvent srae) + { + for (ServletRequestAttributeListener l : listeners_) { + l.attributeReplaced(srae); + } + } + + @Override + public void attributeRemoved(ServletRequestAttributeEvent srae) + { + for (ServletRequestAttributeListener l : listeners_) { + l.attributeRemoved(srae); + } + } +} diff --git a/src/java/nginx/unit/Response.java b/src/java/nginx/unit/Response.java new file mode 100644 index 00000000..099d7f15 --- /dev/null +++ b/src/java/nginx/unit/Response.java @@ -0,0 +1,817 @@ +package nginx.unit; + +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; + +import java.lang.IllegalArgumentException; +import java.lang.String; + +import java.net.URI; +import java.net.URISyntaxException; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +import java.text.SimpleDateFormat; + +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.Enumeration; +import java.util.Locale; +import java.util.TimeZone; +import java.util.Vector; + +import javax.servlet.DispatcherType; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.util.StringUtil; + +public class Response implements HttpServletResponse { + + private long req_info_ptr; + + private static final String defaultCharacterEncoding = "iso-8859-1"; + private String characterEncoding = defaultCharacterEncoding; + private String contentType = null; + private String contentTypeHeader = null; + + private static final Charset ISO_8859_1 = StandardCharsets.ISO_8859_1; + private static final Charset UTF_8 = StandardCharsets.UTF_8; + + private static final String CONTENT_TYPE = "Content-Type"; + private static final byte[] SET_COOKIE_BYTES = "Set-Cookie".getBytes(ISO_8859_1); + private static final byte[] EXPIRES_BYTES = "Expires".getBytes(ISO_8859_1); + + /** + * The only date format permitted when generating HTTP headers. + */ + public static final String RFC1123_DATE = + "EEE, dd MMM yyyy HH:mm:ss zzz"; + + private static final SimpleDateFormat format = + new SimpleDateFormat(RFC1123_DATE, Locale.US); + + private static final String ZERO_DATE_STRING = dateToString(0); + private static final byte[] ZERO_DATE_BYTES = ZERO_DATE_STRING.getBytes(ISO_8859_1); + + /** + * If this string is found within the comment of a cookie added with {@link #addCookie(Cookie)}, then the cookie + * will be set as HTTP ONLY. + */ + public final static String HTTP_ONLY_COMMENT = "__HTTP_ONLY__"; + + private OutputStream outputStream = null; + + private PrintWriter writer = null; + + + public Response(long ptr) { + req_info_ptr = ptr; + } + + /** + * Format a set cookie value by RFC6265 + * + * @param name the name + * @param value the value + * @param domain the domain + * @param path the path + * @param maxAge the maximum age + * @param isSecure true if secure cookie + * @param isHttpOnly true if for http only + */ + public void addSetRFC6265Cookie( + final String name, + final String value, + final String domain, + final String path, + final long maxAge, + final boolean isSecure, + final boolean isHttpOnly) + { + // Check arguments + if (name == null || name.length() == 0) { + throw new IllegalArgumentException("Bad cookie name"); + } + + // Name is checked for legality by servlet spec, but can also be passed directly so check again for quoting + // Per RFC6265, Cookie.name follows RFC2616 Section 2.2 token rules + //Syntax.requireValidRFC2616Token(name, "RFC6265 Cookie name"); + // Ensure that Per RFC6265, Cookie.value follows syntax rules + //Syntax.requireValidRFC6265CookieValue(value); + + // Format value and params + StringBuilder buf = new StringBuilder(); + buf.append(name).append('=').append(value == null ? "" : value); + + // Append path + if (path != null && path.length() > 0) { + buf.append(";Path=").append(path); + } + + // Append domain + if (domain != null && domain.length() > 0) { + buf.append(";Domain=").append(domain); + } + + // Handle max-age and/or expires + if (maxAge >= 0) { + // Always use expires + // This is required as some browser (M$ this means you!) don't handle max-age even with v1 cookies + buf.append(";Expires="); + if (maxAge == 0) + buf.append(ZERO_DATE_STRING); + else + buf.append(dateToString(System.currentTimeMillis() + 1000L * maxAge)); + + buf.append(";Max-Age="); + buf.append(maxAge); + } + + // add the other fields + if (isSecure) + buf.append(";Secure"); + if (isHttpOnly) + buf.append(";HttpOnly"); + + // add the set cookie + addHeader(req_info_ptr, SET_COOKIE_BYTES, + buf.toString().getBytes(ISO_8859_1)); + + // Expire responses with set-cookie headers so they do not get cached. + setHeader(req_info_ptr, EXPIRES_BYTES, ZERO_DATE_BYTES); + } + + @Override + public void addCookie(Cookie cookie) + { + trace("addCookie: " + cookie.getName() + "=" + cookie.getValue()); + + if (StringUtil.isBlank(cookie.getName())) { + throw new IllegalArgumentException("Cookie.name cannot be blank/null"); + } + + if (isCommitted()) { + return; + } + + addCookie_(cookie); + } + + private void addCookie_(Cookie cookie) + { + String comment = cookie.getComment(); + boolean httpOnly = false; + + if (comment != null && comment.contains(HTTP_ONLY_COMMENT)) { + httpOnly = true; + } + + addSetRFC6265Cookie(cookie.getName(), + cookie.getValue(), + cookie.getDomain(), + cookie.getPath(), + cookie.getMaxAge(), + cookie.getSecure(), + httpOnly || cookie.isHttpOnly()); + } + + public void addSessionIdCookie(Cookie cookie) + { + trace("addSessionIdCookie: " + cookie.getName() + "=" + cookie.getValue()); + + if (isCommitted()) { + /* + 9.3 The Include Method + + ... any call to HttpServletRequest.getSession() or + HttpServletRequest.getSession(boolean) that would require + adding a Cookie response header must throw an + IllegalStateException if the response has been committed. + */ + throw new IllegalStateException("Response already sent"); + } + + addCookie_(cookie); + } + + @Override + public void addDateHeader(String name, long date) + { + trace("addDateHeader: " + name + ": " + date); + + if (isCommitted()) { + return; + } + + String value = dateToString(date); + + addHeader(req_info_ptr, name.getBytes(ISO_8859_1), + value.getBytes(ISO_8859_1)); + } + + private static String dateToString(long date) + { + Date dateValue = new Date(date); + format.setTimeZone(TimeZone.getTimeZone("GMT")); + return format.format(dateValue); + } + + + @Override + public void addHeader(String name, String value) + { + trace("addHeader: " + name + ": " + value); + + if (value == null) { + return; + } + + if (isCommitted()) { + return; + } + + if (CONTENT_TYPE.equalsIgnoreCase(name)) { + setContentType(value); + return; + } + + addHeader(req_info_ptr, name.getBytes(ISO_8859_1), + value.getBytes(ISO_8859_1)); + } + + private static native void addHeader(long req_info_ptr, byte[] name, byte[] value); + + + @Override + public void addIntHeader(String name, int value) + { + trace("addIntHeader: " + name + ": " + value); + + if (isCommitted()) { + return; + } + + addIntHeader(req_info_ptr, name.getBytes(ISO_8859_1), value); + } + + private static native void addIntHeader(long req_info_ptr, byte[] name, int value); + + + @Override + public boolean containsHeader(String name) + { + trace("containsHeader: " + name); + + return containsHeader(req_info_ptr, name.getBytes(ISO_8859_1)); + } + + private static native boolean containsHeader(long req_info_ptr, byte[] name); + + + @Override + @Deprecated + public String encodeRedirectUrl(String url) + { + return encodeRedirectURL(url); + } + + @Override + public String encodeRedirectURL(String url) + { + log("encodeRedirectURL: " + url); + + return url; + } + + @Override + @Deprecated + public String encodeUrl(String url) + { + return encodeURL(url); + } + + @Override + public String encodeURL(String url) + { + log("encodeURL: " + url); + + return url; + } + + @Override + public String getHeader(String name) + { + trace("getHeader: " + name); + + return getHeader(req_info_ptr, name.getBytes(ISO_8859_1)); + } + + private static native String getHeader(long req_info_ptr, byte[] name); + + + @Override + public Collection getHeaderNames() + { + trace("getHeaderNames"); + + Enumeration e = getHeaderNames(req_info_ptr); + if (e == null) { + return Collections.emptyList(); + } + + return Collections.list(e); + } + + private static native Enumeration getHeaderNames(long req_info_ptr); + + + @Override + public Collection getHeaders(String name) + { + trace("getHeaders: " + name); + + Enumeration e = getHeaders(req_info_ptr, name.getBytes(ISO_8859_1)); + if (e == null) { + return Collections.emptyList(); + } + + return Collections.list(e); + } + + private static native Enumeration getHeaders(long req_info_ptr, byte[] name); + + + @Override + public int getStatus() + { + trace("getStatus"); + + return getStatus(req_info_ptr); + } + + private static native int getStatus(long req_info_ptr); + + + @Override + public void sendError(int sc) throws IOException + { + sendError(sc, null); + } + + @Override + public void sendError(int sc, String msg) throws IOException + { + trace("sendError: " + sc + ", " + msg); + + if (isCommitted()) { + throw new IllegalStateException("Response already sent"); + } + + setStatus(sc); + + Request request = getRequest(req_info_ptr); + + // If we are allowed to have a body, then produce the error page. + if (sc != SC_NO_CONTENT && sc != SC_NOT_MODIFIED && + sc != SC_PARTIAL_CONTENT && sc >= SC_OK) + { + request.setAttribute_(RequestDispatcher.ERROR_STATUS_CODE, sc); + request.setAttribute_(RequestDispatcher.ERROR_MESSAGE, msg); + request.setAttribute_(RequestDispatcher.ERROR_REQUEST_URI, + request.getRequestURI()); +/* + request.setAttribute_(RequestDispatcher.ERROR_SERVLET_NAME, + request.getServletName()); +*/ + } + +/* + Avoid commit and give chance for error handlers. + + if (!request.isAsyncStarted()) { + commit(); + } +*/ + } + + private static native Request getRequest(long req_info_ptr); + + private void commit() + { + if (writer != null) { + writer.close(); + + } else if (outputStream != null) { + outputStream.close(); + + } else { + commit(req_info_ptr); + } + } + + private static native void commit(long req_info_ptr); + + + @Override + public void sendRedirect(String location) throws IOException + { + trace("sendRedirect: " + location); + + if (isCommitted()) { + return; + } + + try { + URI uri = new URI(location); + + if (!uri.isAbsolute()) { + URI req_uri = new URI(getRequest(req_info_ptr).getRequestURL().toString()); + uri = req_uri.resolve(uri); + + location = uri.toString(); + } + } catch (URISyntaxException e) { + log("sendRedirect: failed to send redirect: " + e); + return; + } + + sendRedirect(req_info_ptr, location.getBytes(ISO_8859_1)); + } + + private static native void sendRedirect(long req_info_ptr, byte[] location); + + + @Override + public void setDateHeader(String name, long date) + { + trace("setDateHeader: " + name + ": " + date); + + if (isCommitted()) { + return; + } + + String value = dateToString(date); + + setHeader(req_info_ptr, name.getBytes(ISO_8859_1), + value.getBytes(ISO_8859_1)); + } + + + @Override + public void setHeader(String name, String value) + { + trace("setHeader: " + name + ": " + value); + + if (isCommitted()) { + return; + } + + if (CONTENT_TYPE.equalsIgnoreCase(name)) { + setContentType(value); + return; + } + + /* + * When value is null container behaviour is undefined. + * - Tomcat ignores setHeader call; + * - Jetty & Resin acts as removeHeader; + */ + if (value == null) { + removeHeader(req_info_ptr, name.getBytes(ISO_8859_1)); + return; + } + + setHeader(req_info_ptr, name.getBytes(ISO_8859_1), + value.getBytes(ISO_8859_1)); + } + + private static native void setHeader(long req_info_ptr, byte[] name, byte[] value); + + private static native void removeHeader(long req_info_ptr, byte[] name); + + @Override + public void setIntHeader(String name, int value) + { + trace("setIntHeader: " + name + ": " + value); + + if (isCommitted()) { + return; + } + + setIntHeader(req_info_ptr, name.getBytes(ISO_8859_1), value); + } + + private static native void setIntHeader(long req_info_ptr, byte[] name, int value); + + + @Override + public void setStatus(int sc) + { + trace("setStatus: " + sc); + + if (isCommitted()) { + return; + } + + setStatus(req_info_ptr, sc); + } + + private static native void setStatus(long req_info_ptr, int sc); + + + @Override + @Deprecated + public void setStatus(int sc, String sm) + { + trace("setStatus: " + sc + "; " + sm); + + if (isCommitted()) { + return; + } + + setStatus(req_info_ptr, sc); + } + + + @Override + public void flushBuffer() throws IOException + { + trace("flushBuffer"); + + if (writer != null) { + writer.flush(); + } + + if (outputStream != null) { + outputStream.flush(); + } + } + + @Override + public int getBufferSize() + { + trace("getBufferSize"); + + return getBufferSize(req_info_ptr); + } + + public static native int getBufferSize(long req_info_ptr); + + + @Override + public String getCharacterEncoding() + { + trace("getCharacterEncoding"); + + return characterEncoding; + } + + @Override + public String getContentType() + { + /* In JIRA decorator get content type called after commit. */ + + String res = contentTypeHeader; + + trace("getContentType: " + res); + + return res; + } + + private static native String getContentType(long req_info_ptr); + + @Override + public Locale getLocale() + { + log("getLocale"); + + return null; + } + + @Override + public ServletOutputStream getOutputStream() throws IOException + { + trace("getOutputStream"); + + if (writer != null) { + throw new IllegalStateException("Writer already created"); + } + + if (outputStream == null) { + outputStream = new OutputStream(req_info_ptr); + } + + return outputStream; + } + + @Override + public PrintWriter getWriter() throws IOException + { + trace("getWriter ( characterEncoding = '" + characterEncoding + "' )"); + + if (outputStream != null) { + throw new IllegalStateException("OutputStream already created"); + } + + if (writer == null) { + ServletOutputStream stream = new OutputStream(req_info_ptr); + + writer = new PrintWriter( + new OutputStreamWriter(stream, Charset.forName(characterEncoding)), + false); + } + + return writer; + } + + @Override + public boolean isCommitted() + { + trace("isCommitted"); + + return isCommitted(req_info_ptr); + } + + public static native boolean isCommitted(long req_info_ptr); + + @Override + public void reset() + { + trace("reset"); + + if (isCommitted()) { + return; + } + + reset(req_info_ptr); + + writer = null; + outputStream = null; + } + + public static native void reset(long req_info_ptr); + + @Override + public void resetBuffer() + { + trace("resetBuffer"); + + resetBuffer(req_info_ptr); + + writer = null; + outputStream = null; + } + + public static native void resetBuffer(long req_info_ptr); + + @Override + public void setBufferSize(int size) + { + trace("setBufferSize: " + size); + + setBufferSize(req_info_ptr, size); + } + + public static native void setBufferSize(long req_info_ptr, int size); + + @Override + public void setCharacterEncoding(String charset) + { + trace("setCharacterEncoding " + charset); + + if (isCommitted()) { + return; + } + + if (charset == null) { + if (writer != null + && !characterEncoding.equalsIgnoreCase(defaultCharacterEncoding)) + { + /* TODO throw */ + return; + } + + characterEncoding = defaultCharacterEncoding; + } else { + if (writer != null + && !characterEncoding.equalsIgnoreCase(charset)) + { + /* TODO throw */ + return; + } + + characterEncoding = charset; + } + + if (contentType != null) { + String type = contentType + ";charset=" + characterEncoding; + + contentTypeHeader = type; + + setContentType(req_info_ptr, type.getBytes(ISO_8859_1)); + } + } + + + @Override + public void setContentLength(int len) + { + trace("setContentLength: " + len); + + if (isCommitted()) { + return; + } + + setContentLength(req_info_ptr, len); + } + + @Override + public void setContentLengthLong(long len) + { + trace("setContentLengthLong: " + len); + + if (isCommitted()) { + return; + } + + setContentLength(req_info_ptr, len); + } + + private static native void setContentLength(long req_info_ptr, long len); + + + @Override + public void setContentType(String type) + { + trace("setContentType: " + type); + + if (isCommitted()) { + return; + } + + if (type == null) { + removeContentType(req_info_ptr); + contentType = null; + contentTypeHeader = null; + return; + } + + String charset = MimeTypes.getCharsetFromContentType(type); + String ctype = MimeTypes.getContentTypeWithoutCharset(type); + + if (writer != null + && charset != null + && !characterEncoding.equalsIgnoreCase(charset)) + { + /* To late to change character encoding */ + charset = characterEncoding; + type = ctype + ";charset=" + characterEncoding; + } + + if (charset == null) { + type = type + ";charset=" + characterEncoding; + } else { + characterEncoding = charset; + } + + contentType = ctype; + contentTypeHeader = type; + + setContentType(req_info_ptr, type.getBytes(ISO_8859_1)); + } + + private static native void setContentType(long req_info_ptr, byte[] type); + + private static native void removeContentType(long req_info_ptr); + + + @Override + public void setLocale(Locale loc) + { + log("setLocale: " + loc); + } + + private void log(String msg) + { + msg = "Response." + msg; + log(req_info_ptr, msg.getBytes(UTF_8)); + } + + public static native void log(long req_info_ptr, byte[] msg); + + + private void trace(String msg) + { + msg = "Response." + msg; + trace(req_info_ptr, msg.getBytes(UTF_8)); + } + + public static native void trace(long req_info_ptr, byte[] msg); +} diff --git a/src/java/nginx/unit/Session.java b/src/java/nginx/unit/Session.java new file mode 100644 index 00000000..6be74709 --- /dev/null +++ b/src/java/nginx/unit/Session.java @@ -0,0 +1,251 @@ +package nginx.unit; + +import java.io.Serializable; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpSessionAttributeListener; +import javax.servlet.http.HttpSessionBindingEvent; +import javax.servlet.http.HttpSessionBindingListener; + +import java.util.Collections; +import java.util.Date; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; + +/** + * @author Andrey Kazankov + */ +public class Session implements HttpSession, Serializable +{ + private final Map attributes = new HashMap<>(); + private final long creation_time = new Date().getTime(); + private long last_access_time = creation_time; + private long access_time = creation_time; + private int max_inactive_interval; + private String id; + private final Context context; + private boolean is_new = true; + private final HttpSessionAttributeListener attr_listener; + + public Session(Context context, String id, + HttpSessionAttributeListener al, int max_inactive_interval) + { + this.id = id; + this.context = context; + attr_listener = al; + this.max_inactive_interval = max_inactive_interval; + } + + public void setId(String id) + { + this.id = id; + } + + @Override + public long getCreationTime() + { + return creation_time; + } + + @Override + public String getId() + { + return id; + } + + @Override + public long getLastAccessedTime() + { + return last_access_time; + } + + @Override + public ServletContext getServletContext() + { + return context; + } + + @Override + public void setMaxInactiveInterval(int i) + { + max_inactive_interval = i; + } + + @Override + public int getMaxInactiveInterval() + { + return max_inactive_interval; + } + + @Deprecated + @Override + public javax.servlet.http.HttpSessionContext getSessionContext() + { + return null; + } + + @Override + public Object getAttribute(String s) + { + synchronized (attributes) { + return attributes.get(s); + } + } + + @Deprecated + @Override + public Object getValue(String s) + { + return getAttribute(s); + } + + @Override + public Enumeration getAttributeNames() + { + synchronized (attributes) { + return Collections.enumeration(attributes.keySet()); + } + } + + @Deprecated + @Override + public String[] getValueNames() + { + synchronized (attributes) { + return attributes.keySet().toArray(new String[attributes.keySet().size()]); + } + } + + @Override + public void setAttribute(String s, Object o) + { + Object old; + + if (o != null && o instanceof HttpSessionBindingListener) { + HttpSessionBindingListener l = (HttpSessionBindingListener) o; + HttpSessionBindingEvent e = new HttpSessionBindingEvent(this, s); + + l.valueBound(e); + } + + synchronized (attributes) { + if (o != null) { + old = attributes.put(s, o); + } else { + old = attributes.remove(s); + } + } + + if (old != null && old instanceof HttpSessionBindingListener) { + HttpSessionBindingListener l = (HttpSessionBindingListener) old; + HttpSessionBindingEvent e = new HttpSessionBindingEvent(this, s); + + l.valueUnbound(e); + } + + if (attr_listener == null) { + return; + } + + if (o == null) { + if (old != null) { + HttpSessionBindingEvent e = new HttpSessionBindingEvent(this, s, old); + attr_listener.attributeRemoved(e); + } + + return; + } + + if (old != null) { + HttpSessionBindingEvent e = new HttpSessionBindingEvent(this, s, old); + attr_listener.attributeReplaced(e); + } else { + HttpSessionBindingEvent e = new HttpSessionBindingEvent(this, s, o); + attr_listener.attributeAdded(e); + } + } + + @Deprecated + @Override + public void putValue(String s, Object o) + { + setAttribute(s,o); + } + + @Override + public void removeAttribute(String s) + { + Object o; + + synchronized (attributes) { + o = attributes.remove(s); + } + + if (o != null && o instanceof HttpSessionBindingListener) { + HttpSessionBindingListener l = (HttpSessionBindingListener) o; + HttpSessionBindingEvent e = new HttpSessionBindingEvent(this, s); + + l.valueUnbound(e); + } + + if (attr_listener == null || o == null) { + return; + } + + HttpSessionBindingEvent e = new HttpSessionBindingEvent(this, s, o); + attr_listener.attributeRemoved(e); + } + + @Deprecated + @Override + public void removeValue(String s) + { + removeAttribute(s); + } + + @Override + public void invalidate() + { + context.invalidateSession(this); + + unboundAttributes(); + } + + private void unboundAttributes() + { + for (Map.Entry a : attributes.entrySet()) { + Object o = a.getValue(); + if (o != null && o instanceof HttpSessionBindingListener) { + HttpSessionBindingListener l = (HttpSessionBindingListener) o; + HttpSessionBindingEvent e = new HttpSessionBindingEvent(this, a.getKey()); + + l.valueUnbound(e); + } + } + + attributes.clear(); + } + + @Override + public boolean isNew() + { + return is_new; + } + + public void accessed() { + synchronized (this) { + is_new = false; + + last_access_time = access_time; + access_time = new Date().getTime(); + } + } + + public boolean checkTimeOut() + { + return (max_inactive_interval > 0) && + (access_time - last_access_time > max_inactive_interval * 1000); + } +} \ No newline at end of file diff --git a/src/java/nginx/unit/SessionAttrProxy.java b/src/java/nginx/unit/SessionAttrProxy.java new file mode 100644 index 00000000..bddeede9 --- /dev/null +++ b/src/java/nginx/unit/SessionAttrProxy.java @@ -0,0 +1,40 @@ +package nginx.unit; + +import java.util.List; + +import javax.servlet.http.HttpSessionAttributeListener; +import javax.servlet.http.HttpSessionBindingEvent; + +public class SessionAttrProxy implements HttpSessionAttributeListener +{ + private final List listeners_; + + public SessionAttrProxy(List listeners) + { + listeners_ = listeners; + } + + @Override + public void attributeAdded(HttpSessionBindingEvent event) + { + for (HttpSessionAttributeListener l : listeners_) { + l.attributeAdded(event); + } + } + + @Override + public void attributeRemoved(HttpSessionBindingEvent event) + { + for (HttpSessionAttributeListener l : listeners_) { + l.attributeRemoved(event); + } + } + + @Override + public void attributeReplaced(HttpSessionBindingEvent event) + { + for (HttpSessionAttributeListener l : listeners_) { + l.attributeReplaced(event); + } + } +} diff --git a/src/java/nginx/unit/Taglib.java b/src/java/nginx/unit/Taglib.java new file mode 100644 index 00000000..f72033ba --- /dev/null +++ b/src/java/nginx/unit/Taglib.java @@ -0,0 +1,44 @@ +package nginx.unit; + +import javax.servlet.descriptor.TaglibDescriptor; + +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +public class Taglib implements TaglibDescriptor +{ + private String uri_ = null; + private String location_ = null; + + public Taglib(NodeList nodes) + { + for (int i = 0; i < nodes.getLength(); i++) { + Node node = nodes.item(i); + String tag_name = node.getNodeName(); + + if (tag_name.equals("taglib-uri")) { + uri_ = node.getTextContent().trim(); + continue; + } + + if (tag_name.equals("taglib-location")) { + location_ = node.getTextContent().trim(); + continue; + } + } + + } + + @Override + public String getTaglibURI() + { + return uri_; + } + + @Override + public String getTaglibLocation() + { + return location_; + } +} + diff --git a/src/java/nginx/unit/UnitSessionCookieConfig.java b/src/java/nginx/unit/UnitSessionCookieConfig.java new file mode 100644 index 00000000..e1b2ae04 --- /dev/null +++ b/src/java/nginx/unit/UnitSessionCookieConfig.java @@ -0,0 +1,110 @@ +package nginx.unit; + +import javax.servlet.SessionCookieConfig; + +/* + + + 60 + + + + + + */ +public class UnitSessionCookieConfig implements SessionCookieConfig { + + private static final String default_name = "JSESSIONID"; + + private String name = default_name; + private String domain; + private String path; + private String comment; + private boolean httpOnly = true; + private boolean secure = false; + private int maxAge = -1; + + @Override + public void setName(String name) + { + this.name = name; + } + + @Override + public String getName() + { + return name; + } + + @Override + public void setDomain(String domain) + { + this.domain = domain; + } + + @Override + public String getDomain() + { + return domain; + } + + @Override + public void setPath(String path) + { + this.path = path; + } + + @Override + public String getPath() + { + return path; + } + + @Override + public void setComment(String comment) + { + this.comment = comment; + } + + @Override + public String getComment() + { + return comment; + } + + @Override + public void setHttpOnly(boolean httpOnly) + { + this.httpOnly = httpOnly; + } + + @Override + public boolean isHttpOnly() + { + return httpOnly; + } + + @Override + public void setSecure(boolean secure) + { + this.secure = secure; + } + + @Override + public boolean isSecure() + { + return secure; + } + + @Override + public void setMaxAge(int maxAge) + { + this.maxAge = maxAge; + } + + @Override + public int getMaxAge() + { + return maxAge; + } +} diff --git a/src/java/nxt_jni.c b/src/java/nxt_jni.c new file mode 100644 index 00000000..02ec1e37 --- /dev/null +++ b/src/java/nxt_jni.c @@ -0,0 +1,175 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#include + +#include +#include +#include + +#include "nxt_jni.h" + + +static jclass nxt_java_NoSuchElementException_class; +static jclass nxt_java_IOException_class; +static jclass nxt_java_IllegalStateException_class; +static jclass nxt_java_File_class; +static jmethodID nxt_java_File_ctor; + +static inline char nxt_java_lowcase(char c); + + +int +nxt_java_jni_init(JNIEnv *env) +{ + jclass cls; + + cls = (*env)->FindClass(env, "java/util/NoSuchElementException"); + if (cls == NULL) { + return NXT_UNIT_ERROR; + } + + nxt_java_NoSuchElementException_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + + + cls = (*env)->FindClass(env, "java/io/IOException"); + if (cls == NULL) { + (*env)->DeleteGlobalRef(env, nxt_java_NoSuchElementException_class); + return NXT_UNIT_ERROR; + } + + nxt_java_IOException_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + + + cls = (*env)->FindClass(env, "java/lang/IllegalStateException"); + if (cls == NULL) { + (*env)->DeleteGlobalRef(env, nxt_java_NoSuchElementException_class); + (*env)->DeleteGlobalRef(env, nxt_java_IOException_class); + return NXT_UNIT_ERROR; + } + + nxt_java_IllegalStateException_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + + + cls = (*env)->FindClass(env, "java/io/File"); + if (cls == NULL) { + (*env)->DeleteGlobalRef(env, nxt_java_NoSuchElementException_class); + (*env)->DeleteGlobalRef(env, nxt_java_IOException_class); + (*env)->DeleteGlobalRef(env, nxt_java_IllegalStateException_class); + return NXT_UNIT_ERROR; + } + + nxt_java_File_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + + + nxt_java_File_ctor = (*env)->GetMethodID(env, nxt_java_File_class, "", + "(Ljava/lang/String;)V"); + if (nxt_java_File_ctor == NULL) { + (*env)->DeleteGlobalRef(env, nxt_java_NoSuchElementException_class); + (*env)->DeleteGlobalRef(env, nxt_java_IOException_class); + (*env)->DeleteGlobalRef(env, nxt_java_IllegalStateException_class); + (*env)->DeleteGlobalRef(env, nxt_java_File_class); + return NXT_UNIT_ERROR; + } + + return NXT_UNIT_OK; +} + + +void +nxt_java_throw_NoSuchElementException(JNIEnv *env, const char *msg) +{ + (*env)->ThrowNew(env, nxt_java_NoSuchElementException_class, msg); +} + + +void +nxt_java_throw_IOException(JNIEnv *env, const char *msg) +{ + (*env)->ThrowNew(env, nxt_java_IOException_class, msg); +} + + +void +nxt_java_throw_IllegalStateException(JNIEnv *env, const char *msg) +{ + (*env)->ThrowNew(env, nxt_java_IllegalStateException_class, msg); +} + + +nxt_unit_field_t * +nxt_java_findHeader(nxt_unit_field_t *f, nxt_unit_field_t *end, + const char *name, uint8_t name_len) +{ + const char *field_name; + + for (/* void */ ; f < end; f++) { + if (f->skip != 0 || f->name_length != name_len) { + continue; + } + + field_name = nxt_unit_sptr_get(&f->name); + + if (nxt_java_strcaseeq(name, field_name, name_len)) { + return f; + } + } + + return NULL; +} + + +int +nxt_java_strcaseeq(const char *str1, const char *str2, int len) +{ + char c1, c2; + const char *end1; + + end1 = str1 + len; + + while (str1 < end1) { + c1 = nxt_java_lowcase(*str1++); + c2 = nxt_java_lowcase(*str2++); + + if (c1 != c2) { + return 0; + } + } + + return 1; +} + + +static inline char +nxt_java_lowcase(char c) +{ + return (c >= 'A' && c <= 'Z') ? c | 0x20 : c; +} + + +jstring +nxt_java_newString(JNIEnv *env, char *str, uint32_t len) +{ + char tmp; + jstring res; + + tmp = str[len]; + + if (tmp != '\0') { + str[len] = '\0'; + } + + res = (*env)->NewStringUTF(env, str); + + if (tmp != '\0') { + str[len] = tmp; + } + + return res; +} diff --git a/src/java/nxt_jni.h b/src/java/nxt_jni.h new file mode 100644 index 00000000..62acb752 --- /dev/null +++ b/src/java/nxt_jni.h @@ -0,0 +1,55 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NXT_JAVA_JNI_H_INCLUDED_ +#define _NXT_JAVA_JNI_H_INCLUDED_ + + +#include +#include + + +int nxt_java_jni_init(JNIEnv *env); + +void nxt_java_throw_NoSuchElementException(JNIEnv *env, const char *msg); + +void nxt_java_throw_IOException(JNIEnv *env, const char *msg); + +void nxt_java_throw_IllegalStateException(JNIEnv *env, const char *msg); + +nxt_unit_field_t *nxt_java_findHeader(nxt_unit_field_t *f, nxt_unit_field_t *e, + const char *name, uint8_t name_len); + +int nxt_java_strcaseeq(const char *str1, const char *str2, int len); + +jstring nxt_java_newString(JNIEnv *env, char *str, uint32_t len); + + +typedef struct { + uint32_t header_size; + uint32_t buf_size; + + jobject jreq; + jobject jresp; + + nxt_unit_buf_t *first; + nxt_unit_buf_t *buf; + +} nxt_java_request_data_t; + + +static inline jlong +nxt_ptr2jlong(void *ptr) +{ + return (jlong) (intptr_t) ptr; +} + +static inline void * +nxt_jlong2ptr(jlong l) +{ + return (void *) (intptr_t) l; +} + +#endif /* _NXT_JAVA_JNI_H_INCLUDED_ */ diff --git a/src/java/nxt_jni_Context.c b/src/java/nxt_jni_Context.c new file mode 100644 index 00000000..8f7adddf --- /dev/null +++ b/src/java/nxt_jni_Context.c @@ -0,0 +1,164 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#include + +#include +#include + +#include "nxt_jni.h" +#include "nxt_jni_Context.h" +#include "nxt_jni_URLClassLoader.h" + + +static jclass nxt_java_Context_class; +static jmethodID nxt_java_Context_start; +static jmethodID nxt_java_Context_service; +static jmethodID nxt_java_Context_stop; + +static void JNICALL nxt_java_Context_log(JNIEnv *env, jclass cls, + jlong ctx_ptr, jstring msg, jint msg_len); +static void JNICALL nxt_java_Context_trace(JNIEnv *env, jclass cls, + jlong ctx_ptr, jstring msg, jint msg_len); + + +int +nxt_java_initContext(JNIEnv *env, jobject cl) +{ + int res; + jclass cls; + + cls = nxt_java_loadClass(env, cl, "nginx.unit.Context"); + if (cls == NULL) { + nxt_unit_warn(NULL, "nginx.unit.Context not found"); + return NXT_UNIT_ERROR; + } + + nxt_java_Context_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + cls = nxt_java_Context_class; + + nxt_java_Context_start = (*env)->GetStaticMethodID(env, cls, "start", + "(Ljava/lang/String;[Ljava/net/URL;)Lnginx/unit/Context;"); + if (nxt_java_Context_start == NULL) { + nxt_unit_warn(NULL, "nginx.unit.Context.start() not found"); + goto failed; + } + + nxt_java_Context_service = (*env)->GetMethodID(env, cls, "service", + "(Lnginx/unit/Request;Lnginx/unit/Response;)V"); + if (nxt_java_Context_service == NULL) { + nxt_unit_warn(NULL, "nginx.unit.Context.service() not found"); + goto failed; + } + + nxt_java_Context_stop = (*env)->GetMethodID(env, cls, "stop", "()V"); + if (nxt_java_Context_service == NULL) { + nxt_unit_warn(NULL, "nginx.unit.Context.stop() not found"); + goto failed; + } + + JNINativeMethod context_methods[] = { + { (char *) "log", + (char *) "(JLjava/lang/String;I)V", + nxt_java_Context_log }, + + { (char *) "trace", + (char *) "(JLjava/lang/String;I)V", + nxt_java_Context_trace }, + + }; + + res = (*env)->RegisterNatives(env, nxt_java_Context_class, + context_methods, + sizeof(context_methods) + / sizeof(context_methods[0])); + + nxt_unit_debug(NULL, "registered Context methods: %d", res); + + if (res != 0) { + nxt_unit_warn(NULL, "registering natives for Context failed"); + goto failed; + } + + return NXT_UNIT_OK; + +failed: + + (*env)->DeleteGlobalRef(env, cls); + return NXT_UNIT_ERROR; +} + + +jobject +nxt_java_startContext(JNIEnv *env, const char *webapp, jobject classpaths) +{ + jstring webapp_str; + + webapp_str = (*env)->NewStringUTF(env, webapp); + if (webapp_str == NULL) { + return NULL; + } + + return (*env)->CallStaticObjectMethod(env, nxt_java_Context_class, + nxt_java_Context_start, webapp_str, + classpaths); +} + + +void +nxt_java_service(JNIEnv *env, jobject ctx, jobject jreq, jobject jresp) +{ + (*env)->CallVoidMethod(env, ctx, nxt_java_Context_service, jreq, jresp); +} + + +void +nxt_java_stopContext(JNIEnv *env, jobject ctx) +{ + (*env)->CallVoidMethod(env, ctx, nxt_java_Context_stop); +} + + +static void JNICALL +nxt_java_Context_log(JNIEnv *env, jclass cls, jlong ctx_ptr, jstring msg, + jint msg_len) +{ + const char *msg_str; + nxt_unit_ctx_t *ctx; + + ctx = nxt_jlong2ptr(ctx_ptr); + + msg_str = (*env)->GetStringUTFChars(env, msg, NULL); + if (msg_str == NULL) { + return; + } + + nxt_unit_log(ctx, NXT_UNIT_LOG_INFO, "%.*s", msg_len, msg_str); + + (*env)->ReleaseStringUTFChars(env, msg, msg_str); +} + + +static void JNICALL +nxt_java_Context_trace(JNIEnv *env, jclass cls, jlong ctx_ptr, jstring msg, + jint msg_len) +{ +#if (NXT_DEBUG) + const char *msg_str; + nxt_unit_ctx_t *ctx; + + ctx = nxt_jlong2ptr(ctx_ptr); + + msg_str = (*env)->GetStringUTFChars(env, msg, NULL); + if (msg_str == NULL) { + return; + } + + nxt_unit_debug(ctx, "%.*s", msg_len, msg_str); + + (*env)->ReleaseStringUTFChars(env, msg, msg_str); +#endif +} diff --git a/src/java/nxt_jni_Context.h b/src/java/nxt_jni_Context.h new file mode 100644 index 00000000..976c36cf --- /dev/null +++ b/src/java/nxt_jni_Context.h @@ -0,0 +1,23 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NXT_JAVA_CONTEXT_H_INCLUDED_ +#define _NXT_JAVA_CONTEXT_H_INCLUDED_ + + +#include + + +int nxt_java_initContext(JNIEnv *env, jobject cl); + +jobject nxt_java_startContext(JNIEnv *env, const char *webapp, + jobject classpaths); + +void nxt_java_service(JNIEnv *env, jobject ctx, jobject jreq, jobject jresp); + +void nxt_java_stopContext(JNIEnv *env, jobject ctx); + +#endif /* _NXT_JAVA_CONTEXT_H_INCLUDED_ */ + diff --git a/src/java/nxt_jni_HeaderNamesEnumeration.c b/src/java/nxt_jni_HeaderNamesEnumeration.c new file mode 100644 index 00000000..eea0c387 --- /dev/null +++ b/src/java/nxt_jni_HeaderNamesEnumeration.c @@ -0,0 +1,153 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#include + +#include +#include +#include +#include + +#include "nxt_jni.h" +#include "nxt_jni_URLClassLoader.h" +#include "nxt_jni_HeaderNamesEnumeration.h" + + +static jlong JNICALL nxt_java_HeaderNamesEnumeration_nextElementPos(JNIEnv *env, + jclass cls, jlong headers_ptr, jlong size, jlong pos); +static jstring JNICALL nxt_java_HeaderNamesEnumeration_nextElement(JNIEnv *env, + jclass cls, jlong headers_ptr, jlong size, jlong pos); + + +static jclass nxt_java_HeaderNamesEnumeration_class; +static jmethodID nxt_java_HeaderNamesEnumeration_ctor; + + +int +nxt_java_initHeaderNamesEnumeration(JNIEnv *env, jobject cl) +{ + int res; + jclass cls; + + cls = nxt_java_loadClass(env, cl, "nginx.unit.HeaderNamesEnumeration"); + if (cls == NULL) { + return NXT_UNIT_ERROR; + } + + nxt_java_HeaderNamesEnumeration_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + cls = nxt_java_HeaderNamesEnumeration_class; + + nxt_java_HeaderNamesEnumeration_ctor = (*env)->GetMethodID(env, cls, + "", "(JJ)V"); + if (nxt_java_HeaderNamesEnumeration_ctor == NULL) { + (*env)->DeleteGlobalRef(env, cls); + return NXT_UNIT_ERROR; + } + + JNINativeMethod hnenum_methods[] = { + { (char *) "nextElementPos", + (char *) "(JJJ)J", + nxt_java_HeaderNamesEnumeration_nextElementPos }, + + { (char *) "nextElement", + (char *) "(JJJ)Ljava/lang/String;", + nxt_java_HeaderNamesEnumeration_nextElement }, + }; + + res = (*env)->RegisterNatives(env, nxt_java_HeaderNamesEnumeration_class, + hnenum_methods, + sizeof(hnenum_methods) + / sizeof(hnenum_methods[0])); + + nxt_unit_debug(NULL, "registered HeaderNamesEnumeration methods: %d", res); + + if (res != 0) { + (*env)->DeleteGlobalRef(env, cls); + return NXT_UNIT_ERROR; + } + + return NXT_UNIT_OK; +} + + +jobject +nxt_java_newHeaderNamesEnumeration(JNIEnv *env, nxt_unit_field_t *f, + uint32_t fields_count) +{ + return (*env)->NewObject(env, + nxt_java_HeaderNamesEnumeration_class, + nxt_java_HeaderNamesEnumeration_ctor, nxt_ptr2jlong(f), + (jlong) fields_count); +} + + +static jlong JNICALL +nxt_java_HeaderNamesEnumeration_nextElementPos(JNIEnv *env, jclass cls, + jlong headers_ptr, jlong size, jlong pos) +{ + nxt_unit_field_t *f; + + f = nxt_jlong2ptr(headers_ptr); + + if (pos >= size) { + return size; + } + + if (pos > 0) { + while (pos < size + && f[pos].hash == f[pos - 1].hash + && f[pos].name_length == f[pos - 1].name_length) + { + pos++; + } + } + + return pos; +} + + +static jstring JNICALL +nxt_java_HeaderNamesEnumeration_nextElement(JNIEnv *env, jclass cls, + jlong headers_ptr, jlong size, jlong pos) +{ + char *name, tmp; + jstring res; + nxt_unit_field_t *f; + + f = nxt_jlong2ptr(headers_ptr); + + if (pos > 0) { + while (pos < size + && f[pos].hash == f[pos - 1].hash + && f[pos].name_length == f[pos - 1].name_length) + { + pos++; + } + } + + if (pos >= size) { + nxt_java_throw_NoSuchElementException(env, "pos >= size"); + + return NULL; + } + + f += pos; + + name = nxt_unit_sptr_get(&f->name); + tmp = name[f->name_length]; + + if (tmp != '\0') { + name[f->name_length] = '\0'; + } + + res = (*env)->NewStringUTF(env, name); + + if (tmp != '\0') { + name[f->name_length] = tmp; + } + + return res; +} diff --git a/src/java/nxt_jni_HeaderNamesEnumeration.h b/src/java/nxt_jni_HeaderNamesEnumeration.h new file mode 100644 index 00000000..90df8f54 --- /dev/null +++ b/src/java/nxt_jni_HeaderNamesEnumeration.h @@ -0,0 +1,19 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NXT_JAVA_HEADERNAMESENUMERATION_H_INCLUDED_ +#define _NXT_JAVA_HEADERNAMESENUMERATION_H_INCLUDED_ + + +#include +#include + + +int nxt_java_initHeaderNamesEnumeration(JNIEnv *env, jobject cl); + +jobject nxt_java_newHeaderNamesEnumeration(JNIEnv *env, nxt_unit_field_t *f, + uint32_t fields_count); + +#endif /* _NXT_JAVA_HEADERNAMESENUMERATION_H_INCLUDED_ */ diff --git a/src/java/nxt_jni_HeadersEnumeration.c b/src/java/nxt_jni_HeadersEnumeration.c new file mode 100644 index 00000000..aaea710d --- /dev/null +++ b/src/java/nxt_jni_HeadersEnumeration.c @@ -0,0 +1,148 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#include + +#include +#include +#include +#include + +#include "nxt_jni.h" +#include "nxt_jni_URLClassLoader.h" +#include "nxt_jni_HeadersEnumeration.h" + + +static jclass nxt_java_HeadersEnumeration_class; +static jmethodID nxt_java_HeadersEnumeration_ctor; + + +static jlong JNICALL nxt_java_HeadersEnumeration_nextElementPos(JNIEnv *env, + jclass cls, jlong headers_ptr, jlong size, jlong ipos, jlong pos); + +static jstring JNICALL nxt_java_HeadersEnumeration_nextElement(JNIEnv *env, + jclass cls, jlong headers_ptr, jlong size, jlong ipos, jlong pos); + + +int +nxt_java_initHeadersEnumeration(JNIEnv *env, jobject cl) +{ + int res; + jclass cls; + + cls = nxt_java_loadClass(env, cl, "nginx.unit.HeadersEnumeration"); + if (cls == NULL) { + return NXT_UNIT_ERROR; + } + + nxt_java_HeadersEnumeration_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + cls = nxt_java_HeadersEnumeration_class; + + nxt_java_HeadersEnumeration_ctor = (*env)->GetMethodID(env, cls, + "", "(JJJ)V"); + if (nxt_java_HeadersEnumeration_ctor == NULL) { + (*env)->DeleteGlobalRef(env, cls); + return NXT_UNIT_ERROR; + } + + JNINativeMethod methods[] = { + { (char *) "nextElementPos", + (char *) "(JJJJ)J", + nxt_java_HeadersEnumeration_nextElementPos }, + + { (char *) "nextElement", + (char *) "(JJJJ)Ljava/lang/String;", + nxt_java_HeadersEnumeration_nextElement }, + }; + + res = (*env)->RegisterNatives(env, nxt_java_HeadersEnumeration_class, + methods, + sizeof(methods) / sizeof(methods[0])); + + nxt_unit_debug(NULL, "registered HeadersEnumeration methods: %d", res); + + if (res != 0) { + (*env)->DeleteGlobalRef(env, cls); + return NXT_UNIT_ERROR; + } + + return NXT_UNIT_OK; +} + + +jobject +nxt_java_newHeadersEnumeration(JNIEnv *env, nxt_unit_field_t *f, + uint32_t fields_count, uint32_t pos) +{ + return (*env)->NewObject(env, + nxt_java_HeadersEnumeration_class, + nxt_java_HeadersEnumeration_ctor, nxt_ptr2jlong(f), + (jlong) fields_count, (jlong) pos); +} + + +static jlong JNICALL +nxt_java_HeadersEnumeration_nextElementPos(JNIEnv *env, jclass cls, + jlong headers_ptr, jlong size, jlong ipos, jlong pos) +{ + nxt_unit_field_t *f, *init_field; + + f = nxt_jlong2ptr(headers_ptr); + + init_field = f + ipos; + + if (pos >= size) { + return size; + } + + f += pos; + + if (f->hash != init_field->hash + || f->name_length != init_field->name_length) + { + return size; + } + + if (!nxt_java_strcaseeq(nxt_unit_sptr_get(&f->name), + nxt_unit_sptr_get(&init_field->name), + init_field->name_length)) + { + return size; + } + + return pos; +} + + +static jstring JNICALL +nxt_java_HeadersEnumeration_nextElement(JNIEnv *env, jclass cls, + jlong headers_ptr, jlong size, jlong ipos, jlong pos) +{ + nxt_unit_field_t *f, *init_field; + + f = nxt_jlong2ptr(headers_ptr); + + init_field = f + ipos; + + if (pos >= size) { + nxt_java_throw_IOException(env, "pos >= size"); + + return NULL; + } + + f += pos; + + if (f->hash != init_field->hash + || f->name_length != init_field->name_length) + { + nxt_java_throw_IOException(env, "f->hash != hash"); + + return NULL; + } + + return nxt_java_newString(env, nxt_unit_sptr_get(&f->value), + f->value_length); +} diff --git a/src/java/nxt_jni_HeadersEnumeration.h b/src/java/nxt_jni_HeadersEnumeration.h new file mode 100644 index 00000000..10f9393c --- /dev/null +++ b/src/java/nxt_jni_HeadersEnumeration.h @@ -0,0 +1,19 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NXT_JAVA_HEADERSENUMERATION_H_INCLUDED_ +#define _NXT_JAVA_HEADERSENUMERATION_H_INCLUDED_ + + +#include +#include + + +int nxt_java_initHeadersEnumeration(JNIEnv *env, jobject cl); + +jobject nxt_java_newHeadersEnumeration(JNIEnv *env, nxt_unit_field_t *f, + uint32_t fields_count, uint32_t pos); + +#endif /* _NXT_JAVA_HEADERSENUMERATION_H_INCLUDED_ */ diff --git a/src/java/nxt_jni_InputStream.c b/src/java/nxt_jni_InputStream.c new file mode 100644 index 00000000..b96ff742 --- /dev/null +++ b/src/java/nxt_jni_InputStream.c @@ -0,0 +1,230 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#include + +#include +#include +#include + +#include "nxt_jni.h" +#include "nxt_jni_InputStream.h" +#include "nxt_jni_URLClassLoader.h" + + +static jint JNICALL nxt_java_InputStream_readLine(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray b, jint off, jint len); +static jboolean JNICALL nxt_java_InputStream_isFinished(JNIEnv *env, jclass cls, + jlong req_info_ptr); +static jint JNICALL nxt_java_InputStream_readByte(JNIEnv *env, jclass cls, + jlong req_info_ptr); +static jint JNICALL nxt_java_InputStream_read(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray b, jint off, jint len); +static jlong JNICALL nxt_java_InputStream_skip(JNIEnv *env, jclass cls, + jlong req_info_ptr, jlong n); +static jint JNICALL nxt_java_InputStream_available(JNIEnv *env, jclass cls, + jlong req_info_ptr); + + +static jclass nxt_java_InputStream_class; + + +int +nxt_java_initInputStream(JNIEnv *env, jobject cl) +{ + int res; + jclass cls; + + cls = nxt_java_loadClass(env, cl, "nginx.unit.InputStream"); + if (cls == NULL) { + return NXT_UNIT_ERROR; + } + + nxt_java_InputStream_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + + JNINativeMethod is_methods[] = { + { (char *) "readLine", + (char *) "(J[BII)I", + nxt_java_InputStream_readLine }, + + { (char *) "isFinished", + (char *) "(J)Z", + nxt_java_InputStream_isFinished }, + + { (char *) "read", + (char *) "(J)I", + nxt_java_InputStream_readByte }, + + { (char *) "read", + (char *) "(J[BII)I", + nxt_java_InputStream_read }, + + { (char *) "skip", + (char *) "(JJ)J", + nxt_java_InputStream_skip }, + + { (char *) "available", + (char *) "(J)I", + nxt_java_InputStream_available }, + }; + + res = (*env)->RegisterNatives(env, nxt_java_InputStream_class, + is_methods, + sizeof(is_methods) / sizeof(is_methods[0])); + + nxt_unit_debug(NULL, "registered InputStream methods: %d", res); + + if (res != 0) { + (*env)->DeleteGlobalRef(env, cls); + return NXT_UNIT_ERROR; + } + + return NXT_UNIT_OK; +} + + +static jint JNICALL +nxt_java_InputStream_readLine(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray out, jint off, jint len) +{ + char *p; + jint size, b_size; + uint8_t *data; + ssize_t res; + nxt_unit_buf_t *b; + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + size = 0; + + for (b = req->content_buf; b; b = nxt_unit_buf_next(b)) { + b_size = b->end - b->free; + p = memchr(b->free, '\n', b_size); + + if (p != NULL) { + p++; + size += p - b->free; + break; + } + + size += b_size; + + if (size >= len) { + break; + } + } + + len = len < size ? len : size; + + data = (*env)->GetPrimitiveArrayCritical(env, out, NULL); + + res = nxt_unit_request_read(req, data + off, len); + + nxt_unit_req_debug(req, "readLine '%.*s'", res, (char *) data + off); + + (*env)->ReleasePrimitiveArrayCritical(env, out, data, 0); + + return res > 0 ? res : -1; +} + + +static jboolean JNICALL +nxt_java_InputStream_isFinished(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + return req->content_length == 0; +} + + +static jint JNICALL +nxt_java_InputStream_readByte(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + uint8_t b; + ssize_t size; + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + size = nxt_unit_request_read(req, &b, 1); + + return size == 1 ? b : -1; +} + + +static jint JNICALL +nxt_java_InputStream_read(JNIEnv *env, jclass cls, jlong req_info_ptr, + jarray b, jint off, jint len) +{ + uint8_t *data; + ssize_t res; + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + data = (*env)->GetPrimitiveArrayCritical(env, b, NULL); + + res = nxt_unit_request_read(req, data + off, len); + + nxt_unit_req_debug(req, "read '%.*s'", res, (char *) data + off); + + (*env)->ReleasePrimitiveArrayCritical(env, b, data, 0); + + return res > 0 ? res : -1; +} + + +static jlong JNICALL +nxt_java_InputStream_skip(JNIEnv *env, jclass cls, jlong req_info_ptr, jlong n) +{ + size_t rest, b_size; + nxt_unit_buf_t *buf; + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + rest = n; + + buf = req->content_buf; + + while (buf != NULL) { + b_size = buf->end - buf->free; + b_size = rest < b_size ? rest : b_size; + + buf->free += b_size; + rest -= b_size; + + if (rest == 0) { + if (buf->end == buf->free) { + buf = nxt_unit_buf_next(buf); + } + + break; + } + + buf = nxt_unit_buf_next(buf); + } + + n = n < (jlong) req->content_length ? n : (jlong) req->content_length; + + req->content_length -= n; + + return n; +} + + +static jint JNICALL +nxt_java_InputStream_available(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + return req->content_length; +} diff --git a/src/java/nxt_jni_InputStream.h b/src/java/nxt_jni_InputStream.h new file mode 100644 index 00000000..b20b262a --- /dev/null +++ b/src/java/nxt_jni_InputStream.h @@ -0,0 +1,15 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NXT_JAVA_INPUTSTREAM_H_INCLUDED_ +#define _NXT_JAVA_INPUTSTREAM_H_INCLUDED_ + + +#include + + +int nxt_java_initInputStream(JNIEnv *env, jobject cl); + +#endif /* _NXT_JAVA_INPUTSTREAM_H_INCLUDED_ */ diff --git a/src/java/nxt_jni_OutputStream.c b/src/java/nxt_jni_OutputStream.c new file mode 100644 index 00000000..170b33ba --- /dev/null +++ b/src/java/nxt_jni_OutputStream.c @@ -0,0 +1,236 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#include + +#include +#include + +#include "nxt_jni.h" +#include "nxt_jni_OutputStream.h" +#include "nxt_jni_URLClassLoader.h" + + +static void JNICALL nxt_java_OutputStream_writeByte(JNIEnv *env, jclass cls, + jlong req_info_ptr, jint b); +static nxt_unit_buf_t *nxt_java_OutputStream_req_buf(JNIEnv *env, + nxt_unit_request_info_t *req); +static void JNICALL nxt_java_OutputStream_write(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray b, jint off, jint len); +static void JNICALL nxt_java_OutputStream_flush(JNIEnv *env, jclass cls, + jlong req_info_ptr); +static void JNICALL nxt_java_OutputStream_close(JNIEnv *env, jclass cls, + jlong req_info_ptr); + + +static jclass nxt_java_OutputStream_class; + + +int +nxt_java_initOutputStream(JNIEnv *env, jobject cl) +{ + int res; + jclass cls; + + cls = nxt_java_loadClass(env, cl, "nginx.unit.OutputStream"); + if (cls == NULL) { + return NXT_UNIT_ERROR; + } + + nxt_java_OutputStream_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + + cls = nxt_java_OutputStream_class; + + JNINativeMethod os_methods[] = { + { (char *) "write", + (char *) "(JI)V", + nxt_java_OutputStream_writeByte }, + + { (char *) "write", + (char *) "(J[BII)V", + nxt_java_OutputStream_write }, + + { (char *) "flush", + (char *) "(J)V", + nxt_java_OutputStream_flush }, + + { (char *) "close", + (char *) "(J)V", + nxt_java_OutputStream_close }, + }; + + res = (*env)->RegisterNatives(env, nxt_java_OutputStream_class, + os_methods, + sizeof(os_methods) / sizeof(os_methods[0])); + + nxt_unit_debug(NULL, "registered OutputStream methods: %d", res); + + if (res != 0) { + (*env)->DeleteGlobalRef(env, cls); + return NXT_UNIT_ERROR; + } + + return NXT_UNIT_OK; +} + + +static void JNICALL +nxt_java_OutputStream_writeByte(JNIEnv *env, jclass cls, jlong req_info_ptr, + jint b) +{ + nxt_unit_buf_t *buf; + nxt_unit_request_info_t *req; + nxt_java_request_data_t *data; + + req = nxt_jlong2ptr(req_info_ptr); + data = req->data; + + buf = nxt_java_OutputStream_req_buf(env, req); + if (buf == NULL) { + return; + } + + *buf->free++ = b; + + if ((uint32_t) (buf->free - buf->start) >= data->buf_size) { + nxt_java_OutputStream_flush_buf(env, req); + } +} + + +int +nxt_java_OutputStream_flush_buf(JNIEnv *env, nxt_unit_request_info_t *req) +{ + int rc; + nxt_java_request_data_t *data; + + data = req->data; + + if (!nxt_unit_response_is_init(req)) { + rc = nxt_unit_response_init(req, 200, 0, 0); + if (rc != NXT_UNIT_OK) { + nxt_java_throw_IOException(env, "Failed to allocate response"); + + return rc; + } + } + + if (!nxt_unit_response_is_sent(req)) { + rc = nxt_unit_response_send(req); + if (rc != NXT_UNIT_OK) { + nxt_java_throw_IOException(env, "Failed to send response headers"); + + return rc; + } + } + + if (data->buf != NULL) { + rc = nxt_unit_buf_send(data->buf); + if (rc != NXT_UNIT_OK) { + nxt_java_throw_IOException(env, "Failed to send buffer"); + + } else { + data->buf = NULL; + } + + } else { + rc = NXT_UNIT_OK; + } + + return rc; +} + + +static nxt_unit_buf_t * +nxt_java_OutputStream_req_buf(JNIEnv *env, nxt_unit_request_info_t *req) +{ + uint32_t size; + nxt_unit_buf_t *buf; + nxt_java_request_data_t *data; + + data = req->data; + buf = data->buf; + + if (buf == NULL || buf->free >= buf->end) { + size = data->buf_size == 0 ? nxt_unit_buf_min() : data->buf_size; + + buf = nxt_unit_response_buf_alloc(req, size); + if (buf == NULL) { + nxt_java_throw_IOException(env, "Failed to allocate buffer"); + + return NULL; + } + + data->buf = buf; + } + + return buf; +} + + +static void JNICALL +nxt_java_OutputStream_write(JNIEnv *env, jclass cls, jlong req_info_ptr, + jarray b, jint off, jint len) +{ + int rc; + jint copy; + uint8_t *ptr; + nxt_unit_buf_t *buf; + nxt_unit_request_info_t *req; + nxt_java_request_data_t *data; + + req = nxt_jlong2ptr(req_info_ptr); + data = req->data; + + ptr = (*env)->GetPrimitiveArrayCritical(env, b, NULL); + + while (len > 0) { + buf = nxt_java_OutputStream_req_buf(env, req); + if (buf == NULL) { + return; + } + + copy = buf->end - buf->free; + copy = copy < len ? copy : len; + + memcpy(buf->free, ptr + off, copy); + buf->free += copy; + + len -= copy; + off += copy; + + if ((uint32_t) (buf->free - buf->start) >= data->buf_size) { + rc = nxt_java_OutputStream_flush_buf(env, req); + if (rc != NXT_UNIT_OK) { + break; + } + } + } + + (*env)->ReleasePrimitiveArrayCritical(env, b, ptr, 0); +} + + +static void JNICALL +nxt_java_OutputStream_flush(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_unit_request_info_t *req; + nxt_java_request_data_t *data; + + req = nxt_jlong2ptr(req_info_ptr); + data = req->data; + + if (data->buf != NULL && data->buf->free > data->buf->start) { + nxt_java_OutputStream_flush_buf(env, req); + } +} + + +static void JNICALL +nxt_java_OutputStream_close(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_java_OutputStream_flush_buf(env, nxt_jlong2ptr(req_info_ptr)); +} diff --git a/src/java/nxt_jni_OutputStream.h b/src/java/nxt_jni_OutputStream.h new file mode 100644 index 00000000..0c3c9989 --- /dev/null +++ b/src/java/nxt_jni_OutputStream.h @@ -0,0 +1,17 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NXT_JAVA_OUTPUTSTREAM_H_INCLUDED_ +#define _NXT_JAVA_OUTPUTSTREAM_H_INCLUDED_ + + +#include + + +int nxt_java_initOutputStream(JNIEnv *env, jobject cl); + +int nxt_java_OutputStream_flush_buf(JNIEnv *env, nxt_unit_request_info_t *req); + +#endif /* _NXT_JAVA_OUTPUTSTREAM_H_INCLUDED_ */ diff --git a/src/java/nxt_jni_Request.c b/src/java/nxt_jni_Request.c new file mode 100644 index 00000000..6fb9cb44 --- /dev/null +++ b/src/java/nxt_jni_Request.c @@ -0,0 +1,658 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#include + +#include +#include +#include +#include +#include + +#include "nxt_jni.h" +#include "nxt_jni_Request.h" +#include "nxt_jni_URLClassLoader.h" +#include "nxt_jni_HeadersEnumeration.h" +#include "nxt_jni_HeaderNamesEnumeration.h" + + +static jstring JNICALL nxt_java_Request_getHeader(JNIEnv *env, jclass cls, + jlong req_ptr, jstring name, jint name_len); +static jobject JNICALL nxt_java_Request_getHeaderNames(JNIEnv *env, jclass cls, + jlong req_ptr); +static jobject JNICALL nxt_java_Request_getHeaders(JNIEnv *env, jclass cls, + jlong req_ptr, jstring name, jint name_len); +static jint JNICALL nxt_java_Request_getIntHeader(JNIEnv *env, jclass cls, + jlong req_ptr, jstring name, jint name_len); +static jstring JNICALL nxt_java_Request_getMethod(JNIEnv *env, jclass cls, + jlong req_ptr); +static jstring JNICALL nxt_java_Request_getQueryString(JNIEnv *env, jclass cls, + jlong req_ptr); +static jstring JNICALL nxt_java_Request_getRequestURI(JNIEnv *env, jclass cls, + jlong req_ptr); +static jlong JNICALL nxt_java_Request_getContentLength(JNIEnv *env, jclass cls, + jlong req_ptr); +static jstring JNICALL nxt_java_Request_getContentType(JNIEnv *env, jclass cls, + jlong req_ptr); +static jstring JNICALL nxt_java_Request_getLocalAddr(JNIEnv *env, jclass cls, + jlong req_ptr); +static jstring JNICALL nxt_java_Request_getLocalName(JNIEnv *env, jclass cls, + jlong req_ptr); +static jint JNICALL nxt_java_Request_getLocalPort(JNIEnv *env, jclass cls, + jlong req_ptr); +static jstring JNICALL nxt_java_Request_getProtocol(JNIEnv *env, jclass cls, + jlong req_ptr); +static jstring JNICALL nxt_java_Request_getRemoteAddr(JNIEnv *env, jclass cls, + jlong req_ptr); +static jstring JNICALL nxt_java_Request_getRemoteHost(JNIEnv *env, jclass cls, + jlong req_ptr); +static jint JNICALL nxt_java_Request_getRemotePort(JNIEnv *env, jclass cls, + jlong req_ptr); +static jstring JNICALL nxt_java_Request_getScheme(JNIEnv *env, jclass cls, + jlong req_ptr); +static jstring JNICALL nxt_java_Request_getServerName(JNIEnv *env, jclass cls, + jlong req_ptr); +static jint JNICALL nxt_java_Request_getServerPort(JNIEnv *env, jclass cls, + jlong req_ptr); +static void JNICALL nxt_java_Request_log(JNIEnv *env, jclass cls, + jlong req_info_ptr, jstring msg, jint msg_len); +static void JNICALL nxt_java_Request_trace(JNIEnv *env, jclass cls, + jlong req_info_ptr, jstring msg, jint msg_len); +static jobject JNICALL nxt_java_Request_getResponse(JNIEnv *env, jclass cls, + jlong req_info_ptr); + + +static jclass nxt_java_Request_class; +static jmethodID nxt_java_Request_ctor; + + +int +nxt_java_initRequest(JNIEnv *env, jobject cl) +{ + int res; + jclass cls; + + cls = nxt_java_loadClass(env, cl, "nginx.unit.Request"); + if (cls == NULL) { + return NXT_UNIT_ERROR; + } + + nxt_java_Request_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + cls = nxt_java_Request_class; + + nxt_java_Request_ctor = (*env)->GetMethodID(env, cls, "", "(Lnginx/unit/Context;JJ)V"); + if (nxt_java_Request_ctor == NULL) { + (*env)->DeleteGlobalRef(env, cls); + return NXT_UNIT_ERROR; + } + + JNINativeMethod request_methods[] = { + { (char *) "getHeader", + (char *) "(JLjava/lang/String;I)Ljava/lang/String;", + nxt_java_Request_getHeader }, + + { (char *) "getHeaderNames", + (char *) "(J)Ljava/util/Enumeration;", + nxt_java_Request_getHeaderNames }, + + { (char *) "getHeaders", + (char *) "(JLjava/lang/String;I)Ljava/util/Enumeration;", + nxt_java_Request_getHeaders }, + + { (char *) "getIntHeader", + (char *) "(JLjava/lang/String;I)I", + nxt_java_Request_getIntHeader }, + + { (char *) "getMethod", + (char *) "(J)Ljava/lang/String;", + nxt_java_Request_getMethod }, + + { (char *) "getQueryString", + (char *) "(J)Ljava/lang/String;", + nxt_java_Request_getQueryString }, + + { (char *) "getRequestURI", + (char *) "(J)Ljava/lang/String;", + nxt_java_Request_getRequestURI }, + + { (char *) "getContentLength", + (char *) "(J)J", + nxt_java_Request_getContentLength }, + + { (char *) "getContentType", + (char *) "(J)Ljava/lang/String;", + nxt_java_Request_getContentType }, + + { (char *) "getLocalAddr", + (char *) "(J)Ljava/lang/String;", + nxt_java_Request_getLocalAddr }, + + { (char *) "getLocalName", + (char *) "(J)Ljava/lang/String;", + nxt_java_Request_getLocalName }, + + { (char *) "getLocalPort", + (char *) "(J)I", + nxt_java_Request_getLocalPort }, + + { (char *) "getProtocol", + (char *) "(J)Ljava/lang/String;", + nxt_java_Request_getProtocol }, + + { (char *) "getRemoteAddr", + (char *) "(J)Ljava/lang/String;", + nxt_java_Request_getRemoteAddr }, + + { (char *) "getRemoteHost", + (char *) "(J)Ljava/lang/String;", + nxt_java_Request_getRemoteHost }, + + { (char *) "getRemotePort", + (char *) "(J)I", + nxt_java_Request_getRemotePort }, + + { (char *) "getScheme", + (char *) "(J)Ljava/lang/String;", + nxt_java_Request_getScheme }, + + { (char *) "getServerName", + (char *) "(J)Ljava/lang/String;", + nxt_java_Request_getServerName }, + + { (char *) "getServerPort", + (char *) "(J)I", + nxt_java_Request_getServerPort }, + + { (char *) "log", + (char *) "(JLjava/lang/String;I)V", + nxt_java_Request_log }, + + { (char *) "trace", + (char *) "(JLjava/lang/String;I)V", + nxt_java_Request_trace }, + + { (char *) "getResponse", + (char *) "(J)Lnginx/unit/Response;", + nxt_java_Request_getResponse }, + + }; + + res = (*env)->RegisterNatives(env, nxt_java_Request_class, + request_methods, + sizeof(request_methods) / sizeof(request_methods[0])); + + nxt_unit_debug(NULL, "registered Request methods: %d", res); + + if (res != 0) { + nxt_unit_warn(NULL, "registering natives for Request failed"); + goto failed; + } + + res = nxt_java_initHeadersEnumeration(env, cl); + if (res != NXT_UNIT_OK) { + goto failed; + } + + res = nxt_java_initHeaderNamesEnumeration(env, cl); + if (res != NXT_UNIT_OK) { + goto failed; + } + + return NXT_UNIT_OK; + +failed: + + (*env)->DeleteGlobalRef(env, cls); + return NXT_UNIT_ERROR; +} + + +jobject +nxt_java_newRequest(JNIEnv *env, jobject ctx, nxt_unit_request_info_t *req) +{ + return (*env)->NewObject(env, nxt_java_Request_class, + nxt_java_Request_ctor, ctx, nxt_ptr2jlong(req), + nxt_ptr2jlong(req->request)); +} + + +static jstring JNICALL +nxt_java_Request_getHeader(JNIEnv *env, jclass cls, jlong req_ptr, + jstring name, jint name_len) +{ + const char *name_str; + nxt_unit_field_t *f; + nxt_unit_request_t *r; + + name_str = (*env)->GetStringUTFChars(env, name, NULL); + if (name_str == NULL) { + return NULL; + } + + r = nxt_jlong2ptr(req_ptr); + + f = nxt_java_findHeader(r->fields, r->fields + r->fields_count, + name_str, name_len); + + (*env)->ReleaseStringUTFChars(env, name, name_str); + + if (f == NULL) { + return NULL; + } + + return (*env)->NewStringUTF(env, nxt_unit_sptr_get(&f->value)); +} + + +static jobject JNICALL +nxt_java_Request_getHeaderNames(JNIEnv *env, jclass cls, jlong req_ptr) +{ + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + return nxt_java_newHeaderNamesEnumeration(env, r->fields, r->fields_count); +} + + +static jobject JNICALL +nxt_java_Request_getHeaders(JNIEnv *env, jclass cls, jlong req_ptr, + jstring name, jint name_len) +{ + const char *name_str; + nxt_unit_field_t *f; + nxt_unit_request_t *r; + + name_str = (*env)->GetStringUTFChars(env, name, NULL); + if (name_str == NULL) { + return NULL; + } + + r = nxt_jlong2ptr(req_ptr); + + f = nxt_java_findHeader(r->fields, r->fields + r->fields_count, + name_str, name_len); + + (*env)->ReleaseStringUTFChars(env, name, name_str); + + if (f == NULL) { + f = r->fields + r->fields_count; + } + + return nxt_java_newHeadersEnumeration(env, r->fields, r->fields_count, + f - r->fields); +} + + +static jint JNICALL +nxt_java_Request_getIntHeader(JNIEnv *env, jclass cls, jlong req_ptr, + jstring name, jint name_len) +{ + jint res; + char *value, *end; + const char *name_str; + nxt_unit_field_t *f; + nxt_unit_request_t *r; + + res = -1; + + name_str = (*env)->GetStringUTFChars(env, name, NULL); + if (name_str == NULL) { + return res; + } + + r = nxt_jlong2ptr(req_ptr); + + f = nxt_java_findHeader(r->fields, r->fields + r->fields_count, + name_str, name_len); + + (*env)->ReleaseStringUTFChars(env, name, name_str); + + if (f == NULL) { + return res; + } + + value = nxt_unit_sptr_get(&f->value); + end = value + f->value_length; + + res = strtol(value, &end, 10); + + if (end < value + f->value_length) { + // TODO throw NumberFormatException.forInputString(value) + } + + return res; +} + + +static jstring JNICALL +nxt_java_Request_getMethod(JNIEnv *env, jclass cls, jlong req_ptr) +{ + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + return (*env)->NewStringUTF(env, nxt_unit_sptr_get(&r->method)); +} + + +static jstring JNICALL +nxt_java_Request_getQueryString(JNIEnv *env, jclass cls, jlong req_ptr) +{ + char *query; + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + if (r->query.offset != 0) { + query = nxt_unit_sptr_get(&r->query); + return (*env)->NewStringUTF(env, query); + } + + return NULL; +} + + +static jstring JNICALL +nxt_java_Request_getRequestURI(JNIEnv *env, jclass cls, jlong req_ptr) +{ + char *target, *query; + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + target = nxt_unit_sptr_get(&r->target); + + if (r->query.offset != 0) { + query = nxt_unit_sptr_get(&r->query); + return nxt_java_newString(env, target, query - target - 1); + } + + return (*env)->NewStringUTF(env, target); +} + + +static jlong JNICALL +nxt_java_Request_getContentLength(JNIEnv *env, jclass cls, jlong req_ptr) +{ + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + return r->content_length; +} + + +static jstring JNICALL +nxt_java_Request_getContentType(JNIEnv *env, jclass cls, jlong req_ptr) +{ + nxt_unit_field_t *f; + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + if (r->content_type_field != NXT_UNIT_NONE_FIELD) { + f = r->fields + r->content_type_field; + + return (*env)->NewStringUTF(env, nxt_unit_sptr_get(&f->value)); + } + + return NULL; +} + + +static jstring JNICALL +nxt_java_Request_getLocalAddr(JNIEnv *env, jclass cls, jlong req_ptr) +{ + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + return nxt_java_newString(env, nxt_unit_sptr_get(&r->local), + r->local_length); +} + + +static jstring JNICALL +nxt_java_Request_getLocalName(JNIEnv *env, jclass cls, jlong req_ptr) +{ + char *local, *colon; + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + local = nxt_unit_sptr_get(&r->local); + colon = memchr(local, ':', r->local_length); + + if (colon == NULL) { + colon = local + r->local_length; + } + + return nxt_java_newString(env, local, colon - local); +} + + +static jint JNICALL +nxt_java_Request_getLocalPort(JNIEnv *env, jclass cls, jlong req_ptr) +{ + jint res; + char *local, *colon, tmp; + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + local = nxt_unit_sptr_get(&r->local); + colon = memchr(local, ':', r->local_length); + + if (colon == NULL) { + return 80; + } + + tmp = local[r->local_length]; + + local[r->local_length] = '\0'; + + res = strtol(colon + 1, NULL, 10); + + local[r->local_length] = tmp; + + return res; +} + + +static jstring JNICALL +nxt_java_Request_getProtocol(JNIEnv *env, jclass cls, jlong req_ptr) +{ + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + return (*env)->NewStringUTF(env, nxt_unit_sptr_get(&r->version)); +} + + +static jstring JNICALL +nxt_java_Request_getRemoteAddr(JNIEnv *env, jclass cls, jlong req_ptr) +{ + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + return nxt_java_newString(env, nxt_unit_sptr_get(&r->remote), + r->remote_length); +} + + +static jstring JNICALL +nxt_java_Request_getRemoteHost(JNIEnv *env, jclass cls, jlong req_ptr) +{ + char *remote, *colon; + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + remote = nxt_unit_sptr_get(&r->remote); + colon = memchr(remote, ':', r->remote_length); + + if (colon == NULL) { + colon = remote + r->remote_length; + } + + return nxt_java_newString(env, remote, colon - remote); +} + + +static jint JNICALL +nxt_java_Request_getRemotePort(JNIEnv *env, jclass cls, jlong req_ptr) +{ + jint res; + char *remote, *colon, tmp; + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + remote = nxt_unit_sptr_get(&r->remote); + colon = memchr(remote, ':', r->remote_length); + + if (colon == NULL) { + return 80; + } + + tmp = remote[r->remote_length]; + + remote[r->remote_length] = '\0'; + + res = strtol(colon + 1, NULL, 10); + + remote[r->remote_length] = tmp; + + return res; +} + + +static jstring JNICALL +nxt_java_Request_getScheme(JNIEnv *env, jclass cls, jlong req_ptr) +{ + return (*env)->NewStringUTF(env, "http"); +} + + +static jstring JNICALL +nxt_java_Request_getServerName(JNIEnv *env, jclass cls, jlong req_ptr) +{ + char *host, *colon; + nxt_unit_field_t *f; + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + f = nxt_java_findHeader(r->fields, r->fields + r->fields_count, + "Host", 4); + if (f != NULL) { + host = nxt_unit_sptr_get(&f->value); + + colon = memchr(host, ':', f->value_length); + + if (colon == NULL) { + colon = host + f->value_length; + } + + return nxt_java_newString(env, host, colon - host); + } + + return nxt_java_Request_getLocalName(env, cls, req_ptr); +} + + +static jint JNICALL +nxt_java_Request_getServerPort(JNIEnv *env, jclass cls, jlong req_ptr) +{ + jint res; + char *host, *colon, tmp; + nxt_unit_field_t *f; + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + f = nxt_java_findHeader(r->fields, r->fields + r->fields_count, + "Host", 4); + if (f != NULL) { + host = nxt_unit_sptr_get(&f->value); + + colon = memchr(host, ':', f->value_length); + + if (colon == NULL) { + return 80; + } + + tmp = host[f->value_length]; + + host[f->value_length] = '\0'; + + res = strtol(colon + 1, NULL, 10); + + host[f->value_length] = tmp; + + return res; + } + + return nxt_java_Request_getLocalPort(env, cls, req_ptr); +} + + +static void JNICALL +nxt_java_Request_log(JNIEnv *env, jclass cls, jlong req_info_ptr, jstring msg, + jint msg_len) +{ + const char *msg_str; + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + msg_str = (*env)->GetStringUTFChars(env, msg, NULL); + if (msg_str == NULL) { + return; + } + + nxt_unit_req_log(req, NXT_UNIT_LOG_INFO, "%.*s", msg_len, msg_str); + + (*env)->ReleaseStringUTFChars(env, msg, msg_str); +} + + +static void JNICALL +nxt_java_Request_trace(JNIEnv *env, jclass cls, jlong req_info_ptr, jstring msg, + jint msg_len) +{ +#if (NXT_DEBUG) + const char *msg_str; + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + msg_str = (*env)->GetStringUTFChars(env, msg, NULL); + if (msg_str == NULL) { + return; + } + + nxt_unit_req_debug(req, "%.*s", msg_len, msg_str); + + (*env)->ReleaseStringUTFChars(env, msg, msg_str); +#endif +} + + +static jobject JNICALL +nxt_java_Request_getResponse(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_unit_request_info_t *req; + nxt_java_request_data_t *data; + + req = nxt_jlong2ptr(req_info_ptr); + data = req->data; + + return data->jresp; +} diff --git a/src/java/nxt_jni_Request.h b/src/java/nxt_jni_Request.h new file mode 100644 index 00000000..1c9c1428 --- /dev/null +++ b/src/java/nxt_jni_Request.h @@ -0,0 +1,18 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NXT_JAVA_REQUEST_H_INCLUDED_ +#define _NXT_JAVA_REQUEST_H_INCLUDED_ + + +#include +#include + + +int nxt_java_initRequest(JNIEnv *env, jobject cl); + +jobject nxt_java_newRequest(JNIEnv *env, jobject ctx, nxt_unit_request_info_t *req); + +#endif /* _NXT_JAVA_REQUEST_H_INCLUDED_ */ diff --git a/src/java/nxt_jni_Response.c b/src/java/nxt_jni_Response.c new file mode 100644 index 00000000..2ccfd854 --- /dev/null +++ b/src/java/nxt_jni_Response.c @@ -0,0 +1,1105 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#include + +#include +#include +#include +#include + +#include "nxt_jni.h" +#include "nxt_jni_Response.h" +#include "nxt_jni_HeadersEnumeration.h" +#include "nxt_jni_HeaderNamesEnumeration.h" +#include "nxt_jni_OutputStream.h" +#include "nxt_jni_URLClassLoader.h" + + +static jclass nxt_java_Response_class; +static jmethodID nxt_java_Response_ctor; + + +static void JNICALL nxt_java_Response_addHeader(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray name, jarray value); + +static nxt_unit_request_info_t *nxt_java_get_response_info( + jlong req_info_ptr, uint32_t extra_fields, uint32_t extra_data); + +static void JNICALL nxt_java_Response_addIntHeader(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray name, jint value); + +static void nxt_java_add_int_header(nxt_unit_request_info_t *req, + const char *name, uint8_t name_len, int value); + +static jboolean JNICALL nxt_java_Response_containsHeader(JNIEnv *env, + jclass cls, jlong req_info_ptr, jarray name); + +static jstring JNICALL nxt_java_Response_getHeader(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray name); + +static jobject JNICALL nxt_java_Response_getHeaderNames(JNIEnv *env, + jclass cls, jlong req_info_ptr); + +static jobject JNICALL nxt_java_Response_getHeaders(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray name); + +static jint JNICALL nxt_java_Response_getStatus(JNIEnv *env, jclass cls, + jlong req_info_ptr); + +static jobject JNICALL nxt_java_Response_getRequest(JNIEnv *env, jclass cls, + jlong req_info_ptr); + +static void JNICALL nxt_java_Response_commit(JNIEnv *env, jclass cls, + jlong req_info_ptr); + +static void JNICALL nxt_java_Response_sendRedirect(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray loc); + +static int nxt_java_response_set_header(jlong req_info_ptr, + const char *name, jint name_len, const char *value, jint value_len); + +static void JNICALL nxt_java_Response_setHeader(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray name, jarray value); + +static void JNICALL nxt_java_Response_removeHeader(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray name); + +static int nxt_java_response_remove_header(jlong req_info_ptr, + const char *name, jint name_len); + +static void JNICALL nxt_java_Response_setIntHeader(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray name, jint value); + +static void JNICALL nxt_java_Response_setStatus(JNIEnv *env, jclass cls, + jlong req_info_ptr, jint sc); + +static jstring JNICALL nxt_java_Response_getContentType(JNIEnv *env, + jclass cls, jlong req_info_ptr); + +static jboolean JNICALL nxt_java_Response_isCommitted(JNIEnv *env, jclass cls, + jlong req_info_ptr); + +static void JNICALL nxt_java_Response_reset(JNIEnv *env, jclass cls, + jlong req_info_ptr); + +static void JNICALL nxt_java_Response_resetBuffer(JNIEnv *env, jclass cls, + jlong req_info_ptr); + +static void JNICALL nxt_java_Response_setBufferSize(JNIEnv *env, jclass cls, + jlong req_info_ptr, jint size); + +static jint JNICALL nxt_java_Response_getBufferSize(JNIEnv *env, jclass cls, + jlong req_info_ptr); + +static void JNICALL nxt_java_Response_setContentLength(JNIEnv *env, jclass cls, + jlong req_info_ptr, jlong len); + +static void JNICALL nxt_java_Response_setContentType(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray type); + +static void JNICALL nxt_java_Response_removeContentType(JNIEnv *env, jclass cls, + jlong req_info_ptr); + +static void JNICALL nxt_java_Response_log(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray msg); + +static void JNICALL nxt_java_Response_trace(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray msg); + +int +nxt_java_initResponse(JNIEnv *env, jobject cl) +{ + int res; + jclass cls; + + cls = nxt_java_loadClass(env, cl, "nginx.unit.Response"); + if (cls == NULL) { + return NXT_UNIT_ERROR; + } + + nxt_java_Response_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + cls = nxt_java_Response_class; + + nxt_java_Response_ctor = (*env)->GetMethodID(env, cls, "", "(J)V"); + if (nxt_java_Response_ctor == NULL) { + (*env)->DeleteGlobalRef(env, cls); + return NXT_UNIT_ERROR; + } + + JNINativeMethod resp_methods[] = { + { (char *) "addHeader", + (char *) "(J[B[B)V", + nxt_java_Response_addHeader }, + + { (char *) "addIntHeader", + (char *) "(J[BI)V", + nxt_java_Response_addIntHeader }, + + { (char *) "containsHeader", + (char *) "(J[B)Z", + nxt_java_Response_containsHeader }, + + { (char *) "getHeader", + (char *) "(J[B)Ljava/lang/String;", + nxt_java_Response_getHeader }, + + { (char *) "getHeaderNames", + (char *) "(J)Ljava/util/Enumeration;", + nxt_java_Response_getHeaderNames }, + + { (char *) "getHeaders", + (char *) "(J[B)Ljava/util/Enumeration;", + nxt_java_Response_getHeaders }, + + { (char *) "getStatus", + (char *) "(J)I", + nxt_java_Response_getStatus }, + + { (char *) "getRequest", + (char *) "(J)Lnginx/unit/Request;", + nxt_java_Response_getRequest }, + + { (char *) "commit", + (char *) "(J)V", + nxt_java_Response_commit }, + + { (char *) "sendRedirect", + (char *) "(J[B)V", + nxt_java_Response_sendRedirect }, + + { (char *) "setHeader", + (char *) "(J[B[B)V", + nxt_java_Response_setHeader }, + + { (char *) "removeHeader", + (char *) "(J[B)V", + nxt_java_Response_removeHeader }, + + { (char *) "setIntHeader", + (char *) "(J[BI)V", + nxt_java_Response_setIntHeader }, + + { (char *) "setStatus", + (char *) "(JI)V", + nxt_java_Response_setStatus }, + + { (char *) "getContentType", + (char *) "(J)Ljava/lang/String;", + nxt_java_Response_getContentType }, + + { (char *) "isCommitted", + (char *) "(J)Z", + nxt_java_Response_isCommitted }, + + { (char *) "reset", + (char *) "(J)V", + nxt_java_Response_reset }, + + { (char *) "resetBuffer", + (char *) "(J)V", + nxt_java_Response_resetBuffer }, + + { (char *) "setBufferSize", + (char *) "(JI)V", + nxt_java_Response_setBufferSize }, + + { (char *) "getBufferSize", + (char *) "(J)I", + nxt_java_Response_getBufferSize }, + + { (char *) "setContentLength", + (char *) "(JJ)V", + nxt_java_Response_setContentLength }, + + { (char *) "setContentType", + (char *) "(J[B)V", + nxt_java_Response_setContentType }, + + { (char *) "removeContentType", + (char *) "(J)V", + nxt_java_Response_removeContentType }, + + { (char *) "log", + (char *) "(J[B)V", + nxt_java_Response_log }, + + { (char *) "trace", + (char *) "(J[B)V", + nxt_java_Response_trace }, + + }; + + res = (*env)->RegisterNatives(env, nxt_java_Response_class, + resp_methods, + sizeof(resp_methods) + / sizeof(resp_methods[0])); + + nxt_unit_debug(NULL, "registered Response methods: %d", res); + + if (res != 0) { + (*env)->DeleteGlobalRef(env, cls); + return NXT_UNIT_ERROR; + } + + return NXT_UNIT_OK; +} + + +jobject +nxt_java_newResponse(JNIEnv *env, nxt_unit_request_info_t *req) +{ + return (*env)->NewObject(env, nxt_java_Response_class, + nxt_java_Response_ctor, nxt_ptr2jlong(req)); +} + + +static void JNICALL +nxt_java_Response_addHeader(JNIEnv *env, jclass cls, jlong req_info_ptr, + jarray name, jarray value) +{ + int rc; + char *name_str, *value_str; + jsize name_len, value_len; + nxt_unit_request_info_t *req; + + name_len = (*env)->GetArrayLength(env, name); + value_len = (*env)->GetArrayLength(env, value); + + req = nxt_java_get_response_info(req_info_ptr, 1, name_len + value_len + 2); + if (req == NULL) { + return; + } + + name_str = (*env)->GetPrimitiveArrayCritical(env, name, NULL); + if (name_str == NULL) { + nxt_unit_req_warn(req, "addHeader: failed to get name content"); + return; + } + + value_str = (*env)->GetPrimitiveArrayCritical(env, value, NULL); + if (value_str == NULL) { + (*env)->ReleasePrimitiveArrayCritical(env, name, name_str, 0); + nxt_unit_req_warn(req, "addHeader: failed to get value content"); + + return; + } + + rc = nxt_unit_response_add_field(req, name_str, name_len, + value_str, value_len); + if (rc != NXT_UNIT_OK) { + // throw + } + + (*env)->ReleasePrimitiveArrayCritical(env, value, value_str, 0); + (*env)->ReleasePrimitiveArrayCritical(env, name, name_str, 0); +} + + +static nxt_unit_request_info_t * +nxt_java_get_response_info(jlong req_info_ptr, uint32_t extra_fields, + uint32_t extra_data) +{ + int rc; + char *p; + uint32_t max_size; + nxt_unit_buf_t *buf; + nxt_unit_request_info_t *req; + nxt_java_request_data_t *data; + + req = nxt_jlong2ptr(req_info_ptr); + + if (nxt_unit_response_is_sent(req)) { + return NULL; + } + + data = req->data; + + if (!nxt_unit_response_is_init(req)) { + max_size = nxt_unit_buf_max(); + max_size = max_size < data->header_size ? max_size : data->header_size; + + rc = nxt_unit_response_init(req, 200, 16, max_size); + if (rc != NXT_UNIT_OK) { + return NULL; + } + } + + buf = req->response_buf; + + if (extra_fields > req->response_max_fields + - req->response->fields_count + || extra_data > (uint32_t) (buf->end - buf->free)) + { + p = buf->start + req->response_max_fields * sizeof(nxt_unit_field_t); + + max_size = 2 * (buf->end - p); + if (max_size > nxt_unit_buf_max()) { + nxt_unit_req_warn(req, "required max_size is too big: %"PRIu32, + max_size); + return NULL; + } + + rc = nxt_unit_response_realloc(req, 2 * req->response_max_fields, + max_size); + if (rc != NXT_UNIT_OK) { + nxt_unit_req_warn(req, "reallocation failed: %"PRIu32", %"PRIu32, + 2 * req->response_max_fields, max_size); + return NULL; + } + } + + return req; +} + + +static void JNICALL +nxt_java_Response_addIntHeader(JNIEnv *env, jclass cls, jlong req_info_ptr, + jarray name, jint value) +{ + char *name_str; + jsize name_len; + nxt_unit_request_info_t *req; + + name_len = (*env)->GetArrayLength(env, name); + + req = nxt_java_get_response_info(req_info_ptr, 1, name_len + 40); + if (req == NULL) { + return; + } + + name_str = (*env)->GetPrimitiveArrayCritical(env, name, NULL); + if (name_str == NULL) { + nxt_unit_req_warn(req, "addIntHeader: failed to get name content"); + return; + } + + nxt_java_add_int_header(req, name_str, name_len, value); + + (*env)->ReleasePrimitiveArrayCritical(env, name, name_str, 0); +} + + +static void +nxt_java_add_int_header(nxt_unit_request_info_t *req, const char *name, + uint8_t name_len, int value) +{ + char *p; + nxt_unit_field_t *f; + nxt_unit_response_t *resp; + + resp = req->response; + + f = resp->fields + resp->fields_count; + p = req->response_buf->free; + + f->hash = nxt_unit_field_hash(name, name_len); + f->skip = 0; + f->name_length = name_len; + + nxt_unit_sptr_set(&f->name, p); + memcpy(p, name, name_len); + p += name_len; + + nxt_unit_sptr_set(&f->value, p); + f->value_length = snprintf(p, 40, "%d", (int) value); + p += f->value_length + 1; + + resp->fields_count++; + req->response_buf->free = p; + +} + + +static jboolean JNICALL +nxt_java_Response_containsHeader(JNIEnv *env, + jclass cls, jlong req_info_ptr, jarray name) +{ + jboolean res; + char *name_str; + jsize name_len; + nxt_unit_response_t *resp; + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + if (!nxt_unit_response_is_init(req)) { + nxt_unit_req_debug(req, "containsHeader: response is not initialized"); + return 0; + } + + if (nxt_unit_response_is_sent(req)) { + nxt_unit_req_debug(req, "containsHeader: response already sent"); + return 0; + } + + name_len = (*env)->GetArrayLength(env, name); + + name_str = (*env)->GetPrimitiveArrayCritical(env, name, NULL); + if (name_str == NULL) { + nxt_unit_req_warn(req, "containsHeader: failed to get name content"); + return 0; + } + + resp = req->response; + + res = nxt_java_findHeader(resp->fields, + resp->fields + resp->fields_count, + name_str, name_len) != NULL; + + (*env)->ReleasePrimitiveArrayCritical(env, name, name_str, 0); + + return res; +} + + +static jstring JNICALL +nxt_java_Response_getHeader(JNIEnv *env, jclass cls, jlong req_info_ptr, + jarray name) +{ + char *name_str; + jsize name_len; + nxt_unit_field_t *f; + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + if (!nxt_unit_response_is_init(req)) { + nxt_unit_req_debug(req, "getHeader: response is not initialized"); + return NULL; + } + + if (nxt_unit_response_is_sent(req)) { + nxt_unit_req_debug(req, "getHeader: response already sent"); + return NULL; + } + + name_len = (*env)->GetArrayLength(env, name); + + name_str = (*env)->GetPrimitiveArrayCritical(env, name, NULL); + if (name_str == NULL) { + nxt_unit_req_warn(req, "getHeader: failed to get name content"); + return NULL; + } + + f = nxt_java_findHeader(req->response->fields, + req->response->fields + req->response->fields_count, + name_str, name_len); + + (*env)->ReleasePrimitiveArrayCritical(env, name, name_str, 0); + + if (f == NULL) { + return NULL; + } + + return nxt_java_newString(env, nxt_unit_sptr_get(&f->value), + f->value_length); +} + + +static jobject JNICALL +nxt_java_Response_getHeaderNames(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + if (!nxt_unit_response_is_init(req)) { + nxt_unit_req_debug(req, "getHeaderNames: response is not initialized"); + return NULL; + } + + if (nxt_unit_response_is_sent(req)) { + nxt_unit_req_debug(req, "getHeaderNames: response already sent"); + return NULL; + } + + return nxt_java_newHeaderNamesEnumeration(env, req->response->fields, + req->response->fields_count); +} + + +static jobject JNICALL +nxt_java_Response_getHeaders(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray name) +{ + char *name_str; + jsize name_len; + nxt_unit_field_t *f; + nxt_unit_response_t *resp; + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + if (!nxt_unit_response_is_init(req)) { + nxt_unit_req_debug(req, "getHeaders: response is not initialized"); + return NULL; + } + + if (nxt_unit_response_is_sent(req)) { + nxt_unit_req_debug(req, "getHeaders: response already sent"); + return NULL; + } + + resp = req->response; + + name_len = (*env)->GetArrayLength(env, name); + + name_str = (*env)->GetPrimitiveArrayCritical(env, name, NULL); + if (name_str == NULL) { + nxt_unit_req_warn(req, "getHeaders: failed to get name content"); + return NULL; + } + + f = nxt_java_findHeader(resp->fields, resp->fields + resp->fields_count, + name_str, name_len); + + (*env)->ReleasePrimitiveArrayCritical(env, name, name_str, 0); + + if (f == NULL) { + f = resp->fields + resp->fields_count; + } + + return nxt_java_newHeadersEnumeration(env, resp->fields, resp->fields_count, + f - resp->fields); +} + + +static jint JNICALL +nxt_java_Response_getStatus(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + if (!nxt_unit_response_is_init(req)) { + nxt_unit_req_debug(req, "getStatus: response is not initialized"); + return 200; + } + + if (nxt_unit_response_is_sent(req)) { + nxt_unit_req_debug(req, "getStatus: response already sent"); + return 200; + } + + return req->response->status; +} + + +static jobject JNICALL +nxt_java_Response_getRequest(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_unit_request_info_t *req; + nxt_java_request_data_t *data; + + req = nxt_jlong2ptr(req_info_ptr); + data = req->data; + + return data->jreq; +} + + +static void JNICALL +nxt_java_Response_commit(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + nxt_java_OutputStream_flush_buf(env, req); +} + + +static void JNICALL +nxt_java_Response_sendRedirect(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray loc) +{ + int rc; + char *loc_str; + jsize loc_len; + nxt_unit_request_info_t *req; + + static const char location[] = "Location"; + static const uint32_t location_len = sizeof(location) - 1; + + req = nxt_jlong2ptr(req_info_ptr); + + if (nxt_unit_response_is_sent(req)) { + nxt_java_throw_IllegalStateException(env, "Response already sent"); + + return; + } + + loc_len = (*env)->GetArrayLength(env, loc); + + req = nxt_java_get_response_info(req_info_ptr, 1, + location_len + loc_len + 2); + if (req == NULL) { + return; + } + + loc_str = (*env)->GetPrimitiveArrayCritical(env, loc, NULL); + if (loc_str == NULL) { + nxt_unit_req_warn(req, "sendRedirect: failed to get loc content"); + return; + } + + req->response->status = 302; + + rc = nxt_java_response_set_header(req_info_ptr, location, location_len, + loc_str, loc_len); + if (rc != NXT_UNIT_OK) { + // throw + } + + (*env)->ReleasePrimitiveArrayCritical(env, loc, loc_str, 0); + + nxt_unit_response_send(req); +} + + +static int +nxt_java_response_set_header(jlong req_info_ptr, + const char *name, jint name_len, const char *value, jint value_len) +{ + int add_field; + char *dst; + nxt_unit_field_t *f, *e; + nxt_unit_response_t *resp; + nxt_unit_request_info_t *req; + + req = nxt_java_get_response_info(req_info_ptr, 0, 0); + if (req == NULL) { + return NXT_UNIT_ERROR; + } + + resp = req->response; + + f = resp->fields; + e = f + resp->fields_count; + + add_field = 1; + + for ( ;; ) { + f = nxt_java_findHeader(f, e, name, name_len); + if (f == NULL) { + break; + } + + if (add_field && f->value_length >= (uint32_t) value_len) { + dst = nxt_unit_sptr_get(&f->value); + memcpy(dst, value, value_len); + dst[value_len] = '\0'; + f->value_length = value_len; + + add_field = 0; + f->skip = 0; + + } else { + f->skip = 1; + } + + ++f; + } + + if (!add_field) { + return NXT_UNIT_OK; + } + + req = nxt_java_get_response_info(req_info_ptr, 1, name_len + value_len + 2); + if (req == NULL) { + return NXT_UNIT_ERROR; + } + + return nxt_unit_response_add_field(req, name, name_len, value, value_len); +} + + +static void JNICALL +nxt_java_Response_setHeader(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray name, jarray value) +{ + int rc; + char *name_str, *value_str; + jsize name_len, value_len; + nxt_unit_request_info_t *req; + + name_str = (*env)->GetPrimitiveArrayCritical(env, name, NULL); + if (name_str == NULL) { + req = nxt_jlong2ptr(req_info_ptr); + nxt_unit_req_warn(req, "setHeader: failed to get name content"); + return; + } + + value_str = (*env)->GetPrimitiveArrayCritical(env, value, NULL); + if (value_str == NULL) { + (*env)->ReleasePrimitiveArrayCritical(env, name, name_str, 0); + + req = nxt_jlong2ptr(req_info_ptr); + nxt_unit_req_warn(req, "setHeader: failed to get value content"); + + return; + } + + name_len = (*env)->GetArrayLength(env, name); + value_len = (*env)->GetArrayLength(env, value); + + rc = nxt_java_response_set_header(req_info_ptr, name_str, name_len, + value_str, value_len); + if (rc != NXT_UNIT_OK) { + // throw + } + + (*env)->ReleasePrimitiveArrayCritical(env, value, value_str, 0); + (*env)->ReleasePrimitiveArrayCritical(env, name, name_str, 0); +} + + +static void JNICALL +nxt_java_Response_removeHeader(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray name) +{ + int rc; + char *name_str; + jsize name_len; + nxt_unit_request_info_t *req; + + name_len = (*env)->GetArrayLength(env, name); + + name_str = (*env)->GetPrimitiveArrayCritical(env, name, NULL); + if (name_str == NULL) { + req = nxt_jlong2ptr(req_info_ptr); + nxt_unit_req_warn(req, "setHeader: failed to get name content"); + return; + } + + rc = nxt_java_response_remove_header(req_info_ptr, name_str, name_len); + if (rc != NXT_UNIT_OK) { + // throw + } + + (*env)->ReleasePrimitiveArrayCritical(env, name, name_str, 0); +} + + +static int +nxt_java_response_remove_header(jlong req_info_ptr, + const char *name, jint name_len) +{ + nxt_unit_field_t *f, *e; + nxt_unit_response_t *resp; + nxt_unit_request_info_t *req; + + req = nxt_java_get_response_info(req_info_ptr, 0, 0); + if (req == NULL) { + return NXT_UNIT_ERROR; + } + + resp = req->response; + + f = resp->fields; + e = f + resp->fields_count; + + for ( ;; ) { + f = nxt_java_findHeader(f, e, name, name_len); + if (f == NULL) { + break; + } + + f->skip = 1; + + ++f; + } + + return NXT_UNIT_OK; +} + + +static void JNICALL +nxt_java_Response_setIntHeader(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray name, jint value) +{ + int value_len, rc; + char value_str[40]; + char *name_str; + jsize name_len; + + value_len = snprintf(value_str, sizeof(value_str), "%d", (int) value); + + name_len = (*env)->GetArrayLength(env, name); + + name_str = (*env)->GetPrimitiveArrayCritical(env, name, NULL); + if (name_str == NULL) { + nxt_unit_req_warn(nxt_jlong2ptr(req_info_ptr), + "setIntHeader: failed to get name content"); + return; + } + + rc = nxt_java_response_set_header(req_info_ptr, name_str, name_len, + value_str, value_len); + if (rc != NXT_UNIT_OK) { + // throw + } + + (*env)->ReleasePrimitiveArrayCritical(env, name, name_str, 0); +} + + +static void JNICALL +nxt_java_Response_setStatus(JNIEnv *env, jclass cls, jlong req_info_ptr, + jint sc) +{ + nxt_unit_request_info_t *req; + + req = nxt_java_get_response_info(req_info_ptr, 0, 0); + if (req == NULL) { + return; + } + + req->response->status = sc; +} + + +static jstring JNICALL +nxt_java_Response_getContentType(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_unit_field_t *f; + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + if (!nxt_unit_response_is_init(req)) { + nxt_unit_req_debug(req, "getContentType: response is not initialized"); + return NULL; + } + + if (nxt_unit_response_is_sent(req)) { + nxt_unit_req_debug(req, "getContentType: response already sent"); + return NULL; + } + + f = nxt_java_findHeader(req->response->fields, + req->response->fields + req->response->fields_count, + "Content-Type", sizeof("Content-Type") - 1); + + if (f == NULL) { + return NULL; + } + + return nxt_java_newString(env, nxt_unit_sptr_get(&f->value), + f->value_length); +} + + +static jboolean JNICALL +nxt_java_Response_isCommitted(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + if (nxt_unit_response_is_sent(req)) { + return 1; + } + + return 0; +} + + +static void JNICALL +nxt_java_Response_reset(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_unit_buf_t *buf; + nxt_unit_request_info_t *req; + nxt_java_request_data_t *data; + + req = nxt_jlong2ptr(req_info_ptr); + + if (nxt_unit_response_is_sent(req)) { + nxt_java_throw_IllegalStateException(env, "Response already sent"); + + return; + } + + data = req->data; + + if (data->buf != NULL && data->buf->free > data->buf->start) { + data->buf->free = data->buf->start; + } + + if (nxt_unit_response_is_init(req)) { + req->response->status = 200; + req->response->fields_count = 0; + + buf = req->response_buf; + + buf->free = buf->start + req->response_max_fields + * sizeof(nxt_unit_field_t); + } +} + + +static void JNICALL +nxt_java_Response_resetBuffer(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_unit_request_info_t *req; + nxt_java_request_data_t *data; + + req = nxt_jlong2ptr(req_info_ptr); + data = req->data; + + if (data->buf != NULL && data->buf->free > data->buf->start) { + data->buf->free = data->buf->start; + } +} + + +static void JNICALL +nxt_java_Response_setBufferSize(JNIEnv *env, jclass cls, jlong req_info_ptr, + jint size) +{ + nxt_unit_request_info_t *req; + nxt_java_request_data_t *data; + + req = nxt_jlong2ptr(req_info_ptr); + data = req->data; + + if (data->buf_size == (uint32_t) size) { + return; + } + + if (data->buf != NULL && data->buf->free > data->buf->start) { + nxt_java_throw_IllegalStateException(env, "Buffer is not empty"); + + return; + } + + data->buf_size = size; + + if (data->buf_size > nxt_unit_buf_max()) { + data->buf_size = nxt_unit_buf_max(); + } + + if (data->buf != NULL + && (uint32_t) (data->buf->end - data->buf->start) < data->buf_size) + { + nxt_unit_buf_free(data->buf); + + data->buf = NULL; + } +} + + +static jint JNICALL +nxt_java_Response_getBufferSize(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_unit_request_info_t *req; + nxt_java_request_data_t *data; + + req = nxt_jlong2ptr(req_info_ptr); + data = req->data; + + return data->buf_size; +} + + +static void JNICALL +nxt_java_Response_setContentLength(JNIEnv *env, jclass cls, jlong req_info_ptr, + jlong len) +{ + nxt_unit_request_info_t *req; + + req = nxt_java_get_response_info(req_info_ptr, 0, 0); + if (req == NULL) { + return; + } + + req->response->content_length = len; +} + + +static void JNICALL +nxt_java_Response_setContentType(JNIEnv *env, jclass cls, jlong req_info_ptr, + jarray type) +{ + int rc; + char *type_str; + jsize type_len; + + static const char content_type[] = "Content-Type"; + static const uint32_t content_type_len = sizeof(content_type) - 1; + + type_len = (*env)->GetArrayLength(env, type); + + type_str = (*env)->GetPrimitiveArrayCritical(env, type, NULL); + if (type_str == NULL) { + return; + } + + rc = nxt_java_response_set_header(req_info_ptr, + content_type, content_type_len, + type_str, type_len); + if (rc != NXT_UNIT_OK) { + // throw + } + + (*env)->ReleasePrimitiveArrayCritical(env, type, type_str, 0); +} + + +static void JNICALL +nxt_java_Response_removeContentType(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_java_response_remove_header(req_info_ptr, "Content-Type", + sizeof("Content-Type") - 1); +} + + +static void JNICALL +nxt_java_Response_log(JNIEnv *env, jclass cls, jlong req_info_ptr, jarray msg) +{ + char *msg_str; + jsize msg_len; + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + msg_len = (*env)->GetArrayLength(env, msg); + + msg_str = (*env)->GetPrimitiveArrayCritical(env, msg, NULL); + if (msg_str == NULL) { + nxt_unit_req_warn(req, "log: failed to get msg content"); + return; + } + + nxt_unit_req_log(req, NXT_UNIT_LOG_INFO, "%.*s", msg_len, msg_str); + + (*env)->ReleasePrimitiveArrayCritical(env, msg, msg_str, 0); +} + + +static void JNICALL +nxt_java_Response_trace(JNIEnv *env, jclass cls, jlong req_info_ptr, jarray msg) +{ +#if (NXT_DEBUG) + char *msg_str; + jsize msg_len; + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + msg_len = (*env)->GetArrayLength(env, msg); + + msg_str = (*env)->GetPrimitiveArrayCritical(env, msg, NULL); + if (msg_str == NULL) { + nxt_unit_req_warn(req, "trace: failed to get msg content"); + return; + } + + nxt_unit_req_debug(req, "%.*s", msg_len, msg_str); + + (*env)->ReleasePrimitiveArrayCritical(env, msg, msg_str, 0); +#endif +} + diff --git a/src/java/nxt_jni_Response.h b/src/java/nxt_jni_Response.h new file mode 100644 index 00000000..d10dba58 --- /dev/null +++ b/src/java/nxt_jni_Response.h @@ -0,0 +1,18 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NXT_JAVA_RESPONSE_H_INCLUDED_ +#define _NXT_JAVA_RESPONSE_H_INCLUDED_ + + +#include +#include + + +int nxt_java_initResponse(JNIEnv *env, jobject cl); + +jobject nxt_java_newResponse(JNIEnv *env, nxt_unit_request_info_t *req); + +#endif /* _NXT_JAVA_RESPONSE_H_INCLUDED_ */ diff --git a/src/java/nxt_jni_Thread.c b/src/java/nxt_jni_Thread.c new file mode 100644 index 00000000..43dd90bd --- /dev/null +++ b/src/java/nxt_jni_Thread.c @@ -0,0 +1,94 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#include +#include + +#include "nxt_jni_Thread.h" + + +static jclass nxt_java_Thread_class; +static jmethodID nxt_java_Thread_currentThread; +static jmethodID nxt_java_Thread_getContextClassLoader; +static jmethodID nxt_java_Thread_setContextClassLoader; + + +int +nxt_java_initThread(JNIEnv *env) +{ + jclass cls; + + cls = (*env)->FindClass(env, "java/lang/Thread"); + if (cls == NULL) { + nxt_unit_warn(NULL, "java.lang.Thread not found"); + return NXT_UNIT_ERROR; + } + + nxt_java_Thread_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + cls = nxt_java_Thread_class; + + nxt_java_Thread_currentThread = (*env)->GetStaticMethodID(env, cls, + "currentThread", "()Ljava/lang/Thread;"); + if (nxt_java_Thread_currentThread == NULL) { + nxt_unit_warn(NULL, "java.lang.Thread.currentThread() not found"); + goto failed; + } + + nxt_java_Thread_getContextClassLoader = (*env)->GetMethodID(env, cls, + "getContextClassLoader", "()Ljava/lang/ClassLoader;"); + if (nxt_java_Thread_getContextClassLoader == NULL) { + nxt_unit_warn(NULL, "java.lang.Thread.getContextClassLoader() " + "not found"); + goto failed; + } + + nxt_java_Thread_setContextClassLoader = (*env)->GetMethodID(env, cls, + "setContextClassLoader", "(Ljava/lang/ClassLoader;)V"); + if (nxt_java_Thread_setContextClassLoader == NULL) { + nxt_unit_warn(NULL, "java.lang.Thread.setContextClassLoader() " + "not found"); + goto failed; + } + + return NXT_UNIT_OK; + +failed: + + (*env)->DeleteGlobalRef(env, cls); + return NXT_UNIT_ERROR; +} + +void +nxt_java_setContextClassLoader(JNIEnv *env, jobject cl) +{ + jobject thread; + + thread = (*env)->CallStaticObjectMethod(env, nxt_java_Thread_class, + nxt_java_Thread_currentThread); + + if (thread == NULL) { + return; + } + + (*env)->CallVoidMethod(env, thread, nxt_java_Thread_setContextClassLoader, + cl); +} + +jobject +nxt_java_getContextClassLoader(JNIEnv *env) +{ + jobject thread; + + thread = (*env)->CallStaticObjectMethod(env, nxt_java_Thread_class, + nxt_java_Thread_currentThread); + + if (thread == NULL) { + return NULL; + } + + return (*env)->CallObjectMethod(env, thread, + nxt_java_Thread_getContextClassLoader); +} diff --git a/src/java/nxt_jni_Thread.h b/src/java/nxt_jni_Thread.h new file mode 100644 index 00000000..4d0b650e --- /dev/null +++ b/src/java/nxt_jni_Thread.h @@ -0,0 +1,20 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NXT_JAVA_THREAD_H_INCLUDED_ +#define _NXT_JAVA_THREAD_H_INCLUDED_ + + +#include + + +int nxt_java_initThread(JNIEnv *env); + +void nxt_java_setContextClassLoader(JNIEnv *env, jobject cl); + +jobject nxt_java_getContextClassLoader(JNIEnv *env); + +#endif /* _NXT_JAVA_THREAD_H_INCLUDED_ */ + diff --git a/src/java/nxt_jni_URLClassLoader.c b/src/java/nxt_jni_URLClassLoader.c new file mode 100644 index 00000000..bf3ab0c3 --- /dev/null +++ b/src/java/nxt_jni_URLClassLoader.c @@ -0,0 +1,187 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#include +#include + +#include "nxt_jni_URLClassLoader.h" + + +static jclass nxt_java_URLClassLoader_class; +static jmethodID nxt_java_URLClassLoader_ctor; +static jmethodID nxt_java_URLClassLoader_parent_ctor; +static jmethodID nxt_java_URLClassLoader_loadClass; +static jmethodID nxt_java_URLClassLoader_addURL; + +static jclass nxt_java_URL_class; +static jmethodID nxt_java_URL_ctor; + + +int +nxt_java_initURLClassLoader(JNIEnv *env) +{ + jclass cls; + + cls = (*env)->FindClass(env, "java/net/URLClassLoader"); + if (cls == NULL) { + nxt_unit_warn(NULL, "java.net.URLClassLoader not found"); + return NXT_UNIT_ERROR; + } + + nxt_java_URLClassLoader_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + cls = nxt_java_URLClassLoader_class; + + nxt_java_URLClassLoader_ctor = (*env)->GetMethodID(env, cls, + "", "([Ljava/net/URL;)V"); + if (nxt_java_URLClassLoader_ctor == NULL) { + nxt_unit_warn(NULL, "java.net.URLClassLoader constructor not found"); + goto failed; + } + + nxt_java_URLClassLoader_parent_ctor = (*env)->GetMethodID(env, cls, + "", "([Ljava/net/URL;Ljava/lang/ClassLoader;)V"); + if (nxt_java_URLClassLoader_ctor == NULL) { + nxt_unit_warn(NULL, "java.net.URLClassLoader constructor not found"); + goto failed; + } + + nxt_java_URLClassLoader_loadClass = (*env)->GetMethodID(env, cls, + "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"); + if (nxt_java_URLClassLoader_loadClass == NULL) { + nxt_unit_warn(NULL, "java.net.URLClassLoader.loadClass not found"); + goto failed; + } + + nxt_java_URLClassLoader_addURL = (*env)->GetMethodID(env, cls, + "addURL", "(Ljava/net/URL;)V"); + if (nxt_java_URLClassLoader_addURL == NULL) { + nxt_unit_warn(NULL, "java.net.URLClassLoader.addURL not found"); + goto failed; + } + + cls = (*env)->FindClass(env, "java/net/URL"); + if (cls == NULL) { + nxt_unit_warn(NULL, "java.net.URL not found"); + return NXT_UNIT_ERROR; + } + + nxt_java_URL_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + cls = nxt_java_URL_class; + + nxt_java_URL_ctor = (*env)->GetMethodID(env, cls, + "", "(Ljava/lang/String;)V"); + if (nxt_java_URL_ctor == NULL) { + nxt_unit_warn(NULL, "java.net.URL constructor not found"); + goto failed; + } + + return NXT_UNIT_OK; + +failed: + + (*env)->DeleteGlobalRef(env, cls); + return NXT_UNIT_ERROR; +} + + +jobject +nxt_java_newURLClassLoader(JNIEnv *env, int url_count, char **urls) +{ + jobjectArray jurls; + + jurls = nxt_java_newURLs(env, url_count, urls); + if (jurls == NULL) { + return NULL; + } + + return (*env)->NewObject(env, nxt_java_URLClassLoader_class, + nxt_java_URLClassLoader_ctor, jurls); +} + + +jobject +nxt_java_newURLClassLoader_parent(JNIEnv *env, int url_count, char **urls, + jobject parent) +{ + jobjectArray jurls; + + jurls = nxt_java_newURLs(env, url_count, urls); + if (jurls == NULL) { + return NULL; + } + + return (*env)->NewObject(env, nxt_java_URLClassLoader_class, + nxt_java_URLClassLoader_parent_ctor, jurls, + parent); +} + + +jobjectArray +nxt_java_newURLs(JNIEnv *env, int url_count, char **urls) +{ + int i; + jstring surl; + jobject jurl; + jobjectArray jurls; + + jurls = (*env)->NewObjectArray(env, url_count, nxt_java_URL_class, NULL); + if (jurls == NULL) { + return NULL; + } + + for (i = 0; i < url_count; i++) { + surl = (*env)->NewStringUTF(env, urls[i]); + if (surl == NULL) { + return NULL; + } + + jurl = (*env)->NewObject(env, nxt_java_URL_class, nxt_java_URL_ctor, + surl); + if (jurl == NULL) { + return NULL; + } + + (*env)->SetObjectArrayElement(env, jurls, i, jurl); + } + + return jurls; +} + + +jclass +nxt_java_loadClass(JNIEnv *env, jobject cl, const char *name) +{ + jstring jname; + + jname = (*env)->NewStringUTF(env, name); + if (jname == NULL) { + return NULL; + } + + return (*env)->CallObjectMethod(env, cl, nxt_java_URLClassLoader_loadClass, + jname); +} + + +void +nxt_java_addURL(JNIEnv *env, jobject cl, const char *url) +{ + jstring surl; + jobject jurl; + + surl = (*env)->NewStringUTF(env, url); + if (surl == NULL) { + return; + } + + jurl = (*env)->NewObject(env, nxt_java_URL_class, nxt_java_URL_ctor, surl); + if (jurl == NULL) { + return; + } + + (*env)->CallVoidMethod(env, cl, nxt_java_URLClassLoader_addURL, jurl); +} diff --git a/src/java/nxt_jni_URLClassLoader.h b/src/java/nxt_jni_URLClassLoader.h new file mode 100644 index 00000000..4cf2c0ec --- /dev/null +++ b/src/java/nxt_jni_URLClassLoader.h @@ -0,0 +1,27 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NXT_JAVA_URLCLASSLOADER_H_INCLUDED_ +#define _NXT_JAVA_URLCLASSLOADER_H_INCLUDED_ + + +#include + + +int nxt_java_initURLClassLoader(JNIEnv *env); + +jobject nxt_java_newURLClassLoader(JNIEnv *env, int url_count, char **urls); + +jobject nxt_java_newURLClassLoader_parent(JNIEnv *env, int url_count, + char **urls, jobject parent); + +jobjectArray nxt_java_newURLs(JNIEnv *env, int url_count, char **urls); + +jclass nxt_java_loadClass(JNIEnv *env, jobject cl, const char *name); + +void nxt_java_addURL(JNIEnv *env, jobject cl, const char *url); + +#endif /* _NXT_JAVA_URLCLASSLOADER_H_INCLUDED_ */ + diff --git a/src/nxt_application.c b/src/nxt_application.c index acdebe04..a2827b75 100644 --- a/src/nxt_application.c +++ b/src/nxt_application.c @@ -331,6 +331,17 @@ nxt_app_start(nxt_task_t *task, void *data) nxt_app = nxt_app_module_load(task, lang->file); } + if (nxt_app->pre_init != NULL) { + ret = nxt_app->pre_init(task, data); + + if (nxt_slow_path(ret != NXT_OK)) { + nxt_debug(task, "application pre_init failed"); + + } else { + nxt_debug(task, "application pre_init done"); + } + } + if (app_conf->working_directory != NULL && app_conf->working_directory[0] != 0) { @@ -521,6 +532,9 @@ nxt_app_parse_type(u_char *p, size_t length) } else if (nxt_str_eq(&str, "ruby", 4)) { return NXT_APP_RUBY; + + } else if (nxt_str_eq(&str, "java", 4)) { + return NXT_APP_JAVA; } return NXT_APP_UNKNOWN; diff --git a/src/nxt_application.h b/src/nxt_application.h index fe8113c6..781f05e0 100644 --- a/src/nxt_application.h +++ b/src/nxt_application.h @@ -20,6 +20,7 @@ typedef enum { NXT_APP_PHP, NXT_APP_PERL, NXT_APP_RUBY, + NXT_APP_JAVA, NXT_APP_UNKNOWN, } nxt_app_type_t; @@ -70,6 +71,14 @@ typedef struct { } nxt_ruby_app_conf_t; +typedef struct { + nxt_conf_value_t *classpath; + char *webapp; + nxt_conf_value_t *options; + char *unit_jars; +} nxt_java_app_conf_t; + + struct nxt_common_app_conf_s { nxt_str_t name; nxt_str_t type; @@ -85,6 +94,7 @@ struct nxt_common_app_conf_s { nxt_php_app_conf_t php; nxt_perl_app_conf_t perl; nxt_ruby_app_conf_t ruby; + nxt_java_app_conf_t java; } u; }; @@ -152,6 +162,8 @@ struct nxt_app_module_s { nxt_str_t type; const char *version; + nxt_int_t (*pre_init)(nxt_task_t *task, + nxt_common_app_conf_t *conf); nxt_int_t (*init)(nxt_task_t *task, nxt_common_app_conf_t *conf); }; diff --git a/src/nxt_conf.h b/src/nxt_conf.h index 052334fe..20ff3b1e 100644 --- a/src/nxt_conf.h +++ b/src/nxt_conf.h @@ -125,7 +125,7 @@ void nxt_conf_set_element(nxt_conf_value_t *array, nxt_uint_t index, nxt_conf_value_t *value); nxt_int_t nxt_conf_set_element_string_dup(nxt_conf_value_t *array, nxt_mp_t *mp, nxt_uint_t index, nxt_str_t *value); -nxt_uint_t nxt_conf_array_elements_count(nxt_conf_value_t *value); +NXT_EXPORT nxt_uint_t nxt_conf_array_elements_count(nxt_conf_value_t *value); void nxt_conf_array_qsort(nxt_conf_value_t *value, int (*compare)(const void *, const void *)); diff --git a/src/nxt_conf_validation.c b/src/nxt_conf_validation.c index 655097fd..5653b9eb 100644 --- a/src/nxt_conf_validation.c +++ b/src/nxt_conf_validation.c @@ -88,6 +88,10 @@ static nxt_int_t nxt_conf_vldt_argument(nxt_conf_validation_t *vldt, nxt_conf_value_t *value); static nxt_int_t nxt_conf_vldt_php_option(nxt_conf_validation_t *vldt, nxt_str_t *name, nxt_conf_value_t *value); +static nxt_int_t nxt_conf_vldt_java_classpath(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value); +static nxt_int_t nxt_conf_vldt_java_option(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value); static nxt_conf_vldt_object_t nxt_conf_vldt_http_members[] = { @@ -423,6 +427,31 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_ruby_members[] = { }; +static nxt_conf_vldt_object_t nxt_conf_vldt_java_members[] = { + { nxt_string("classpath"), + NXT_CONF_VLDT_ARRAY, + &nxt_conf_vldt_array_iterator, + (void *) &nxt_conf_vldt_java_classpath}, + + { nxt_string("webapp"), + NXT_CONF_VLDT_STRING, + NULL, + NULL }, + + { nxt_string("options"), + NXT_CONF_VLDT_ARRAY, + &nxt_conf_vldt_array_iterator, + (void *) &nxt_conf_vldt_java_option}, + + { nxt_string("unit_jars"), + NXT_CONF_VLDT_STRING, + NULL, + NULL }, + + NXT_CONF_VLDT_NEXT(&nxt_conf_vldt_common_members) +}; + + nxt_int_t nxt_conf_validate(nxt_conf_validation_t *vldt) { @@ -818,6 +847,7 @@ nxt_conf_vldt_app(nxt_conf_validation_t *vldt, nxt_str_t *name, nxt_conf_vldt_php_members, nxt_conf_vldt_perl_members, nxt_conf_vldt_ruby_members, + nxt_conf_vldt_java_members, }; ret = nxt_conf_vldt_type(vldt, name, value, NXT_CONF_VLDT_OBJECT); @@ -1216,3 +1246,44 @@ nxt_conf_vldt_php_option(nxt_conf_validation_t *vldt, nxt_str_t *name, return NXT_OK; } + + +static nxt_int_t +nxt_conf_vldt_java_classpath(nxt_conf_validation_t *vldt, nxt_conf_value_t *value) +{ + nxt_str_t str; + + if (nxt_conf_type(value) != NXT_CONF_STRING) { + return nxt_conf_vldt_error(vldt, "The \"classpath\" array " + "must contain only string values."); + } + + nxt_conf_get_string(value, &str); + + if (nxt_memchr(str.start, '\0', str.length) != NULL) { + return nxt_conf_vldt_error(vldt, "The \"classpath\" array must not " + "contain strings with null character."); + } + + return NXT_OK; +} + +static nxt_int_t +nxt_conf_vldt_java_option(nxt_conf_validation_t *vldt, nxt_conf_value_t *value) +{ + nxt_str_t str; + + if (nxt_conf_type(value) != NXT_CONF_STRING) { + return nxt_conf_vldt_error(vldt, "The \"options\" array " + "must contain only string values."); + } + + nxt_conf_get_string(value, &str); + + if (nxt_memchr(str.start, '\0', str.length) != NULL) { + return nxt_conf_vldt_error(vldt, "The \"options\" array must not " + "contain strings with null character."); + } + + return NXT_OK; +} diff --git a/src/nxt_external.c b/src/nxt_external.c index c7aacffc..89fe08c8 100644 --- a/src/nxt_external.c +++ b/src/nxt_external.c @@ -18,6 +18,7 @@ nxt_app_module_t nxt_external_module = { NULL, nxt_string("external"), "*", + NULL, nxt_external_init, }; diff --git a/src/nxt_java.c b/src/nxt_java.c new file mode 100644 index 00000000..bf4931ab --- /dev/null +++ b/src/nxt_java.c @@ -0,0 +1,462 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "java/nxt_jni_Thread.h" +#include "java/nxt_jni_Context.h" +#include "java/nxt_jni_Request.h" +#include "java/nxt_jni_Response.h" +#include "java/nxt_jni_InputStream.h" +#include "java/nxt_jni_OutputStream.h" +#include "java/nxt_jni_URLClassLoader.h" + +#include "nxt_jars.h" + +static nxt_int_t nxt_java_pre_init(nxt_task_t *task, + nxt_common_app_conf_t *conf); +static nxt_int_t nxt_java_init(nxt_task_t *task, nxt_common_app_conf_t *conf); +static void nxt_java_request_handler(nxt_unit_request_info_t *req); + +static uint32_t compat[] = { + NXT_VERNUM, NXT_DEBUG, +}; + +char *nxt_java_modules; + + +#define NXT_STRING(x) _NXT_STRING(x) +#define _NXT_STRING(x) #x + +NXT_EXPORT nxt_app_module_t nxt_app_module = { + sizeof(compat), + compat, + nxt_string("java"), + NXT_STRING(NXT_JAVA_VERSION), + nxt_java_pre_init, + nxt_java_init, +}; + +typedef struct { + JNIEnv *env; + jobject ctx; +} nxt_java_data_t; + + +static nxt_int_t +nxt_java_pre_init(nxt_task_t *task, nxt_common_app_conf_t *conf) +{ + const char *unit_jars; + + unit_jars = conf->u.java.unit_jars; + if (unit_jars == NULL) { + unit_jars = NXT_JARS; + } + + nxt_java_modules = realpath(unit_jars, NULL); + if (nxt_java_modules == NULL) { + nxt_alert(task, "realpath(%s) failed: %E", NXT_JARS, nxt_errno); + return NXT_ERROR; + } + + return NXT_OK; +} + + +static char ** +nxt_java_module_jars(const char *jars[], int jar_count) +{ + char **res, *jurl; + nxt_int_t modules_len, jlen, i; + const char **jar; + + res = nxt_malloc(jar_count * sizeof(char*)); + if (res == NULL) { + return NULL; + } + + modules_len = nxt_strlen(nxt_java_modules); + + for (i = 0, jar = jars; *jar != NULL; jar++) { + jlen = nxt_length("file:") + modules_len + nxt_length("/") + + nxt_strlen(*jar) + 1; + jurl = nxt_malloc(jlen); + if (jurl == NULL) { + return NULL; + } + + res[i++] = jurl; + + jurl = nxt_cpymem(jurl, "file:", nxt_length("file:")); + jurl = nxt_cpymem(jurl, nxt_java_modules, modules_len); + *jurl++ = '/'; + jurl = nxt_cpymem(jurl, *jar, nxt_strlen(*jar)); + *jurl++ = '\0'; + } + + return res; +} + + +static nxt_int_t +nxt_java_init(nxt_task_t *task, nxt_common_app_conf_t *conf) +{ + jint rc; + char *opt, *real_path; + char **classpath_arr, **unit_jars, **system_jars; + JavaVM *jvm; + JNIEnv *env; + jobject cl, classpath; + nxt_str_t str; + nxt_int_t opt_len, real_path_len; + nxt_uint_t i, unit_jars_count, classpath_count, system_jars_count; + JavaVMOption *jvm_opt; + JavaVMInitArgs jvm_args; + nxt_unit_ctx_t *ctx; + nxt_unit_init_t java_init; + nxt_java_data_t data; + nxt_conf_value_t *value; + nxt_java_app_conf_t *c; + + //setenv("ASAN_OPTIONS", "handle_segv=0", 1); + + jvm_args.version = JNI_VERSION_1_6; + jvm_args.nOptions = 0; + jvm_args.ignoreUnrecognized = 0; + + c = &conf->u.java; + + if (c->options != NULL) { + jvm_args.nOptions += nxt_conf_array_elements_count(c->options); + } + + jvm_opt = nxt_malloc(jvm_args.nOptions * sizeof(JavaVMOption)); + if (jvm_opt == NULL) { + nxt_alert(task, "failed to allocate jvm_opt"); + return NXT_ERROR; + } + + jvm_args.options = jvm_opt; + + unit_jars_count = nxt_nitems(nxt_java_unit_jars) - 1; + + unit_jars = nxt_java_module_jars(nxt_java_unit_jars, unit_jars_count); + if (unit_jars == NULL) { + nxt_alert(task, "failed to allocate buffer for unit_jars array"); + + return NXT_ERROR; + } + + system_jars_count = nxt_nitems(nxt_java_system_jars) - 1; + + system_jars = nxt_java_module_jars(nxt_java_system_jars, system_jars_count); + if (system_jars == NULL) { + nxt_alert(task, "failed to allocate buffer for system_jars array"); + + return NXT_ERROR; + } + + if (c->options != NULL) { + + for (i = 0; /* void */ ; i++) { + value = nxt_conf_get_array_element(c->options, i); + if (value == NULL) { + break; + } + + nxt_conf_get_string(value, &str); + + opt = nxt_malloc(str.length + 1); + if (opt == NULL) { + nxt_alert(task, "failed to allocate jvm_opt"); + return NXT_ERROR; + } + + memcpy(opt, str.start, str.length); + opt[str.length] = '\0'; + + jvm_opt[i].optionString = opt; + } + } + + if (c->classpath != NULL) { + classpath_count = nxt_conf_array_elements_count(c->classpath); + classpath_arr = nxt_malloc(classpath_count * sizeof(char *)); + + for (i = 0; /* void */ ; i++) { + value = nxt_conf_get_array_element(c->classpath, i); + if (value == NULL) { + break; + } + + nxt_conf_get_string(value, &str); + + opt_len = str.length + 1; + + char *sc = memchr(str.start, ':', str.length); + if (sc == NULL && str.start[0] == '/') { + opt_len += nxt_length("file:"); + } + + opt = nxt_malloc(opt_len); + if (opt == NULL) { + nxt_alert(task, "failed to allocate classpath"); + return NXT_ERROR; + } + + if (sc == NULL && str.start[0] != '/') { + nxt_memcpy(opt, str.start, str.length); + opt[str.length] = '\0'; + + real_path = realpath(opt, NULL); + if (real_path == NULL) { + nxt_alert(task, "realpath(%s) failed: %E", opt, nxt_errno); + return NXT_ERROR; + } + + real_path_len = nxt_strlen(real_path); + + free(opt); + + opt_len = nxt_length("file:") + real_path_len + 1; + + opt = nxt_malloc(opt_len); + if (opt == NULL) { + nxt_alert(task, "failed to allocate classpath"); + return NXT_ERROR; + } + + } else { + real_path = (char *) str.start; /* I love this cast! */ + real_path_len = str.length; + } + + classpath_arr[i] = opt; + + if (sc == NULL) { + opt = nxt_cpymem(opt, "file:", nxt_length("file:")); + } + + opt = nxt_cpymem(opt, real_path, real_path_len); + *opt = '\0'; + } + + } else { + classpath_count = 0; + classpath_arr = NULL; + } + + rc = JNI_CreateJavaVM(&jvm, (void **) &env, &jvm_args); + if (rc != JNI_OK) { + nxt_alert(task, "failed to create Java VM: %d", (int) rc); + return NXT_ERROR; + } + + rc = nxt_java_initThread(env); + if (rc != NXT_UNIT_OK) { + nxt_alert(task, "nxt_java_initThread() failed"); + goto env_failed; + } + + rc = nxt_java_initURLClassLoader(env); + if (rc != NXT_UNIT_OK) { + nxt_alert(task, "nxt_java_initURLClassLoader() failed"); + goto env_failed; + } + + cl = nxt_java_newURLClassLoader(env, system_jars_count, system_jars); + if (cl == NULL) { + nxt_alert(task, "nxt_java_newURLClassLoader failed"); + goto env_failed; + } + + nxt_java_setContextClassLoader(env, cl); + + cl = nxt_java_newURLClassLoader_parent(env, unit_jars_count, unit_jars, cl); + if (cl == NULL) { + nxt_alert(task, "nxt_java_newURLClassLoader_parent failed"); + goto env_failed; + } + + nxt_java_setContextClassLoader(env, cl); + + rc = nxt_java_initContext(env, cl); + if (rc != NXT_UNIT_OK) { + nxt_alert(task, "nxt_java_initContext() failed"); + goto env_failed; + } + + rc = nxt_java_initRequest(env, cl); + if (rc != NXT_UNIT_OK) { + nxt_alert(task, "nxt_java_initRequest() failed"); + goto env_failed; + } + + rc = nxt_java_initResponse(env, cl); + if (rc != NXT_UNIT_OK) { + nxt_alert(task, "nxt_java_initResponse() failed"); + goto env_failed; + } + + rc = nxt_java_initInputStream(env, cl); + if (rc != NXT_UNIT_OK) { + nxt_alert(task, "nxt_java_initInputStream() failed"); + goto env_failed; + } + + rc = nxt_java_initOutputStream(env, cl); + if (rc != NXT_UNIT_OK) { + nxt_alert(task, "nxt_java_initOutputStream() failed"); + goto env_failed; + } + + nxt_java_jni_init(env); + if (rc != NXT_UNIT_OK) { + nxt_alert(task, "nxt_java_jni_init() failed"); + goto env_failed; + } + + classpath = nxt_java_newURLs(env, classpath_count, classpath_arr); + if (classpath == NULL) { + nxt_alert(task, "nxt_java_newURLs failed"); + goto env_failed; + } + + data.env = env; + data.ctx = nxt_java_startContext(env, c->webapp, classpath); + + if ((*env)->ExceptionCheck(env)) { + nxt_alert(task, "Unhandled exception in application start"); + (*env)->ExceptionDescribe(env); + return NXT_ERROR; + } + + nxt_unit_default_init(task, &java_init); + + java_init.callbacks.request_handler = nxt_java_request_handler; + java_init.request_data_size = sizeof(nxt_java_request_data_t); + java_init.data = &data; + + ctx = nxt_unit_init(&java_init); + if (nxt_slow_path(ctx == NULL)) { + nxt_alert(task, "nxt_unit_init() failed"); + return NXT_ERROR; + } + + rc = nxt_unit_run(ctx); + if (nxt_slow_path(rc != NXT_UNIT_OK)) { + /* TODO report error */ + } + + nxt_unit_done(ctx); + + nxt_java_stopContext(env, data.ctx); + + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + } + + (*jvm)->DestroyJavaVM(jvm); + + exit(0); + + return NXT_OK; + +env_failed: + + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + } + + return NXT_ERROR; +} + + +static void +nxt_java_request_handler(nxt_unit_request_info_t *req) +{ + JNIEnv *env; + jobject jreq, jresp; + nxt_java_data_t *java_data; + nxt_java_request_data_t *data; + + java_data = req->unit->data; + env = java_data->env; + data = req->data; + + jreq = nxt_java_newRequest(env, java_data->ctx, req); + if (jreq == NULL) { + nxt_unit_req_alert(req, "failed to create Request instance"); + + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + } + + nxt_unit_request_done(req, NXT_UNIT_ERROR); + return; + } + + jresp = nxt_java_newResponse(env, req); + if (jresp == NULL) { + nxt_unit_req_alert(req, "failed to create Response instance"); + + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + } + + (*env)->DeleteLocalRef(env, jreq); + + nxt_unit_request_done(req, NXT_UNIT_ERROR); + return; + } + + data->header_size = 10 * 1024; + data->buf_size = 32 * 1024; /* from Jetty */ + data->jreq = jreq; + data->jresp = jresp; + data->buf = NULL; + + nxt_unit_request_group_dup_fields(req); + + nxt_java_service(env, java_data->ctx, jreq, jresp); + + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + } + + if (!nxt_unit_response_is_init(req)) { + nxt_unit_response_init(req, 200, 0, 0); + } + + if (!nxt_unit_response_is_sent(req)) { + nxt_unit_response_send(req); + } + + if (data->buf != NULL) { + nxt_unit_buf_send(data->buf); + + data->buf = NULL; + } + + (*env)->DeleteLocalRef(env, jresp); + (*env)->DeleteLocalRef(env, jreq); + + nxt_unit_request_done(req, NXT_UNIT_OK); +} + diff --git a/src/nxt_main_process.c b/src/nxt_main_process.c index a33117e2..f756bff7 100644 --- a/src/nxt_main_process.c +++ b/src/nxt_main_process.c @@ -219,12 +219,38 @@ static nxt_conf_map_t nxt_ruby_app_conf[] = { }; +static nxt_conf_map_t nxt_java_app_conf[] = { + { + nxt_string("classpath"), + NXT_CONF_MAP_PTR, + offsetof(nxt_common_app_conf_t, u.java.classpath), + }, + { + nxt_string("webapp"), + NXT_CONF_MAP_CSTRZ, + offsetof(nxt_common_app_conf_t, u.java.webapp), + }, + { + nxt_string("options"), + NXT_CONF_MAP_PTR, + offsetof(nxt_common_app_conf_t, u.java.options), + }, + { + nxt_string("unit_jars"), + NXT_CONF_MAP_CSTRZ, + offsetof(nxt_common_app_conf_t, u.java.unit_jars), + }, + +}; + + static nxt_conf_app_map_t nxt_app_maps[] = { { nxt_nitems(nxt_external_app_conf), nxt_external_app_conf }, { nxt_nitems(nxt_python_app_conf), nxt_python_app_conf }, { nxt_nitems(nxt_php_app_conf), nxt_php_app_conf }, { nxt_nitems(nxt_perl_app_conf), nxt_perl_app_conf }, { nxt_nitems(nxt_ruby_app_conf), nxt_ruby_app_conf }, + { nxt_nitems(nxt_java_app_conf), nxt_java_app_conf }, }; diff --git a/src/nxt_php_sapi.c b/src/nxt_php_sapi.c index 559a3e37..e36dd20a 100644 --- a/src/nxt_php_sapi.c +++ b/src/nxt_php_sapi.c @@ -164,6 +164,7 @@ NXT_EXPORT nxt_app_module_t nxt_app_module = { compat, nxt_string("php"), PHP_VERSION, + NULL, nxt_php_init, }; diff --git a/src/nxt_python_wsgi.c b/src/nxt_python_wsgi.c index 871c8e91..80ffb0f7 100644 --- a/src/nxt_python_wsgi.c +++ b/src/nxt_python_wsgi.c @@ -105,6 +105,7 @@ NXT_EXPORT nxt_app_module_t nxt_app_module = { compat, nxt_string("python"), PY_VERSION, + NULL, nxt_python_init, }; diff --git a/src/nxt_router.c b/src/nxt_router.c index 51c84df9..e46e8f82 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -293,6 +293,7 @@ static const nxt_str_t *nxt_app_msg_prefix[] = { &http_prefix, &http_prefix, &http_prefix, + &empty_prefix, }; diff --git a/src/perl/nxt_perl_psgi.c b/src/perl/nxt_perl_psgi.c index 275ed913..efcff7e3 100644 --- a/src/perl/nxt_perl_psgi.c +++ b/src/perl/nxt_perl_psgi.c @@ -113,6 +113,7 @@ NXT_EXPORT nxt_app_module_t nxt_app_module = { nxt_perl_psgi_compat, nxt_string("perl"), PERL_VERSION_STRING, + NULL, nxt_perl_psgi_init, }; diff --git a/src/ruby/nxt_ruby.c b/src/ruby/nxt_ruby.c index d099a338..17831175 100644 --- a/src/ruby/nxt_ruby.c +++ b/src/ruby/nxt_ruby.c @@ -76,6 +76,7 @@ NXT_EXPORT nxt_app_module_t nxt_app_module = { compat, nxt_string("ruby"), ruby_version, + NULL, nxt_ruby_init, }; -- cgit From afda14d1f23cc49eee5939c25278eec3501d8631 Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Thu, 28 Feb 2019 18:03:21 +0300 Subject: Preserving message 'share' field when pushing to queue. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As far as I understand, this field is important to control the number of buffers send in a single write attempt. Furthermore, having uninitialized field is always bad. This closes #204 issue on GitHub. Thanks to 洪志道 (Hong Zhi Dao). --- src/nxt_port_socket.c | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/nxt_port_socket.c b/src/nxt_port_socket.c index aed3a292..01fe2dab 100644 --- a/src/nxt_port_socket.c +++ b/src/nxt_port_socket.c @@ -179,6 +179,7 @@ nxt_port_msg_create(nxt_task_t *task, nxt_port_send_msg_t *m) msg->link.prev = NULL; msg->buf = m->buf; + msg->share = m->share; msg->fd = m->fd; msg->close_fd = m->close_fd; msg->port_msg = m->port_msg; -- cgit From 834e8ca576868c5ecd104bd5ebec9968c7f80a0b Mon Sep 17 00:00:00 2001 From: Igor Sysoev Date: Thu, 28 Feb 2019 18:04:11 +0300 Subject: Fixed timer and event race condition. When idle timeout occurs at the same time as a request comes in, the timer handler closes connection while the read event triggers request processing, and this eventually leads to segmentation fault. --- src/nxt_conn.h | 3 ++- src/nxt_conn_read.c | 7 ++++--- src/nxt_conn_write.c | 5 +++-- src/nxt_event_conn_job_sendfile.c | 6 ++++-- src/nxt_h1proto.c | 4 ++++ 5 files changed, 17 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/nxt_conn.h b/src/nxt_conn.h index d8b48694..7284808b 100644 --- a/src/nxt_conn.h +++ b/src/nxt_conn.h @@ -157,7 +157,8 @@ struct nxt_conn_s { nxt_sockaddr_t *local; const char *action; - uint8_t blocked; /* 1 bit */ + uint8_t block_read; /* 1 bit */ + uint8_t block_write; /* 1 bit */ uint8_t delayed; /* 1 bit */ #define NXT_CONN_SENDFILE_OFF 0 diff --git a/src/nxt_conn_read.c b/src/nxt_conn_read.c index 8228326b..83969b31 100644 --- a/src/nxt_conn_read.c +++ b/src/nxt_conn_read.c @@ -45,10 +45,11 @@ nxt_conn_io_read(nxt_task_t *task, void *obj, void *data) c = obj; - nxt_debug(task, "conn read fd:%d rdy:%d cl:%d", - c->socket.fd, c->socket.read_ready, c->socket.closed); + nxt_debug(task, "conn read fd:%d rdy:%d cl:%d er:%d bl:%d", + c->socket.fd, c->socket.read_ready, c->socket.closed, + c->socket.error, c->block_read); - if (c->socket.error != 0) { + if (c->socket.error != 0 || c->block_read) { return; } diff --git a/src/nxt_conn_write.c b/src/nxt_conn_write.c index 80d6f5cf..298d8f75 100644 --- a/src/nxt_conn_write.c +++ b/src/nxt_conn_write.c @@ -22,9 +22,10 @@ nxt_conn_io_write(nxt_task_t *task, void *obj, void *data) c = obj; - nxt_debug(task, "conn write fd:%d", c->socket.fd); + nxt_debug(task, "conn write fd:%d er:%d bl:%d", + c->socket.fd, c->socket.error, c->block_write); - if (c->socket.error != 0) { + if (c->socket.error != 0 || c->block_write) { goto error; } diff --git a/src/nxt_event_conn_job_sendfile.c b/src/nxt_event_conn_job_sendfile.c index 2ca6e421..0f6f9353 100644 --- a/src/nxt_event_conn_job_sendfile.c +++ b/src/nxt_event_conn_job_sendfile.c @@ -80,7 +80,8 @@ nxt_event_conn_job_sendfile_start(nxt_task_t *task, void *obj, void *data) c->write = NULL; jbs->ready_handler = nxt_event_conn_job_sendfile_return; - c->blocked = 1; + c->block_read = 1; + c->block_write = 1; nxt_job_start(task, &jbs->job, nxt_event_conn_job_sendfile_handler); return; @@ -170,7 +171,8 @@ nxt_event_conn_job_sendfile_return(nxt_task_t *task, void *obj, void *data) jbs = obj; c = data; - c->blocked = 0; + c->block_read = 0; + c->block_write = 0; sent = jbs->sent; c->sent += sent; diff --git a/src/nxt_h1proto.c b/src/nxt_h1proto.c index b5b5d6e6..07e3c7bc 100644 --- a/src/nxt_h1proto.c +++ b/src/nxt_h1proto.c @@ -1220,6 +1220,7 @@ nxt_h1p_conn_request_timeout(nxt_task_t *task, void *obj, void *data) nxt_debug(task, "h1p conn request timeout"); c = nxt_read_timer_conn(timer); + c->block_read = 1; /* * Disable SO_LINGER off during socket closing * to send "408 Request Timeout" error response. @@ -1250,6 +1251,7 @@ nxt_h1p_conn_request_send_timeout(nxt_task_t *task, void *obj, void *data) nxt_debug(task, "h1p conn request send timeout"); c = nxt_write_timer_conn(timer); + c->block_write = 1; h1p = c->socket.data; nxt_h1p_request_error(task, h1p, h1p->request); @@ -1464,6 +1466,7 @@ nxt_h1p_idle_timeout(nxt_task_t *task, void *obj, void *data) nxt_debug(task, "h1p idle timeout"); c = nxt_read_timer_conn(timer); + c->block_read = 1; nxt_h1p_idle_response(task, c); } @@ -1559,6 +1562,7 @@ nxt_h1p_idle_response_timeout(nxt_task_t *task, void *obj, void *data) nxt_debug(task, "h1p idle timeout response timeout"); c = nxt_read_timer_conn(timer); + c->block_write = 1; nxt_h1p_shutdown(task, c); } -- cgit From a5dd0f8aa9b81921ff28c486a39fd46607dbdbd9 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Thu, 28 Feb 2019 20:20:41 +0300 Subject: Made QUERY_STRING mandatory. According to CGI/1.1 RFC 3875: The server MUST set this variable; if the Script-URI does not include a query component, the QUERY_STRING MUST be defined as an empty string (""). Python's PEP 333(3) allows omitting it in WSGI interface; PHP docs force no requirements; PSGI and Rack specifications require it even if empty. When nginx proxies requests over FastCGI, it always provides QUERY_STRING. and some PHP apps have been observed to fail if it is missing (see issue #201 on GitHub). A drawback of this change (besides a small overhead) is that there will be no easy way to tell a missing query string from an empty one (i.e. requests with or without the "?" character); yet, it's negligible compared to the possible benefits of wider application compatibility. This closes #226 issue on GitHub. --- src/nxt_php_sapi.c | 6 ++---- src/nxt_python_wsgi.c | 6 +----- src/perl/nxt_perl_psgi.c | 6 ++---- src/ruby/nxt_ruby.c | 6 ++---- 4 files changed, 7 insertions(+), 17 deletions(-) (limited to 'src') diff --git a/src/nxt_php_sapi.c b/src/nxt_php_sapi.c index e36dd20a..80321a85 100644 --- a/src/nxt_php_sapi.c +++ b/src/nxt_php_sapi.c @@ -917,10 +917,8 @@ nxt_php_register_variables(zval *track_vars_array TSRMLS_DC) track_vars_array TSRMLS_CC); nxt_php_set_sptr(req, "REQUEST_URI", &r->target, r->target_length, track_vars_array TSRMLS_CC); - if (r->query.offset) { - nxt_php_set_sptr(req, "QUERY_STRING", &r->query, r->query_length, - track_vars_array TSRMLS_CC); - } + nxt_php_set_sptr(req, "QUERY_STRING", &r->query, r->query_length, + track_vars_array TSRMLS_CC); nxt_php_set_sptr(req, "REMOTE_ADDR", &r->remote, r->remote_length, track_vars_array TSRMLS_CC); diff --git a/src/nxt_python_wsgi.c b/src/nxt_python_wsgi.c index 80ffb0f7..6478f38c 100644 --- a/src/nxt_python_wsgi.c +++ b/src/nxt_python_wsgi.c @@ -720,11 +720,7 @@ nxt_python_get_environ(nxt_python_run_ctx_t *ctx) RC(nxt_python_add_sptr(ctx, "REQUEST_METHOD", &r->method, r->method_length)); RC(nxt_python_add_sptr(ctx, "REQUEST_URI", &r->target, r->target_length)); - - if (r->query.offset) { - RC(nxt_python_add_sptr(ctx, "QUERY_STRING", &r->query, - r->query_length)); - } + RC(nxt_python_add_sptr(ctx, "QUERY_STRING", &r->query, r->query_length)); RC(nxt_python_add_sptr(ctx, "PATH_INFO", &r->path, r->path_length)); RC(nxt_python_add_sptr(ctx, "REMOTE_ADDR", &r->remote, r->remote_length)); diff --git a/src/perl/nxt_perl_psgi.c b/src/perl/nxt_perl_psgi.c index efcff7e3..3e865d46 100644 --- a/src/perl/nxt_perl_psgi.c +++ b/src/perl/nxt_perl_psgi.c @@ -548,10 +548,8 @@ nxt_perl_psgi_env_create(PerlInterpreter *my_perl, RC(nxt_perl_psgi_add_value(my_perl, hash_env, NL("psgi.streaming"), &PL_sv_no)); - if (r->query.offset) { - RC(nxt_perl_psgi_add_sptr(my_perl, hash_env, NL("QUERY_STRING"), - &r->query, r->query_length)); - } + RC(nxt_perl_psgi_add_sptr(my_perl, hash_env, NL("QUERY_STRING"), + &r->query, r->query_length)); RC(nxt_perl_psgi_add_sptr(my_perl, hash_env, NL("SERVER_PROTOCOL"), &r->version, r->version_length)); RC(nxt_perl_psgi_add_sptr(my_perl, hash_env, NL("REMOTE_ADDR"), diff --git a/src/ruby/nxt_ruby.c b/src/ruby/nxt_ruby.c index 17831175..b2398abe 100644 --- a/src/ruby/nxt_ruby.c +++ b/src/ruby/nxt_ruby.c @@ -442,10 +442,8 @@ nxt_ruby_read_request(VALUE hash_env) nxt_ruby_add_sptr(hash_env, NL("REQUEST_URI"), &r->target, r->target_length); nxt_ruby_add_sptr(hash_env, NL("PATH_INFO"), &r->path, r->path_length); - if (r->query.offset) { - nxt_ruby_add_sptr(hash_env, NL("QUERY_STRING"), &r->query, - r->query_length); - } + nxt_ruby_add_sptr(hash_env, NL("QUERY_STRING"), &r->query, + r->query_length); nxt_ruby_add_sptr(hash_env, NL("SERVER_PROTOCOL"), &r->version, r->version_length); nxt_ruby_add_sptr(hash_env, NL("REMOTE_ADDR"), &r->remote, -- cgit From 3b2c1d0eaaf8252242d2a164ad768a985e8ded5c Mon Sep 17 00:00:00 2001 From: Alexander Borisov Date: Fri, 1 Mar 2019 16:50:25 +0300 Subject: Perl: added implementation delayed response and streaming body. --- src/perl/nxt_perl_psgi.c | 192 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 176 insertions(+), 16 deletions(-) (limited to 'src') diff --git a/src/perl/nxt_perl_psgi.c b/src/perl/nxt_perl_psgi.c index 3e865d46..0b4b31d7 100644 --- a/src/perl/nxt_perl_psgi.c +++ b/src/perl/nxt_perl_psgi.c @@ -92,6 +92,8 @@ static ssize_t nxt_perl_psgi_io_read(nxt_unit_read_info_t *read_info, void *dst, size_t size); static int nxt_perl_psgi_result_array(PerlInterpreter *my_perl, SV *result, nxt_unit_request_info_t *req); +static void nxt_perl_psgi_result_cb(PerlInterpreter *my_perl, SV *result, + nxt_unit_request_info_t *req); static nxt_int_t nxt_perl_psgi_init(nxt_task_t *task, nxt_common_app_conf_t *conf); @@ -101,8 +103,11 @@ static void nxt_perl_psgi_atexit(void); typedef SV *(*nxt_perl_psgi_callback_f)(PerlInterpreter *my_perl, SV *env, nxt_task_t *task); -static PerlInterpreter *nxt_perl_psgi; -static nxt_perl_psgi_io_arg_t nxt_perl_psgi_arg_input, nxt_perl_psgi_arg_error; +static CV *nxt_perl_psgi_cb; +static PerlInterpreter *nxt_perl_psgi; +static nxt_perl_psgi_io_arg_t nxt_perl_psgi_arg_input; +static nxt_perl_psgi_io_arg_t nxt_perl_psgi_arg_error; +static nxt_unit_request_info_t *nxt_perl_psgi_request; static uint32_t nxt_perl_psgi_compat[] = { NXT_VERNUM, NXT_DEBUG, @@ -206,6 +211,115 @@ XS(XS_NGINX__Unit__PSGI_exit) } +XS(XS_NGINX__Unit__Sandbox_write); +XS(XS_NGINX__Unit__Sandbox_write) +{ + int rc; + char *body; + size_t len; + + dXSARGS; + + if (nxt_slow_path(items != 2)) { + Perl_croak(aTHX_ "Wrong number of arguments. Need one string"); + + XSRETURN_EMPTY; + } + + body = SvPV(ST(1), len); + + rc = nxt_unit_response_write(nxt_perl_psgi_request, body, len); + if (nxt_slow_path(rc != NXT_UNIT_OK)) { + Perl_croak(aTHX_ "Failed to write response body"); + + XSRETURN_EMPTY; + } + + XSRETURN_IV(len); +} + + +nxt_inline void +nxt_perl_psgi_cb_request_done(nxt_int_t status) +{ + nxt_unit_request_info_t *req; + + req = nxt_perl_psgi_request; + + if (req != NULL) { + nxt_unit_request_done(req, status); + nxt_perl_psgi_request = NULL; + } +} + + +XS(XS_NGINX__Unit__Sandbox_close); +XS(XS_NGINX__Unit__Sandbox_close) +{ + I32 ax; + + ax = POPMARK; + + nxt_perl_psgi_cb_request_done(NXT_UNIT_OK); + + XSRETURN_NO; +} + + +XS(XS_NGINX__Unit__Sandbox_cb); +XS(XS_NGINX__Unit__Sandbox_cb) +{ + SV *obj; + int rc; + long array_len; + + dXSARGS; + + if (nxt_slow_path(items != 1)) { + nxt_perl_psgi_cb_request_done(NXT_UNIT_ERROR); + + Perl_croak(aTHX_ "Wrong number of arguments"); + + XSRETURN_EMPTY; + } + + if (nxt_slow_path(SvOK(ST(0)) == 0 || SvROK(ST(0)) == 0 + || SvTYPE(SvRV(ST(0))) != SVt_PVAV)) + { + nxt_perl_psgi_cb_request_done(NXT_UNIT_ERROR); + + Perl_croak(aTHX_ "PSGI: An unexpected response was received " + "from Perl Application"); + + XSRETURN_EMPTY; + } + + rc = nxt_perl_psgi_result_array(PERL_GET_CONTEXT, ST(0), + nxt_perl_psgi_request); + if (nxt_slow_path(rc != NXT_UNIT_OK)) { + nxt_perl_psgi_cb_request_done(NXT_UNIT_ERROR); + + Perl_croak(aTHX_ (char *) NULL); + + XSRETURN_EMPTY; + } + + array_len = av_len((AV *) SvRV(ST(0))); + + if (array_len < 2) { + obj = sv_bless(newRV_noinc((SV *) newHV()), + gv_stashpv("NGINX::Unit::Sandbox", GV_ADD)); + ST(0) = obj; + + XSRETURN(1); + } + + nxt_perl_psgi_cb_request_done(NXT_UNIT_OK); + + XSRETURN_EMPTY; +} + + static void nxt_perl_psgi_xs_init(pTHX) { @@ -218,6 +332,14 @@ nxt_perl_psgi_xs_init(pTHX) /* DynaLoader for Perl modules who use XS */ newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, __FILE__); + + newXS("NGINX::Unit::Sandbox::write", XS_NGINX__Unit__Sandbox_write, + __FILE__); + newXS("NGINX::Unit::Sandbox::close", XS_NGINX__Unit__Sandbox_close, + __FILE__); + + nxt_perl_psgi_cb = newXS("NGINX::Unit::Sandbox::cb", + XS_NGINX__Unit__Sandbox_cb, __FILE__); } @@ -300,6 +422,9 @@ nxt_perl_psgi_module_create(nxt_task_t *task, const char *script) static nxt_str_t prefix = nxt_string( "package NGINX::Unit::Sandbox;" + "sub new {" + " return bless {}, $_[0];" + "}" "{my $app = do \"" ); @@ -546,7 +671,7 @@ nxt_perl_psgi_env_create(PerlInterpreter *my_perl, RC(nxt_perl_psgi_add_value(my_perl, hash_env, NL("psgi.nonblocking"), &PL_sv_no)); RC(nxt_perl_psgi_add_value(my_perl, hash_env, NL("psgi.streaming"), - &PL_sv_no)); + &PL_sv_yes)); RC(nxt_perl_psgi_add_sptr(my_perl, hash_env, NL("QUERY_STRING"), &r->query, r->query_length)); @@ -975,6 +1100,36 @@ nxt_perl_psgi_result_array(PerlInterpreter *my_perl, SV *result, } +static void +nxt_perl_psgi_result_cb(PerlInterpreter *my_perl, SV *result, + nxt_unit_request_info_t *req) +{ + dSP; + + ENTER; + SAVETMPS; + + PUSHMARK(sp); + XPUSHs(newRV_noinc((SV*) nxt_perl_psgi_cb)); + PUTBACK; + + call_sv(result, G_EVAL|G_SCALAR); + + SPAGAIN; + + if (SvTRUE(ERRSV)) { + nxt_unit_error(NULL, "PSGI: Failed to execute result callback: \n%s", + SvPV_nolen(ERRSV)); + + nxt_perl_psgi_cb_request_done(NXT_UNIT_ERROR); + } + + PUTBACK; + FREETMPS; + LEAVE; +} + + static nxt_int_t nxt_perl_psgi_init(nxt_task_t *task, nxt_common_app_conf_t *conf) { @@ -1031,6 +1186,8 @@ nxt_perl_psgi_request_handler(nxt_unit_request_info_t *req) input.my_perl = my_perl; input.req = req; + nxt_perl_psgi_request = req; + /* * Create environ variable for perl sub "application". * > sub application { @@ -1051,23 +1208,26 @@ nxt_perl_psgi_request_handler(nxt_unit_request_info_t *req) /* Call perl sub and get result as SV*. */ result = nxt_perl_psgi_call_var_application(my_perl, env, module->app, req); - /* - * We expect ARRAY ref like a - * ['200', ['Content-Type' => "text/plain"], ["body"]] - */ - if (nxt_slow_path(SvOK(result) == 0 || SvROK(result) == 0 - || SvTYPE(SvRV(result)) != SVt_PVAV)) - { - nxt_unit_req_error(req, "PSGI: An unexpected response was received " - "from Perl Application"); + if (nxt_fast_path(SvOK(result) != 0 && SvROK(result) != 0)) { - rc = NXT_UNIT_ERROR; + if (SvTYPE(SvRV(result)) == SVt_PVAV) { + rc = nxt_perl_psgi_result_array(my_perl, result, req); + nxt_unit_request_done(req, rc); + goto release; + } - } else { - rc = nxt_perl_psgi_result_array(my_perl, result, req); + if (SvTYPE(SvRV(result)) == SVt_PVCV) { + nxt_perl_psgi_result_cb(my_perl, result, req); + goto release; + } } - nxt_unit_request_done(req, rc); + nxt_unit_req_error(req, "PSGI: An unexpected response was received " + "from Perl Application"); + + nxt_unit_request_done(req, NXT_UNIT_ERROR); + +release: SvREFCNT_dec(result); SvREFCNT_dec(env); -- cgit From d92feef571696c4e0ec837fd7f7e237aea038af4 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Fri, 1 Mar 2019 17:12:40 +0300 Subject: README.JSR-340 legal notice to save our ughm.. bottoms of bodies. --- src/java/README.JSR-340 | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/java/README.JSR-340 (limited to 'src') diff --git a/src/java/README.JSR-340 b/src/java/README.JSR-340 new file mode 100644 index 00000000..0eb189a7 --- /dev/null +++ b/src/java/README.JSR-340 @@ -0,0 +1,16 @@ +NOTICE: + +This version of Unit code is made available in support of the open source +development process. This is an intermediate build made available for +testing purposes only. This Unit code is untested and presumed incompatible +with the JSR 340 Java Servlet 3.1 specification. You should not deploy or +write to this code. You should instead deploy and write production +applications on pre-built binaries that have been tested and certified +to meet the JSR-340 compatibility requirements such as certified binaries +published for the JSR-340 reference implementation available at +https://javaee.github.io/glassfish/. + +Redistribution of any Intermediate Build must retain this notice. + +Oracle and Java are registered trademarks of Oracle and/or its affiliates. +Other names may be trademarks of their respective owners. -- cgit From 86d4a0e473ee2f207f83357e8a3f32b2e20e4e12 Mon Sep 17 00:00:00 2001 From: Igor Sysoev Date: Fri, 1 Mar 2019 18:20:05 +0300 Subject: Fixed TLS connections hanging. After event is delivered from the kernel its further processing is blocked. Non-ready TSL I/O operation should mark connection I/O state as not ready to unblock events and to allow their further processing. Otherwise the connection hangs. --- src/nxt_openssl.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nxt_openssl.c b/src/nxt_openssl.c index 99dd2077..c01f92c8 100644 --- a/src/nxt_openssl.c +++ b/src/nxt_openssl.c @@ -855,12 +855,11 @@ nxt_openssl_conn_test_error(nxt_task_t *task, nxt_conn_t *c, int ret, switch (tls->ssl_error) { case SSL_ERROR_WANT_READ: + c->socket.read_ready = 0; if (io != NXT_OPENSSL_READ) { nxt_fd_event_block_write(task->thread->engine, &c->socket); - c->socket.read_ready = 0; - if (nxt_fd_event_is_disabled(c->socket.read)) { nxt_fd_event_enable_read(task->thread->engine, &c->socket); } @@ -869,12 +868,11 @@ nxt_openssl_conn_test_error(nxt_task_t *task, nxt_conn_t *c, int ret, return NXT_AGAIN; case SSL_ERROR_WANT_WRITE: + c->socket.write_ready = 0; if (io != NXT_OPENSSL_WRITE) { nxt_fd_event_block_read(task->thread->engine, &c->socket); - c->socket.write_ready = 0; - if (nxt_fd_event_is_disabled(c->socket.write)) { nxt_fd_event_enable_write(task->thread->engine, &c->socket); } -- cgit