1
2 /*
3 * Copyright (C) NGINX, Inc.
4 */
5
6
7 #include <jni.h>
8
9 #include <nxt_main.h>
10 #include <nxt_runtime.h>
11 #include <nxt_router.h>
12 #include <nxt_unit.h>
13 #include <nxt_unit_field.h>
14 #include <nxt_unit_request.h>
15 #include <nxt_unit_response.h>
16 #include <nxt_unit_websocket.h>
17
18 #include <java/nxt_jni.h>
19
20 #include "java/nxt_jni_Thread.h"
21 #include "java/nxt_jni_Context.h"
22 #include "java/nxt_jni_Request.h"
23 #include "java/nxt_jni_Response.h"
24 #include "java/nxt_jni_InputStream.h"
25 #include "java/nxt_jni_OutputStream.h"
26 #include "java/nxt_jni_URLClassLoader.h"
27
28 #include "nxt_jars.h"
29
30 #include NXT_JAVA_MOUNTS_H
31
32 static nxt_int_t nxt_java_setup(nxt_task_t *task, nxt_process_t *process,
33 nxt_common_app_conf_t *conf);
34 static nxt_int_t nxt_java_start(nxt_task_t *task,
35 nxt_process_data_t *data);
36 static void nxt_java_request_handler(nxt_unit_request_info_t *req);
37 static void nxt_java_websocket_handler(nxt_unit_websocket_frame_t *ws);
38 static void nxt_java_close_handler(nxt_unit_request_info_t *req);
39 static int nxt_java_ready_handler(nxt_unit_ctx_t *ctx);
40 static void *nxt_java_thread_func(void *main_ctx);
41 static int nxt_java_init_threads(nxt_java_app_conf_t *c);
42 static void nxt_java_join_threads(nxt_unit_ctx_t *ctx,
43 nxt_java_app_conf_t *c);
44
45 static uint32_t compat[] = {
46 NXT_VERNUM, NXT_DEBUG,
47 };
48
49 char *nxt_java_modules;
50
51 static pthread_t *nxt_java_threads;
52 static pthread_attr_t *nxt_java_thread_attr;
53
54
55 #define NXT_STRING(x) _NXT_STRING(x)
56 #define _NXT_STRING(x) #x
57
58 NXT_EXPORT nxt_app_module_t nxt_app_module = {
59 sizeof(compat),
60 compat,
61 nxt_string("java"),
62 NXT_STRING(NXT_JAVA_VERSION),
63 nxt_java_mounts,
64 nxt_nitems(nxt_java_mounts),
65 nxt_java_setup,
66 nxt_java_start,
67 };
68
69 typedef struct {
70 JavaVM *jvm;
71 jobject cl;
72 jobject ctx;
73 nxt_java_app_conf_t *conf;
74 } nxt_java_data_t;
75
76
77 static nxt_int_t
nxt_java_setup(nxt_task_t * task,nxt_process_t * process,nxt_common_app_conf_t * conf)78 nxt_java_setup(nxt_task_t *task, nxt_process_t *process,
79 nxt_common_app_conf_t *conf)
80 {
81 char *path, *relpath, *p, *rootfs;
82 size_t jars_dir_len, rootfs_len;
83 const char *unit_jars;
84
85 rootfs = (char *) process->isolation.rootfs;
86 rootfs_len = 0;
87
88 unit_jars = conf->u.java.unit_jars;
89 if (unit_jars == NULL) {
90 if (rootfs != NULL) {
91 unit_jars = "/";
92 } else {
93 unit_jars = NXT_JARS;
94 }
95 }
96
97 relpath = strdup(unit_jars);
98 if (nxt_slow_path(relpath == NULL)) {
99 return NXT_ERROR;
100 }
101
102 if (rootfs != NULL) {
103 jars_dir_len = strlen(unit_jars);
104 rootfs_len = strlen(rootfs);
105
106 path = nxt_malloc(jars_dir_len + rootfs_len + 1);
107 if (nxt_slow_path(path == NULL)) {
108 free(relpath);
109 return NXT_ERROR;
110 }
111
112 p = nxt_cpymem(path, process->isolation.rootfs, rootfs_len);
113 p = nxt_cpymem(p, relpath, jars_dir_len);
114 *p = '\0';
115
116 free(relpath);
117
118 } else {
119 path = relpath;
120 }
121
122 nxt_java_modules = realpath(path, NULL);
123 if (nxt_java_modules == NULL) {
124 nxt_alert(task, "realpath(\"%s\") failed %E", path, nxt_errno);
125 goto free;
126 }
127
128 if (rootfs != NULL && strlen(path) > rootfs_len) {
129 nxt_java_modules = path + rootfs_len;
130 }
131
132 nxt_debug(task, "JAVA MODULES: %s", nxt_java_modules);
133
134 return NXT_OK;
135
136 free:
137
138 nxt_free(path);
139
140 return NXT_ERROR;
141 }
142
143
144 static char **
nxt_java_module_jars(const char * jars[],int jar_count)145 nxt_java_module_jars(const char *jars[], int jar_count)
146 {
147 char **res, *jurl;
148 uint8_t pathsep;
149 nxt_int_t modules_len, jlen, i;
150 const char **jar;
151
152 res = nxt_malloc(jar_count * sizeof(char*));
153 if (res == NULL) {
154 return NULL;
155 }
156
157 modules_len = nxt_strlen(nxt_java_modules);
158
159 pathsep = nxt_java_modules[modules_len - 1] == '/';
160
161 for (i = 0, jar = jars; *jar != NULL; jar++) {
162 jlen = nxt_length("file:") + modules_len
163 + (!pathsep ? nxt_length("/") : 0)
164 + nxt_strlen(*jar) + 1;
165
166 jurl = nxt_malloc(jlen);
167 if (jurl == NULL) {
168 return NULL;
169 }
170
171 res[i++] = jurl;
172
173 jurl = nxt_cpymem(jurl, "file:", nxt_length("file:"));
174 jurl = nxt_cpymem(jurl, nxt_java_modules, modules_len);
175
176 if (!pathsep) {
177 *jurl++ = '/';
178 }
179
180 jurl = nxt_cpymem(jurl, *jar, nxt_strlen(*jar));
181 *jurl++ = '\0';
182 }
183
184 return res;
185 }
186
187
188 static nxt_int_t
nxt_java_start(nxt_task_t * task,nxt_process_data_t * data)189 nxt_java_start(nxt_task_t *task, nxt_process_data_t *data)
190 {
191 jint rc;
192 char *opt, *real_path;
193 char **classpath_arr, **unit_jars, **system_jars;
194 JavaVM *jvm;
195 JNIEnv *env;
196 jobject cl, classpath;
197 nxt_str_t str;
198 nxt_int_t opt_len, real_path_len;
199 nxt_uint_t i, unit_jars_count, classpath_count;
200 nxt_uint_t system_jars_count;
201 JavaVMOption *jvm_opt;
202 JavaVMInitArgs jvm_args;
203 nxt_unit_ctx_t *ctx;
204 nxt_unit_init_t java_init;
205 nxt_java_data_t java_data;
206 nxt_conf_value_t *value;
207 nxt_java_app_conf_t *c;
208 nxt_common_app_conf_t *app_conf;
209
210 //setenv("ASAN_OPTIONS", "handle_segv=0", 1);
211
212 jvm_args.version = JNI_VERSION_1_6;
213 jvm_args.nOptions = 0;
214 jvm_args.ignoreUnrecognized = 0;
215
216 app_conf = data->app;
217 c = &app_conf->u.java;
218
219 if (c->options != NULL) {
220 jvm_args.nOptions += nxt_conf_array_elements_count(c->options);
221 }
222
223 jvm_opt = nxt_malloc(jvm_args.nOptions * sizeof(JavaVMOption));
224 if (jvm_opt == NULL) {
225 nxt_alert(task, "failed to allocate jvm_opt");
226 return NXT_ERROR;
227 }
228
229 jvm_args.options = jvm_opt;
230
231 unit_jars_count = nxt_nitems(nxt_java_unit_jars) - 1;
232
233 unit_jars = nxt_java_module_jars(nxt_java_unit_jars, unit_jars_count);
234 if (unit_jars == NULL) {
235 nxt_alert(task, "failed to allocate buffer for unit_jars array");
236
237 return NXT_ERROR;
238 }
239
240 system_jars_count = nxt_nitems(nxt_java_system_jars) - 1;
241
242 system_jars = nxt_java_module_jars(nxt_java_system_jars, system_jars_count);
243 if (system_jars == NULL) {
244 nxt_alert(task, "failed to allocate buffer for system_jars array");
245
246 return NXT_ERROR;
247 }
248
249 if (c->options != NULL) {
250
251 for (i = 0; /* void */ ; i++) {
252 value = nxt_conf_get_array_element(c->options, i);
253 if (value == NULL) {
254 break;
255 }
256
257 nxt_conf_get_string(value, &str);
258
259 opt = nxt_malloc(str.length + 1);
260 if (opt == NULL) {
261 nxt_alert(task, "failed to allocate jvm_opt");
262 return NXT_ERROR;
263 }
264
265 memcpy(opt, str.start, str.length);
266 opt[str.length] = '\0';
267
268 jvm_opt[i].optionString = opt;
269 }
270 }
271
272 if (c->classpath != NULL) {
273 classpath_count = nxt_conf_array_elements_count(c->classpath);
274 classpath_arr = nxt_malloc(classpath_count * sizeof(char *));
275
276 for (i = 0; /* void */ ; i++) {
277 value = nxt_conf_get_array_element(c->classpath, i);
278 if (value == NULL) {
279 break;
280 }
281
282 nxt_conf_get_string(value, &str);
283
284 opt_len = str.length + 1;
285
286 char *sc = memchr(str.start, ':', str.length);
287 if (sc == NULL && str.start[0] == '/') {
288 opt_len += nxt_length("file:");
289 }
290
291 opt = nxt_malloc(opt_len);
292 if (opt == NULL) {
293 nxt_alert(task, "failed to allocate classpath");
294 return NXT_ERROR;
295 }
296
297 if (sc == NULL && str.start[0] != '/') {
298 nxt_memcpy(opt, str.start, str.length);
299 opt[str.length] = '\0';
300
301 real_path = realpath(opt, NULL);
302 if (real_path == NULL) {
303 nxt_alert(task, "realpath(%s) failed: %E", opt, nxt_errno);
304 return NXT_ERROR;
305 }
306
307 real_path_len = nxt_strlen(real_path);
308
309 free(opt);
310
311 opt_len = nxt_length("file:") + real_path_len + 1;
312
313 opt = nxt_malloc(opt_len);
314 if (opt == NULL) {
315 nxt_alert(task, "failed to allocate classpath");
316 return NXT_ERROR;
317 }
318
319 } else {
320 real_path = (char *) str.start; /* I love this cast! */
321 real_path_len = str.length;
322 }
323
324 classpath_arr[i] = opt;
325
326 if (sc == NULL) {
327 opt = nxt_cpymem(opt, "file:", nxt_length("file:"));
328 }
329
330 opt = nxt_cpymem(opt, real_path, real_path_len);
331 *opt = '\0';
332 }
333
334 } else {
335 classpath_count = 0;
336 classpath_arr = NULL;
337 }
338
339 rc = JNI_CreateJavaVM(&jvm, (void **) &env, &jvm_args);
340 if (rc != JNI_OK) {
341 nxt_alert(task, "failed to create Java VM: %d", (int) rc);
342 return NXT_ERROR;
343 }
344
345 rc = nxt_java_initThread(env);
346 if (rc != NXT_UNIT_OK) {
347 nxt_alert(task, "nxt_java_initThread() failed");
348 goto env_failed;
349 }
350
351 rc = nxt_java_initURLClassLoader(env);
352 if (rc != NXT_UNIT_OK) {
353 nxt_alert(task, "nxt_java_initURLClassLoader() failed");
354 goto env_failed;
355 }
356
357 cl = nxt_java_newURLClassLoader(env, system_jars_count, system_jars);
358 if (cl == NULL) {
359 nxt_alert(task, "nxt_java_newURLClassLoader failed");
360 goto env_failed;
361 }
362
363 nxt_java_setContextClassLoader(env, cl);
364
365 cl = nxt_java_newURLClassLoader_parent(env, unit_jars_count, unit_jars, cl);
366 if (cl == NULL) {
367 nxt_alert(task, "nxt_java_newURLClassLoader_parent failed");
368 goto env_failed;
369 }
370
371 nxt_java_setContextClassLoader(env, cl);
372
373 rc = nxt_java_initContext(env, cl);
374 if (rc != NXT_UNIT_OK) {
375 nxt_alert(task, "nxt_java_initContext() failed");
376 goto env_failed;
377 }
378
379 rc = nxt_java_initRequest(env, cl);
380 if (rc != NXT_UNIT_OK) {
381 nxt_alert(task, "nxt_java_initRequest() failed");
382 goto env_failed;
383 }
384
385 rc = nxt_java_initResponse(env, cl);
386 if (rc != NXT_UNIT_OK) {
387 nxt_alert(task, "nxt_java_initResponse() failed");
388 goto env_failed;
389 }
390
391 rc = nxt_java_initInputStream(env, cl);
392 if (rc != NXT_UNIT_OK) {
393 nxt_alert(task, "nxt_java_initInputStream() failed");
394 goto env_failed;
395 }
396
397 rc = nxt_java_initOutputStream(env, cl);
398 if (rc != NXT_UNIT_OK) {
399 nxt_alert(task, "nxt_java_initOutputStream() failed");
400 goto env_failed;
401 }
402
403 nxt_java_jni_init(env);
404 if (rc != NXT_UNIT_OK) {
405 nxt_alert(task, "nxt_java_jni_init() failed");
406 goto env_failed;
407 }
408
409 classpath = nxt_java_newURLs(env, classpath_count, classpath_arr);
410 if (classpath == NULL) {
411 nxt_alert(task, "nxt_java_newURLs failed");
412 goto env_failed;
413 }
414
415 java_data.jvm = jvm;
416 java_data.cl = cl;
417 java_data.ctx = nxt_java_startContext(env, c->webapp, classpath);
418 java_data.conf = c;
419
420 if ((*env)->ExceptionCheck(env)) {
421 nxt_alert(task, "Unhandled exception in application start");
422 (*env)->ExceptionDescribe(env);
423 return NXT_ERROR;
424 }
425
426 rc = nxt_java_init_threads(c);
427 if (nxt_slow_path(rc == NXT_UNIT_ERROR)) {
428 return NXT_ERROR;
429 }
430
431 nxt_unit_default_init(task, &java_init, app_conf);
432
433 java_init.callbacks.request_handler = nxt_java_request_handler;
434 java_init.callbacks.websocket_handler = nxt_java_websocket_handler;
435 java_init.callbacks.close_handler = nxt_java_close_handler;
436 java_init.callbacks.ready_handler = nxt_java_ready_handler;
437 java_init.request_data_size = sizeof(nxt_java_request_data_t);
438 java_init.data = &java_data;
439 java_init.ctx_data = env;
440
441 ctx = nxt_unit_init(&java_init);
442 if (nxt_slow_path(ctx == NULL)) {
443 nxt_alert(task, "nxt_unit_init() failed");
444 return NXT_ERROR;
445 }
446
447 rc = nxt_unit_run(ctx);
448
449 nxt_java_join_threads(ctx, c);
450
451 nxt_java_stopContext(env, java_data.ctx);
452
453 if ((*env)->ExceptionCheck(env)) {
454 (*env)->ExceptionDescribe(env);
455 }
456
457 nxt_unit_done(ctx);
458
459 (*jvm)->DestroyJavaVM(jvm);
460
461 exit(rc);
462
463 return NXT_OK;
464
465 env_failed:
466
467 if ((*env)->ExceptionCheck(env)) {
468 (*env)->ExceptionDescribe(env);
469 }
470
471 return NXT_ERROR;
472 }
473
474
475 static void
nxt_java_request_handler(nxt_unit_request_info_t * req)476 nxt_java_request_handler(nxt_unit_request_info_t *req)
477 {
478 JNIEnv *env;
479 jobject jreq, jresp;
480 nxt_java_data_t *java_data;
481 nxt_java_request_data_t *data;
482
483 java_data = req->unit->data;
484 env = req->ctx->data;
485 data = req->data;
486
487 jreq = nxt_java_newRequest(env, java_data->ctx, req);
488 if (jreq == NULL) {
489 nxt_unit_req_alert(req, "failed to create Request instance");
490
491 if ((*env)->ExceptionCheck(env)) {
492 (*env)->ExceptionDescribe(env);
493 (*env)->ExceptionClear(env);
494 }
495
496 nxt_unit_request_done(req, NXT_UNIT_ERROR);
497 return;
498 }
499
500 jresp = nxt_java_newResponse(env, req);
501 if (jresp == NULL) {
502 nxt_unit_req_alert(req, "failed to create Response instance");
503
504 if ((*env)->ExceptionCheck(env)) {
505 (*env)->ExceptionDescribe(env);
506 (*env)->ExceptionClear(env);
507 }
508
509 (*env)->DeleteLocalRef(env, jreq);
510
511 nxt_unit_request_done(req, NXT_UNIT_ERROR);
512 return;
513 }
514
515 data->header_size = 10 * 1024;
516 data->buf_size = 32 * 1024; /* from Jetty */
517 data->jreq = jreq;
518 data->jresp = jresp;
519 data->buf = NULL;
520
521 nxt_unit_request_group_dup_fields(req);
522
523 nxt_java_service(env, java_data->ctx, jreq, jresp);
524
525 if ((*env)->ExceptionCheck(env)) {
526 (*env)->ExceptionDescribe(env);
527 (*env)->ExceptionClear(env);
528 }
529
530 if (!nxt_unit_response_is_init(req)) {
531 nxt_unit_response_init(req, 200, 0, 0);
532 }
533
534 if (!nxt_unit_response_is_sent(req)) {
535 nxt_unit_response_send(req);
536 }
537
538 if (data->buf != NULL) {
539 nxt_unit_buf_send(data->buf);
540
541 data->buf = NULL;
542 }
543
544 if (nxt_unit_response_is_websocket(req)) {
545 data->jreq = (*env)->NewGlobalRef(env, jreq);
546 data->jresp = (*env)->NewGlobalRef(env, jresp);
547
548 } else {
549 nxt_unit_request_done(req, NXT_UNIT_OK);
550 }
551
552 (*env)->DeleteLocalRef(env, jresp);
553 (*env)->DeleteLocalRef(env, jreq);
554 }
555
556
557 static void
nxt_java_websocket_handler(nxt_unit_websocket_frame_t * ws)558 nxt_java_websocket_handler(nxt_unit_websocket_frame_t *ws)
559 {
560 void *b;
561 JNIEnv *env;
562 jobject jbuf;
563 nxt_java_request_data_t *data;
564
565 env = ws->req->ctx->data;
566 data = ws->req->data;
567
568 b = malloc(ws->payload_len);
569 if (b != NULL) {
570 nxt_unit_websocket_read(ws, b, ws->payload_len);
571
572 jbuf = (*env)->NewDirectByteBuffer(env, b, ws->payload_len);
573 if (jbuf != NULL) {
574 nxt_java_Request_websocket(env, data->jreq, jbuf,
575 ws->header->opcode, ws->header->fin);
576
577 if ((*env)->ExceptionCheck(env)) {
578 (*env)->ExceptionDescribe(env);
579 (*env)->ExceptionClear(env);
580 }
581
582 (*env)->DeleteLocalRef(env, jbuf);
583 }
584
585 free(b);
586 }
587
588 nxt_unit_websocket_done(ws);
589 }
590
591
592 static void
nxt_java_close_handler(nxt_unit_request_info_t * req)593 nxt_java_close_handler(nxt_unit_request_info_t *req)
594 {
595 JNIEnv *env;
596 nxt_java_request_data_t *data;
597
598 env = req->ctx->data;
599 data = req->data;
600
601 nxt_java_Request_close(env, data->jreq);
602
603 (*env)->DeleteGlobalRef(env, data->jresp);
604 (*env)->DeleteGlobalRef(env, data->jreq);
605
606 nxt_unit_request_done(req, NXT_UNIT_OK);
607 }
608
609
610 static int
nxt_java_ready_handler(nxt_unit_ctx_t * ctx)611 nxt_java_ready_handler(nxt_unit_ctx_t *ctx)
612 {
613 int res;
614 uint32_t i;
615 nxt_java_data_t *java_data;
616 nxt_java_app_conf_t *c;
617
618 java_data = ctx->unit->data;
619 c = java_data->conf;
620
621 if (c->threads <= 1) {
622 return NXT_UNIT_OK;
623 }
624
625 for (i = 0; i < c->threads - 1; i++) {
626 res = pthread_create(&nxt_java_threads[i], nxt_java_thread_attr,
627 nxt_java_thread_func, ctx);
628
629 if (nxt_fast_path(res == 0)) {
630 nxt_unit_debug(ctx, "thread #%d created", (int) (i + 1));
631
632 } else {
633 nxt_unit_alert(ctx, "thread #%d create failed: %s (%d)",
634 (int) (i + 1), strerror(res), res);
635
636 return NXT_UNIT_ERROR;
637 }
638 }
639
640 return NXT_UNIT_OK;
641 }
642
643
644 static void *
nxt_java_thread_func(void * data)645 nxt_java_thread_func(void *data)
646 {
647 int rc;
648 JavaVM *jvm;
649 JNIEnv *env;
650 nxt_unit_ctx_t *main_ctx, *ctx;
651 nxt_java_data_t *java_data;
652
653 main_ctx = data;
654
655 nxt_unit_debug(main_ctx, "worker thread start");
656
657 java_data = main_ctx->unit->data;
658 jvm = java_data->jvm;
659
660 rc = (*jvm)->AttachCurrentThread(jvm, (void **) &env, NULL);
661 if (rc != JNI_OK) {
662 nxt_unit_alert(main_ctx, "failed to attach Java VM: %d", (int) rc);
663 return NULL;
664 }
665
666 nxt_java_setContextClassLoader(env, java_data->cl);
667
668 ctx = nxt_unit_ctx_alloc(main_ctx, env);
669 if (nxt_slow_path(ctx == NULL)) {
670 goto fail;
671 }
672
673 (void) nxt_unit_run(ctx);
674
675 nxt_unit_done(ctx);
676
677 fail:
678
679 (*jvm)->DetachCurrentThread(jvm);
680
681 nxt_unit_debug(NULL, "worker thread end");
682
683 return NULL;
684 }
685
686
687 static int
nxt_java_init_threads(nxt_java_app_conf_t * c)688 nxt_java_init_threads(nxt_java_app_conf_t *c)
689 {
690 int res;
691 static pthread_attr_t attr;
692
693 if (c->threads <= 1) {
694 return NXT_UNIT_OK;
695 }
696
697 if (c->thread_stack_size > 0) {
698 res = pthread_attr_init(&attr);
699 if (nxt_slow_path(res != 0)) {
700 nxt_unit_alert(NULL, "thread attr init failed: %s (%d)",
701 strerror(res), res);
702
703 return NXT_UNIT_ERROR;
704 }
705
706 res = pthread_attr_setstacksize(&attr, c->thread_stack_size);
707 if (nxt_slow_path(res != 0)) {
708 nxt_unit_alert(NULL, "thread attr set stack size failed: %s (%d)",
709 strerror(res), res);
710
711 return NXT_UNIT_ERROR;
712 }
713
714 nxt_java_thread_attr = &attr;
715 }
716
717 nxt_java_threads = nxt_unit_malloc(NULL,
718 sizeof(pthread_t) * (c->threads - 1));
719 if (nxt_slow_path(nxt_java_threads == NULL)) {
720 nxt_unit_alert(NULL, "Failed to allocate thread id array");
721
722 return NXT_UNIT_ERROR;
723 }
724
725 memset(nxt_java_threads, 0, sizeof(pthread_t) * (c->threads - 1));
726
727 return NXT_UNIT_OK;
728 }
729
730
731 static void
nxt_java_join_threads(nxt_unit_ctx_t * ctx,nxt_java_app_conf_t * c)732 nxt_java_join_threads(nxt_unit_ctx_t *ctx, nxt_java_app_conf_t *c)
733 {
734 int res;
735 uint32_t i;
736
737 if (nxt_java_threads == NULL) {
738 return;
739 }
740
741 for (i = 0; i < c->threads - 1; i++) {
742 if ((uintptr_t) nxt_java_threads[i] == 0) {
743 continue;
744 }
745
746 res = pthread_join(nxt_java_threads[i], NULL);
747
748 if (nxt_fast_path(res == 0)) {
749 nxt_unit_debug(ctx, "thread #%d joined", (int) i);
750
751 } else {
752 nxt_unit_alert(ctx, "thread #%d join failed: %s (%d)",
753 (int) i, strerror(res), res);
754 }
755 }
756
757 nxt_unit_free(ctx, nxt_java_threads);
758 }
759
760
761