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