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