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