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