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