xref: /unit/src/nxt_java.c (revision 1532:4b4d0c3ce94b)
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 
40 static uint32_t  compat[] = {
41     NXT_VERNUM, NXT_DEBUG,
42 };
43 
44 char  *nxt_java_modules;
45 
46 
47 #define NXT_STRING(x)   _NXT_STRING(x)
48 #define _NXT_STRING(x)  #x
49 
50 NXT_EXPORT nxt_app_module_t  nxt_app_module = {
51     sizeof(compat),
52     compat,
53     nxt_string("java"),
54     NXT_STRING(NXT_JAVA_VERSION),
55     nxt_java_mounts,
56     nxt_nitems(nxt_java_mounts),
57     nxt_java_setup,
58     nxt_java_start,
59 };
60 
61 typedef struct {
62     JNIEnv   *env;
63     jobject  ctx;
64 } nxt_java_data_t;
65 
66 
67 static nxt_int_t
68 nxt_java_setup(nxt_task_t *task, nxt_process_t *process,
69     nxt_common_app_conf_t *conf)
70 {
71     char        *path, *relpath, *p, *rootfs;
72     size_t      jars_dir_len, rootfs_len;
73     const char  *unit_jars;
74 
75     rootfs = (char *) process->isolation.rootfs;
76     rootfs_len = 0;
77 
78     unit_jars = conf->u.java.unit_jars;
79     if (unit_jars == NULL) {
80         if (rootfs != NULL) {
81             unit_jars = "/";
82         } else {
83             unit_jars = NXT_JARS;
84         }
85     }
86 
87     relpath = strdup(unit_jars);
88     if (nxt_slow_path(relpath == NULL)) {
89         return NXT_ERROR;
90     }
91 
92     if (rootfs != NULL) {
93         jars_dir_len = strlen(unit_jars);
94         rootfs_len = strlen(rootfs);
95 
96         path = nxt_malloc(jars_dir_len + rootfs_len + 1);
97         if (nxt_slow_path(path == NULL)) {
98             free(relpath);
99             return NXT_ERROR;
100         }
101 
102         p = nxt_cpymem(path, process->isolation.rootfs, rootfs_len);
103         p = nxt_cpymem(p, relpath, jars_dir_len);
104         *p = '\0';
105 
106         free(relpath);
107 
108     } else {
109         path = relpath;
110     }
111 
112     nxt_java_modules = realpath(path, NULL);
113     if (nxt_java_modules == NULL) {
114         nxt_alert(task, "realpath(\"%s\") failed %E", path, nxt_errno);
115         goto free;
116     }
117 
118     if (rootfs != NULL && strlen(path) > rootfs_len) {
119         nxt_java_modules = path + rootfs_len;
120     }
121 
122     nxt_debug(task, "JAVA MODULES: %s", nxt_java_modules);
123 
124     return NXT_OK;
125 
126 free:
127 
128     nxt_free(path);
129 
130     return NXT_ERROR;
131 }
132 
133 
134 static char **
135 nxt_java_module_jars(const char *jars[], int jar_count)
136 {
137     char        **res, *jurl;
138     uint8_t     pathsep;
139     nxt_int_t   modules_len, jlen, i;
140     const char  **jar;
141 
142     res = nxt_malloc(jar_count * sizeof(char*));
143     if (res == NULL) {
144         return NULL;
145     }
146 
147     modules_len = nxt_strlen(nxt_java_modules);
148 
149     pathsep = nxt_java_modules[modules_len - 1] == '/';
150 
151     for (i = 0, jar = jars; *jar != NULL; jar++) {
152         jlen = nxt_length("file:") + modules_len
153                + (!pathsep ? nxt_length("/") : 0)
154                + nxt_strlen(*jar) + 1;
155 
156         jurl = nxt_malloc(jlen);
157         if (jurl == NULL) {
158             return NULL;
159         }
160 
161         res[i++] = jurl;
162 
163         jurl = nxt_cpymem(jurl, "file:", nxt_length("file:"));
164         jurl = nxt_cpymem(jurl, nxt_java_modules, modules_len);
165 
166         if (!pathsep) {
167             *jurl++ = '/';
168         }
169 
170         jurl = nxt_cpymem(jurl, *jar, nxt_strlen(*jar));
171         *jurl++ = '\0';
172     }
173 
174     return res;
175 }
176 
177 
178 static nxt_int_t
179 nxt_java_start(nxt_task_t *task, nxt_process_data_t *data)
180 {
181     jint                   rc;
182     char                   *opt, *real_path;
183     char                   **classpath_arr, **unit_jars, **system_jars;
184     JavaVM                 *jvm;
185     JNIEnv                 *env;
186     jobject                cl, classpath;
187     nxt_str_t              str;
188     nxt_int_t              opt_len, real_path_len;
189     nxt_uint_t             i, unit_jars_count, classpath_count;
190     nxt_uint_t             system_jars_count;
191     JavaVMOption           *jvm_opt;
192     JavaVMInitArgs         jvm_args;
193     nxt_unit_ctx_t         *ctx;
194     nxt_unit_init_t        java_init;
195     nxt_java_data_t        java_data;
196     nxt_conf_value_t       *value;
197     nxt_java_app_conf_t    *c;
198     nxt_common_app_conf_t  *app_conf;
199 
200     //setenv("ASAN_OPTIONS", "handle_segv=0", 1);
201 
202     jvm_args.version = JNI_VERSION_1_6;
203     jvm_args.nOptions = 0;
204     jvm_args.ignoreUnrecognized = 0;
205 
206     app_conf = data->app;
207     c = &app_conf->u.java;
208 
209     if (c->options != NULL) {
210         jvm_args.nOptions += nxt_conf_array_elements_count(c->options);
211     }
212 
213     jvm_opt = nxt_malloc(jvm_args.nOptions * sizeof(JavaVMOption));
214     if (jvm_opt == NULL) {
215         nxt_alert(task, "failed to allocate jvm_opt");
216         return NXT_ERROR;
217     }
218 
219     jvm_args.options = jvm_opt;
220 
221     unit_jars_count = nxt_nitems(nxt_java_unit_jars) - 1;
222 
223     unit_jars = nxt_java_module_jars(nxt_java_unit_jars, unit_jars_count);
224     if (unit_jars == NULL) {
225         nxt_alert(task, "failed to allocate buffer for unit_jars array");
226 
227         return NXT_ERROR;
228     }
229 
230     system_jars_count = nxt_nitems(nxt_java_system_jars) - 1;
231 
232     system_jars = nxt_java_module_jars(nxt_java_system_jars, system_jars_count);
233     if (system_jars == NULL) {
234         nxt_alert(task, "failed to allocate buffer for system_jars array");
235 
236         return NXT_ERROR;
237     }
238 
239     if (c->options != NULL) {
240 
241         for (i = 0; /* void */ ; i++) {
242             value = nxt_conf_get_array_element(c->options, i);
243             if (value == NULL) {
244                 break;
245             }
246 
247             nxt_conf_get_string(value, &str);
248 
249             opt = nxt_malloc(str.length + 1);
250             if (opt == NULL) {
251                 nxt_alert(task, "failed to allocate jvm_opt");
252                 return NXT_ERROR;
253             }
254 
255             memcpy(opt, str.start, str.length);
256             opt[str.length] = '\0';
257 
258             jvm_opt[i].optionString = opt;
259         }
260     }
261 
262     if (c->classpath != NULL) {
263         classpath_count = nxt_conf_array_elements_count(c->classpath);
264         classpath_arr = nxt_malloc(classpath_count * sizeof(char *));
265 
266         for (i = 0; /* void */ ; i++) {
267             value = nxt_conf_get_array_element(c->classpath, i);
268             if (value == NULL) {
269                 break;
270             }
271 
272             nxt_conf_get_string(value, &str);
273 
274             opt_len = str.length + 1;
275 
276             char *sc = memchr(str.start, ':', str.length);
277             if (sc == NULL && str.start[0] == '/') {
278                 opt_len += nxt_length("file:");
279             }
280 
281             opt = nxt_malloc(opt_len);
282             if (opt == NULL) {
283                 nxt_alert(task, "failed to allocate classpath");
284                 return NXT_ERROR;
285             }
286 
287             if (sc == NULL && str.start[0] != '/') {
288                 nxt_memcpy(opt, str.start, str.length);
289                 opt[str.length] = '\0';
290 
291                 real_path = realpath(opt, NULL);
292                 if (real_path == NULL) {
293                     nxt_alert(task, "realpath(%s) failed: %E", opt, nxt_errno);
294                     return NXT_ERROR;
295                 }
296 
297                 real_path_len = nxt_strlen(real_path);
298 
299                 free(opt);
300 
301                 opt_len = nxt_length("file:") + real_path_len + 1;
302 
303                 opt = nxt_malloc(opt_len);
304                 if (opt == NULL) {
305                     nxt_alert(task, "failed to allocate classpath");
306                     return NXT_ERROR;
307                 }
308 
309             } else {
310                 real_path = (char *) str.start;  /* I love this cast! */
311                 real_path_len = str.length;
312             }
313 
314             classpath_arr[i] = opt;
315 
316             if (sc == NULL) {
317                 opt = nxt_cpymem(opt, "file:", nxt_length("file:"));
318             }
319 
320             opt = nxt_cpymem(opt, real_path, real_path_len);
321             *opt = '\0';
322         }
323 
324     } else {
325         classpath_count = 0;
326         classpath_arr = NULL;
327     }
328 
329     rc = JNI_CreateJavaVM(&jvm, (void **) &env, &jvm_args);
330     if (rc != JNI_OK) {
331         nxt_alert(task, "failed to create Java VM: %d", (int) rc);
332         return NXT_ERROR;
333     }
334 
335     rc = nxt_java_initThread(env);
336     if (rc != NXT_UNIT_OK) {
337         nxt_alert(task, "nxt_java_initThread() failed");
338         goto env_failed;
339     }
340 
341     rc = nxt_java_initURLClassLoader(env);
342     if (rc != NXT_UNIT_OK) {
343         nxt_alert(task, "nxt_java_initURLClassLoader() failed");
344         goto env_failed;
345     }
346 
347     cl = nxt_java_newURLClassLoader(env, system_jars_count, system_jars);
348     if (cl == NULL) {
349         nxt_alert(task, "nxt_java_newURLClassLoader failed");
350         goto env_failed;
351     }
352 
353     nxt_java_setContextClassLoader(env, cl);
354 
355     cl = nxt_java_newURLClassLoader_parent(env, unit_jars_count, unit_jars, cl);
356     if (cl == NULL) {
357         nxt_alert(task, "nxt_java_newURLClassLoader_parent failed");
358         goto env_failed;
359     }
360 
361     nxt_java_setContextClassLoader(env, cl);
362 
363     rc = nxt_java_initContext(env, cl);
364     if (rc != NXT_UNIT_OK) {
365         nxt_alert(task, "nxt_java_initContext() failed");
366         goto env_failed;
367     }
368 
369     rc = nxt_java_initRequest(env, cl);
370     if (rc != NXT_UNIT_OK) {
371         nxt_alert(task, "nxt_java_initRequest() failed");
372         goto env_failed;
373     }
374 
375     rc = nxt_java_initResponse(env, cl);
376     if (rc != NXT_UNIT_OK) {
377         nxt_alert(task, "nxt_java_initResponse() failed");
378         goto env_failed;
379     }
380 
381     rc = nxt_java_initInputStream(env, cl);
382     if (rc != NXT_UNIT_OK) {
383         nxt_alert(task, "nxt_java_initInputStream() failed");
384         goto env_failed;
385     }
386 
387     rc = nxt_java_initOutputStream(env, cl);
388     if (rc != NXT_UNIT_OK) {
389         nxt_alert(task, "nxt_java_initOutputStream() failed");
390         goto env_failed;
391     }
392 
393     nxt_java_jni_init(env);
394     if (rc != NXT_UNIT_OK) {
395         nxt_alert(task, "nxt_java_jni_init() failed");
396         goto env_failed;
397     }
398 
399     classpath = nxt_java_newURLs(env, classpath_count, classpath_arr);
400     if (classpath == NULL) {
401         nxt_alert(task, "nxt_java_newURLs failed");
402         goto env_failed;
403     }
404 
405     java_data.env = env;
406     java_data.ctx = nxt_java_startContext(env, c->webapp, classpath);
407 
408     if ((*env)->ExceptionCheck(env)) {
409         nxt_alert(task, "Unhandled exception in application start");
410         (*env)->ExceptionDescribe(env);
411         return NXT_ERROR;
412     }
413 
414     nxt_unit_default_init(task, &java_init);
415 
416     java_init.callbacks.request_handler = nxt_java_request_handler;
417     java_init.callbacks.websocket_handler = nxt_java_websocket_handler;
418     java_init.callbacks.close_handler = nxt_java_close_handler;
419     java_init.request_data_size = sizeof(nxt_java_request_data_t);
420     java_init.data = &java_data;
421     java_init.shm_limit = app_conf->shm_limit;
422 
423     ctx = nxt_unit_init(&java_init);
424     if (nxt_slow_path(ctx == NULL)) {
425         nxt_alert(task, "nxt_unit_init() failed");
426         return NXT_ERROR;
427     }
428 
429     rc = nxt_unit_run(ctx);
430     if (nxt_slow_path(rc != NXT_UNIT_OK)) {
431         /* TODO report error */
432     }
433 
434     nxt_java_stopContext(env, java_data.ctx);
435 
436     if ((*env)->ExceptionCheck(env)) {
437         (*env)->ExceptionDescribe(env);
438     }
439 
440     nxt_unit_done(ctx);
441 
442     (*jvm)->DestroyJavaVM(jvm);
443 
444     exit(0);
445 
446     return NXT_OK;
447 
448 env_failed:
449 
450     if ((*env)->ExceptionCheck(env)) {
451         (*env)->ExceptionDescribe(env);
452     }
453 
454     return NXT_ERROR;
455 }
456 
457 
458 static void
459 nxt_java_request_handler(nxt_unit_request_info_t *req)
460 {
461     JNIEnv                   *env;
462     jobject                  jreq, jresp;
463     nxt_java_data_t          *java_data;
464     nxt_java_request_data_t  *data;
465 
466     java_data = req->unit->data;
467     env = java_data->env;
468     data = req->data;
469 
470     jreq = nxt_java_newRequest(env, java_data->ctx, req);
471     if (jreq == NULL) {
472         nxt_unit_req_alert(req, "failed to create Request instance");
473 
474         if ((*env)->ExceptionCheck(env)) {
475             (*env)->ExceptionDescribe(env);
476             (*env)->ExceptionClear(env);
477         }
478 
479         nxt_unit_request_done(req, NXT_UNIT_ERROR);
480         return;
481     }
482 
483     jresp = nxt_java_newResponse(env, req);
484     if (jresp == NULL) {
485         nxt_unit_req_alert(req, "failed to create Response instance");
486 
487         if ((*env)->ExceptionCheck(env)) {
488             (*env)->ExceptionDescribe(env);
489             (*env)->ExceptionClear(env);
490         }
491 
492         (*env)->DeleteLocalRef(env, jreq);
493 
494         nxt_unit_request_done(req, NXT_UNIT_ERROR);
495         return;
496     }
497 
498     data->header_size = 10 * 1024;
499     data->buf_size = 32 * 1024; /* from Jetty */
500     data->jreq = jreq;
501     data->jresp = jresp;
502     data->buf = NULL;
503 
504     nxt_unit_request_group_dup_fields(req);
505 
506     nxt_java_service(env, java_data->ctx, jreq, jresp);
507 
508     if ((*env)->ExceptionCheck(env)) {
509         (*env)->ExceptionDescribe(env);
510         (*env)->ExceptionClear(env);
511     }
512 
513     if (!nxt_unit_response_is_init(req)) {
514         nxt_unit_response_init(req, 200, 0, 0);
515     }
516 
517     if (!nxt_unit_response_is_sent(req)) {
518         nxt_unit_response_send(req);
519     }
520 
521     if (data->buf != NULL) {
522         nxt_unit_buf_send(data->buf);
523 
524         data->buf = NULL;
525     }
526 
527     if (nxt_unit_response_is_websocket(req)) {
528         data->jreq = (*env)->NewGlobalRef(env, jreq);
529         data->jresp = (*env)->NewGlobalRef(env, jresp);
530 
531     } else {
532         nxt_unit_request_done(req, NXT_UNIT_OK);
533     }
534 
535     (*env)->DeleteLocalRef(env, jresp);
536     (*env)->DeleteLocalRef(env, jreq);
537 }
538 
539 
540 static void
541 nxt_java_websocket_handler(nxt_unit_websocket_frame_t *ws)
542 {
543     void                     *b;
544     JNIEnv                   *env;
545     jobject                  jbuf;
546     nxt_java_data_t          *java_data;
547     nxt_java_request_data_t  *data;
548 
549     java_data = ws->req->unit->data;
550     env = java_data->env;
551     data = ws->req->data;
552 
553     b = malloc(ws->payload_len);
554     if (b != NULL) {
555         nxt_unit_websocket_read(ws, b, ws->payload_len);
556 
557         jbuf = (*env)->NewDirectByteBuffer(env, b, ws->payload_len);
558         if (jbuf != NULL) {
559             nxt_java_Request_websocket(env, data->jreq, jbuf,
560                                        ws->header->opcode, ws->header->fin);
561 
562             if ((*env)->ExceptionCheck(env)) {
563                 (*env)->ExceptionDescribe(env);
564                 (*env)->ExceptionClear(env);
565             }
566 
567             (*env)->DeleteLocalRef(env, jbuf);
568         }
569 
570         free(b);
571     }
572 
573     nxt_unit_websocket_done(ws);
574 }
575 
576 
577 static void
578 nxt_java_close_handler(nxt_unit_request_info_t *req)
579 {
580     JNIEnv                   *env;
581     nxt_java_data_t          *java_data;
582     nxt_java_request_data_t  *data;
583 
584     java_data = req->unit->data;
585     env = java_data->env;
586     data = req->data;
587 
588     nxt_java_Request_close(env, data->jreq);
589 
590     (*env)->DeleteGlobalRef(env, data->jresp);
591     (*env)->DeleteGlobalRef(env, data->jreq);
592 
593     nxt_unit_request_done(req, NXT_UNIT_OK);
594 }
595 
596