1 /* 2 * Copyright (C) Alexander Borisov 3 * Copyright (C) NGINX, Inc. 4 */ 5 6 #include <ruby/nxt_ruby.h> 7 8 #include <nxt_unit.h> 9 #include <nxt_unit_request.h> 10 11 #include <ruby/thread.h> 12 13 #include NXT_RUBY_MOUNTS_H 14 15 #include <locale.h> 16 17 18 #define NXT_RUBY_RACK_API_VERSION_MAJOR 1 19 #define NXT_RUBY_RACK_API_VERSION_MINOR 3 20 21 22 typedef struct { 23 nxt_task_t *task; 24 nxt_str_t *script; 25 nxt_ruby_ctx_t *rctx; 26 } nxt_ruby_rack_init_t; 27 28 29 static nxt_int_t nxt_ruby_start(nxt_task_t *task, 30 nxt_process_data_t *data); 31 static VALUE nxt_ruby_init_basic(VALUE arg); 32 33 static VALUE nxt_ruby_hook_procs_load(VALUE path); 34 static VALUE nxt_ruby_hook_register(VALUE arg); 35 static VALUE nxt_ruby_hook_call(VALUE name); 36 37 static VALUE nxt_ruby_rack_init(nxt_ruby_rack_init_t *rack_init); 38 39 static VALUE nxt_ruby_require_rubygems(VALUE arg); 40 static VALUE nxt_ruby_bundler_setup(VALUE arg); 41 static VALUE nxt_ruby_require_rack(VALUE arg); 42 static VALUE nxt_ruby_rack_parse_script(VALUE ctx); 43 static VALUE nxt_ruby_rack_env_create(VALUE arg); 44 static int nxt_ruby_init_io(nxt_ruby_ctx_t *rctx); 45 static void nxt_ruby_request_handler(nxt_unit_request_info_t *req); 46 static void *nxt_ruby_request_handler_gvl(void *req); 47 static int nxt_ruby_ready_handler(nxt_unit_ctx_t *ctx); 48 static void *nxt_ruby_thread_create_gvl(void *rctx); 49 static VALUE nxt_ruby_thread_func(VALUE arg); 50 static void *nxt_ruby_unit_run(void *ctx); 51 static void nxt_ruby_ubf(void *ctx); 52 static int nxt_ruby_init_threads(nxt_ruby_app_conf_t *c); 53 static void nxt_ruby_join_threads(nxt_unit_ctx_t *ctx, 54 nxt_ruby_app_conf_t *c); 55 56 static VALUE nxt_ruby_rack_app_run(VALUE arg); 57 static int nxt_ruby_read_request(nxt_unit_request_info_t *req, VALUE hash_env); 58 nxt_inline void nxt_ruby_add_sptr(VALUE hash_env, VALUE name, 59 nxt_unit_sptr_t *sptr, uint32_t len); 60 static nxt_int_t nxt_ruby_rack_result_status(nxt_unit_request_info_t *req, 61 VALUE result); 62 static int nxt_ruby_rack_result_headers(nxt_unit_request_info_t *req, 63 VALUE result, nxt_int_t status); 64 static int nxt_ruby_hash_info(VALUE r_key, VALUE r_value, VALUE arg); 65 static int nxt_ruby_hash_add(VALUE r_key, VALUE r_value, VALUE arg); 66 static int nxt_ruby_rack_result_body(nxt_unit_request_info_t *req, 67 VALUE result); 68 static int nxt_ruby_rack_result_body_file_write(nxt_unit_request_info_t *req, 69 VALUE filepath); 70 static void *nxt_ruby_response_write_cb(void *read_info); 71 static VALUE nxt_ruby_rack_result_body_each(VALUE body, VALUE arg, 72 int argc, const VALUE *argv, VALUE blockarg); 73 static void *nxt_ruby_response_write(void *body); 74 75 static void nxt_ruby_exception_log(nxt_unit_request_info_t *req, 76 uint32_t level, const char *desc); 77 78 static void nxt_ruby_ctx_done(nxt_ruby_ctx_t *rctx); 79 static void nxt_ruby_atexit(void); 80 81 82 static uint32_t compat[] = { 83 NXT_VERNUM, NXT_DEBUG, 84 }; 85 86 static VALUE nxt_ruby_hook_procs; 87 static VALUE nxt_ruby_rackup; 88 static VALUE nxt_ruby_call; 89 90 static uint32_t nxt_ruby_threads; 91 static nxt_ruby_ctx_t *nxt_ruby_ctxs; 92 93 NXT_EXPORT nxt_app_module_t nxt_app_module = { 94 sizeof(compat), 95 compat, 96 nxt_string("ruby"), 97 ruby_version, 98 nxt_ruby_mounts, 99 nxt_nitems(nxt_ruby_mounts), 100 NULL, 101 nxt_ruby_start, 102 }; 103 104 typedef struct { 105 nxt_str_t string; 106 VALUE *v; 107 } nxt_ruby_string_t; 108 109 static VALUE nxt_rb_80_str; 110 static VALUE nxt_rb_content_length_str; 111 static VALUE nxt_rb_content_type_str; 112 static VALUE nxt_rb_http_str; 113 static VALUE nxt_rb_https_str; 114 static VALUE nxt_rb_path_info_str; 115 static VALUE nxt_rb_query_string_str; 116 static VALUE nxt_rb_rack_url_scheme_str; 117 static VALUE nxt_rb_remote_addr_str; 118 static VALUE nxt_rb_request_method_str; 119 static VALUE nxt_rb_request_uri_str; 120 static VALUE nxt_rb_server_addr_str; 121 static VALUE nxt_rb_server_name_str; 122 static VALUE nxt_rb_server_port_str; 123 static VALUE nxt_rb_server_protocol_str; 124 static VALUE nxt_rb_on_worker_boot; 125 static VALUE nxt_rb_on_worker_shutdown; 126 static VALUE nxt_rb_on_thread_boot; 127 static VALUE nxt_rb_on_thread_shutdown; 128 129 static nxt_ruby_string_t nxt_rb_strings[] = { 130 { nxt_string("80"), &nxt_rb_80_str }, 131 { nxt_string("CONTENT_LENGTH"), &nxt_rb_content_length_str }, 132 { nxt_string("CONTENT_TYPE"), &nxt_rb_content_type_str }, 133 { nxt_string("http"), &nxt_rb_http_str }, 134 { nxt_string("https"), &nxt_rb_https_str }, 135 { nxt_string("PATH_INFO"), &nxt_rb_path_info_str }, 136 { nxt_string("QUERY_STRING"), &nxt_rb_query_string_str }, 137 { nxt_string("rack.url_scheme"), &nxt_rb_rack_url_scheme_str }, 138 { nxt_string("REMOTE_ADDR"), &nxt_rb_remote_addr_str }, 139 { nxt_string("REQUEST_METHOD"), &nxt_rb_request_method_str }, 140 { nxt_string("REQUEST_URI"), &nxt_rb_request_uri_str }, 141 { nxt_string("SERVER_ADDR"), &nxt_rb_server_addr_str }, 142 { nxt_string("SERVER_NAME"), &nxt_rb_server_name_str }, 143 { nxt_string("SERVER_PORT"), &nxt_rb_server_port_str }, 144 { nxt_string("SERVER_PROTOCOL"), &nxt_rb_server_protocol_str }, 145 { nxt_string("on_worker_boot"), &nxt_rb_on_worker_boot }, 146 { nxt_string("on_worker_shutdown"), &nxt_rb_on_worker_shutdown }, 147 { nxt_string("on_thread_boot"), &nxt_rb_on_thread_boot }, 148 { nxt_string("on_thread_shutdown"), &nxt_rb_on_thread_shutdown }, 149 { nxt_null_string, NULL }, 150 }; 151 152 153 static int 154 nxt_ruby_init_strings(void) 155 { 156 VALUE v; 157 nxt_ruby_string_t *pstr; 158 159 pstr = nxt_rb_strings; 160 161 while (pstr->string.start != NULL) { 162 v = rb_str_new_static((char *) pstr->string.start, pstr->string.length); 163 164 if (nxt_slow_path(v == Qnil)) { 165 nxt_unit_alert(NULL, "Ruby: failed to create const string '%.*s'", 166 (int) pstr->string.length, 167 (char *) pstr->string.start); 168 169 return NXT_UNIT_ERROR; 170 } 171 172 *pstr->v = v; 173 174 rb_gc_register_address(pstr->v); 175 176 pstr++; 177 } 178 179 return NXT_UNIT_OK; 180 } 181 182 183 static void 184 nxt_ruby_done_strings(void) 185 { 186 nxt_ruby_string_t *pstr; 187 188 pstr = nxt_rb_strings; 189 190 while (pstr->string.start != NULL) { 191 rb_gc_unregister_address(pstr->v); 192 193 *pstr->v = Qnil; 194 195 pstr++; 196 } 197 } 198 199 200 static VALUE 201 nxt_ruby_hook_procs_load(VALUE path) 202 { 203 VALUE module, file, file_obj; 204 205 module = rb_define_module("Unit"); 206 207 nxt_ruby_hook_procs = rb_hash_new(); 208 209 rb_gc_register_address(&nxt_ruby_hook_procs); 210 211 rb_define_module_function(module, "on_worker_boot", 212 &nxt_ruby_hook_register, 0); 213 rb_define_module_function(module, "on_worker_shutdown", 214 &nxt_ruby_hook_register, 0); 215 rb_define_module_function(module, "on_thread_boot", 216 &nxt_ruby_hook_register, 0); 217 rb_define_module_function(module, "on_thread_shutdown", 218 &nxt_ruby_hook_register, 0); 219 220 file = rb_const_get(rb_cObject, rb_intern("File")); 221 file_obj = rb_funcall(file, rb_intern("read"), 1, path); 222 223 return rb_funcall(module, rb_intern("module_eval"), 3, file_obj, path, 224 INT2NUM(1)); 225 } 226 227 228 static VALUE 229 nxt_ruby_hook_register(VALUE arg) 230 { 231 VALUE kernel, callee, callee_str; 232 233 rb_need_block(); 234 235 kernel = rb_const_get(rb_cObject, rb_intern("Kernel")); 236 callee = rb_funcall(kernel, rb_intern("__callee__"), 0); 237 callee_str = rb_funcall(callee, rb_intern("to_s"), 0); 238 239 rb_hash_aset(nxt_ruby_hook_procs, callee_str, rb_block_proc()); 240 241 return Qnil; 242 } 243 244 245 static VALUE 246 nxt_ruby_hook_call(VALUE name) 247 { 248 VALUE proc; 249 250 proc = rb_hash_lookup(nxt_ruby_hook_procs, name); 251 if (proc == Qnil) { 252 return Qnil; 253 } 254 255 return rb_funcall(proc, rb_intern("call"), 0); 256 } 257 258 259 static nxt_int_t 260 nxt_ruby_start(nxt_task_t *task, nxt_process_data_t *data) 261 { 262 int state, rc; 263 VALUE res, path; 264 nxt_ruby_ctx_t ruby_ctx; 265 nxt_unit_ctx_t *unit_ctx; 266 nxt_unit_init_t ruby_unit_init; 267 nxt_ruby_app_conf_t *c; 268 nxt_ruby_rack_init_t rack_init; 269 nxt_common_app_conf_t *conf; 270 271 static char *argv[2] = { (char *) "NGINX_Unit", (char *) "-e0" }; 272 273 conf = data->app; 274 c = &conf->u.ruby; 275 276 nxt_ruby_threads = c->threads; 277 278 setlocale(LC_CTYPE, ""); 279 280 RUBY_INIT_STACK 281 ruby_init(); 282 ruby_options(2, argv); 283 ruby_script("NGINX_Unit"); 284 285 ruby_ctx.env = Qnil; 286 ruby_ctx.io_input = Qnil; 287 ruby_ctx.io_error = Qnil; 288 ruby_ctx.thread = Qnil; 289 ruby_ctx.ctx = NULL; 290 ruby_ctx.req = NULL; 291 292 rack_init.task = task; 293 rack_init.script = &c->script; 294 rack_init.rctx = &ruby_ctx; 295 296 nxt_ruby_init_strings(); 297 298 res = rb_protect(nxt_ruby_init_basic, 299 (VALUE) (uintptr_t) &rack_init, &state); 300 if (nxt_slow_path(res == Qnil || state != 0)) { 301 nxt_ruby_exception_log(NULL, NXT_LOG_ALERT, 302 "Failed to init basic variables"); 303 return NXT_ERROR; 304 } 305 306 nxt_ruby_call = Qnil; 307 nxt_ruby_hook_procs = Qnil; 308 309 if (c->hooks.start != NULL) { 310 path = rb_str_new((const char *) c->hooks.start, 311 (long) c->hooks.length); 312 313 rb_protect(nxt_ruby_hook_procs_load, path, &state); 314 rb_str_free(path); 315 if (nxt_slow_path(state != 0)) { 316 nxt_ruby_exception_log(NULL, NXT_LOG_ALERT, 317 "Failed to setup hooks"); 318 return NXT_ERROR; 319 } 320 } 321 322 if (nxt_ruby_hook_procs != Qnil) { 323 rb_protect(nxt_ruby_hook_call, nxt_rb_on_worker_boot, &state); 324 if (nxt_slow_path(state != 0)) { 325 nxt_ruby_exception_log(NULL, NXT_LOG_ERR, 326 "Failed to call on_worker_boot()"); 327 return NXT_ERROR; 328 } 329 } 330 331 nxt_ruby_rackup = nxt_ruby_rack_init(&rack_init); 332 if (nxt_slow_path(nxt_ruby_rackup == Qnil)) { 333 return NXT_ERROR; 334 } 335 336 rb_gc_register_address(&nxt_ruby_rackup); 337 338 nxt_ruby_call = rb_intern("call"); 339 if (nxt_slow_path(nxt_ruby_call == Qnil)) { 340 nxt_alert(task, "Ruby: Unable to find rack entry point"); 341 342 goto fail; 343 } 344 345 rb_gc_register_address(&nxt_ruby_call); 346 347 ruby_ctx.env = rb_protect(nxt_ruby_rack_env_create, 348 (VALUE) (uintptr_t) &ruby_ctx, &state); 349 if (nxt_slow_path(ruby_ctx.env == Qnil || state != 0)) { 350 nxt_ruby_exception_log(NULL, NXT_LOG_ALERT, 351 "Failed to create 'environ' variable"); 352 goto fail; 353 } 354 355 rc = nxt_ruby_init_threads(c); 356 if (nxt_slow_path(rc == NXT_UNIT_ERROR)) { 357 goto fail; 358 } 359 360 nxt_unit_default_init(task, &ruby_unit_init, conf); 361 362 ruby_unit_init.callbacks.request_handler = nxt_ruby_request_handler; 363 ruby_unit_init.callbacks.ready_handler = nxt_ruby_ready_handler; 364 ruby_unit_init.data = c; 365 ruby_unit_init.ctx_data = &ruby_ctx; 366 367 unit_ctx = nxt_unit_init(&ruby_unit_init); 368 if (nxt_slow_path(unit_ctx == NULL)) { 369 goto fail; 370 } 371 372 if (nxt_ruby_hook_procs != Qnil) { 373 rb_protect(nxt_ruby_hook_call, nxt_rb_on_thread_boot, &state); 374 if (nxt_slow_path(state != 0)) { 375 nxt_ruby_exception_log(NULL, NXT_LOG_ERR, 376 "Failed to call on_thread_boot()"); 377 } 378 } 379 380 rc = (intptr_t) rb_thread_call_without_gvl(nxt_ruby_unit_run, unit_ctx, 381 nxt_ruby_ubf, unit_ctx); 382 383 if (nxt_ruby_hook_procs != Qnil) { 384 rb_protect(nxt_ruby_hook_call, nxt_rb_on_thread_shutdown, &state); 385 if (nxt_slow_path(state != 0)) { 386 nxt_ruby_exception_log(NULL, NXT_LOG_ERR, 387 "Failed to call on_thread_shutdown()"); 388 } 389 } 390 391 nxt_ruby_join_threads(unit_ctx, c); 392 393 if (nxt_ruby_hook_procs != Qnil) { 394 rb_protect(nxt_ruby_hook_call, nxt_rb_on_worker_shutdown, &state); 395 if (nxt_slow_path(state != 0)) { 396 nxt_ruby_exception_log(NULL, NXT_LOG_ERR, 397 "Failed to call on_worker_shutdown()"); 398 } 399 } 400 401 nxt_unit_done(unit_ctx); 402 403 nxt_ruby_ctx_done(&ruby_ctx); 404 405 nxt_ruby_atexit(); 406 407 exit(rc); 408 409 return NXT_OK; 410 411 fail: 412 413 nxt_ruby_join_threads(NULL, c); 414 415 nxt_ruby_ctx_done(&ruby_ctx); 416 417 nxt_ruby_atexit(); 418 419 return NXT_ERROR; 420 } 421 422 423 static VALUE 424 nxt_ruby_init_basic(VALUE arg) 425 { 426 int state; 427 nxt_ruby_rack_init_t *rack_init; 428 429 rack_init = (nxt_ruby_rack_init_t *) (uintptr_t) arg; 430 431 state = rb_enc_find_index("encdb"); 432 if (nxt_slow_path(state == 0)) { 433 nxt_alert(rack_init->task, 434 "Ruby: Failed to find encoding index 'encdb'"); 435 436 return Qnil; 437 } 438 439 rb_funcall(rb_cObject, rb_intern("require"), 1, 440 rb_str_new2("enc/trans/transdb")); 441 442 return arg; 443 } 444 445 446 static VALUE 447 nxt_ruby_rack_init(nxt_ruby_rack_init_t *rack_init) 448 { 449 int state; 450 VALUE rackup, err; 451 452 rb_protect(nxt_ruby_require_rubygems, Qnil, &state); 453 if (nxt_slow_path(state != 0)) { 454 nxt_ruby_exception_log(NULL, NXT_LOG_ALERT, 455 "Failed to require 'rubygems' package"); 456 return Qnil; 457 } 458 459 rb_protect(nxt_ruby_bundler_setup, Qnil, &state); 460 if (state != 0) { 461 err = rb_errinfo(); 462 463 if (rb_obj_is_kind_of(err, rb_eLoadError) == Qfalse) { 464 nxt_ruby_exception_log(NULL, NXT_LOG_ALERT, 465 "Failed to require 'bundler/setup' package"); 466 return Qnil; 467 } 468 469 rb_set_errinfo(Qnil); 470 } 471 472 rb_protect(nxt_ruby_require_rack, Qnil, &state); 473 if (nxt_slow_path(state != 0)) { 474 nxt_ruby_exception_log(NULL, NXT_LOG_ALERT, 475 "Failed to require 'rack' package"); 476 return Qnil; 477 } 478 479 rackup = rb_protect(nxt_ruby_rack_parse_script, 480 (VALUE) (uintptr_t) rack_init, &state); 481 if (nxt_slow_path(TYPE(rackup) != T_ARRAY || state != 0)) { 482 nxt_ruby_exception_log(NULL, NXT_LOG_ALERT, 483 "Failed to parse rack script"); 484 return Qnil; 485 } 486 487 if (nxt_slow_path(RARRAY_LEN(rackup) < 1)) { 488 nxt_alert(rack_init->task, "Ruby: Invalid rack config file"); 489 return Qnil; 490 } 491 492 return RARRAY_PTR(rackup)[0]; 493 } 494 495 496 static VALUE 497 nxt_ruby_require_rubygems(VALUE arg) 498 { 499 return rb_funcall(rb_cObject, rb_intern("require"), 1, 500 rb_str_new2("rubygems")); 501 } 502 503 504 static VALUE 505 nxt_ruby_bundler_setup(VALUE arg) 506 { 507 return rb_funcall(rb_cObject, rb_intern("require"), 1, 508 rb_str_new2("bundler/setup")); 509 } 510 511 512 static VALUE 513 nxt_ruby_require_rack(VALUE arg) 514 { 515 return rb_funcall(rb_cObject, rb_intern("require"), 1, rb_str_new2("rack")); 516 } 517 518 519 static VALUE 520 nxt_ruby_rack_parse_script(VALUE ctx) 521 { 522 VALUE script, res, rack, builder; 523 nxt_ruby_rack_init_t *rack_init; 524 525 rack_init = (nxt_ruby_rack_init_t *) (uintptr_t) ctx; 526 527 rack = rb_const_get(rb_cObject, rb_intern("Rack")); 528 builder = rb_const_get(rack, rb_intern("Builder")); 529 530 script = rb_str_new((const char *) rack_init->script->start, 531 (long) rack_init->script->length); 532 533 res = rb_funcall(builder, rb_intern("parse_file"), 1, script); 534 535 rb_str_free(script); 536 537 return res; 538 } 539 540 541 static VALUE 542 nxt_ruby_rack_env_create(VALUE arg) 543 { 544 int rc; 545 VALUE hash_env, version; 546 nxt_ruby_ctx_t *rctx; 547 548 rctx = (nxt_ruby_ctx_t *) (uintptr_t) arg; 549 550 rc = nxt_ruby_init_io(rctx); 551 if (nxt_slow_path(rc != NXT_UNIT_OK)) { 552 return Qnil; 553 } 554 555 hash_env = rb_hash_new(); 556 557 rb_hash_aset(hash_env, rb_str_new2("SERVER_SOFTWARE"), 558 rb_str_new((const char *) nxt_server.start, 559 (long) nxt_server.length)); 560 561 version = rb_ary_new(); 562 563 rb_ary_push(version, UINT2NUM(NXT_RUBY_RACK_API_VERSION_MAJOR)); 564 rb_ary_push(version, UINT2NUM(NXT_RUBY_RACK_API_VERSION_MINOR)); 565 566 rb_hash_aset(hash_env, rb_str_new2("rack.version"), version); 567 rb_hash_aset(hash_env, rb_str_new2("rack.input"), rctx->io_input); 568 rb_hash_aset(hash_env, rb_str_new2("rack.errors"), rctx->io_error); 569 rb_hash_aset(hash_env, rb_str_new2("rack.multithread"), 570 nxt_ruby_threads > 1 ? Qtrue : Qfalse); 571 rb_hash_aset(hash_env, rb_str_new2("rack.multiprocess"), Qtrue); 572 rb_hash_aset(hash_env, rb_str_new2("rack.run_once"), Qfalse); 573 rb_hash_aset(hash_env, rb_str_new2("rack.hijack?"), Qfalse); 574 rb_hash_aset(hash_env, rb_str_new2("rack.hijack"), Qnil); 575 rb_hash_aset(hash_env, rb_str_new2("rack.hijack_io"), Qnil); 576 577 rctx->env = hash_env; 578 579 rb_gc_register_address(&rctx->env); 580 581 return hash_env; 582 } 583 584 585 static int 586 nxt_ruby_init_io(nxt_ruby_ctx_t *rctx) 587 { 588 VALUE io_input, io_error; 589 590 io_input = nxt_ruby_stream_io_input_init(); 591 592 rctx->io_input = rb_funcall(io_input, rb_intern("new"), 1, 593 (VALUE) (uintptr_t) rctx); 594 if (nxt_slow_path(rctx->io_input == Qnil)) { 595 nxt_unit_alert(NULL, 596 "Ruby: Failed to create environment 'rack.input' var"); 597 598 return NXT_UNIT_ERROR; 599 } 600 601 rb_gc_register_address(&rctx->io_input); 602 603 io_error = nxt_ruby_stream_io_error_init(); 604 605 rctx->io_error = rb_funcall(io_error, rb_intern("new"), 1, 606 (VALUE) (uintptr_t) rctx); 607 if (nxt_slow_path(rctx->io_error == Qnil)) { 608 nxt_unit_alert(NULL, 609 "Ruby: Failed to create environment 'rack.error' var"); 610 611 return NXT_UNIT_ERROR; 612 } 613 614 rb_gc_register_address(&rctx->io_error); 615 616 return NXT_UNIT_OK; 617 } 618 619 620 static void 621 nxt_ruby_request_handler(nxt_unit_request_info_t *req) 622 { 623 (void) rb_thread_call_with_gvl(nxt_ruby_request_handler_gvl, req); 624 } 625 626 627 static void * 628 nxt_ruby_request_handler_gvl(void *data) 629 { 630 int state; 631 VALUE res; 632 nxt_ruby_ctx_t *rctx; 633 nxt_unit_request_info_t *req; 634 635 req = data; 636 637 rctx = req->ctx->data; 638 rctx->req = req; 639 640 res = rb_protect(nxt_ruby_rack_app_run, (VALUE) (uintptr_t) req, &state); 641 if (nxt_slow_path(res == Qnil || state != 0)) { 642 nxt_ruby_exception_log(req, NXT_LOG_ERR, 643 "Failed to run ruby script"); 644 645 nxt_unit_request_done(req, NXT_UNIT_ERROR); 646 647 } else { 648 nxt_unit_request_done(req, NXT_UNIT_OK); 649 } 650 651 rctx->req = NULL; 652 653 return NULL; 654 } 655 656 657 static VALUE 658 nxt_ruby_rack_app_run(VALUE arg) 659 { 660 int rc; 661 VALUE env, result; 662 nxt_int_t status; 663 nxt_ruby_ctx_t *rctx; 664 nxt_unit_request_info_t *req; 665 666 req = (nxt_unit_request_info_t *) arg; 667 668 rctx = req->ctx->data; 669 670 env = rb_hash_dup(rctx->env); 671 672 rc = nxt_ruby_read_request(req, env); 673 if (nxt_slow_path(rc != NXT_UNIT_OK)) { 674 nxt_unit_req_alert(req, 675 "Ruby: Failed to process incoming request"); 676 677 goto fail; 678 } 679 680 result = rb_funcall(nxt_ruby_rackup, nxt_ruby_call, 1, env); 681 if (nxt_slow_path(TYPE(result) != T_ARRAY)) { 682 nxt_unit_req_error(req, 683 "Ruby: Invalid response format from application"); 684 685 goto fail; 686 } 687 688 if (nxt_slow_path(RARRAY_LEN(result) != 3)) { 689 nxt_unit_req_error(req, 690 "Ruby: Invalid response format from application. " 691 "Need 3 entries [Status, Headers, Body]"); 692 693 goto fail; 694 } 695 696 status = nxt_ruby_rack_result_status(req, result); 697 if (nxt_slow_path(status < 0)) { 698 nxt_unit_req_error(req, 699 "Ruby: Invalid response status from application."); 700 701 goto fail; 702 } 703 704 rc = nxt_ruby_rack_result_headers(req, result, status); 705 if (nxt_slow_path(rc != NXT_UNIT_OK)) { 706 goto fail; 707 } 708 709 rc = nxt_ruby_rack_result_body(req, result); 710 if (nxt_slow_path(rc != NXT_UNIT_OK)) { 711 goto fail; 712 } 713 714 rb_hash_delete(env, rb_obj_id(env)); 715 716 return result; 717 718 fail: 719 720 rb_hash_delete(env, rb_obj_id(env)); 721 722 return Qnil; 723 } 724 725 726 static int 727 nxt_ruby_read_request(nxt_unit_request_info_t *req, VALUE hash_env) 728 { 729 VALUE name; 730 uint32_t i; 731 nxt_unit_field_t *f; 732 nxt_unit_request_t *r; 733 734 r = req->request; 735 736 nxt_ruby_add_sptr(hash_env, nxt_rb_request_method_str, &r->method, 737 r->method_length); 738 nxt_ruby_add_sptr(hash_env, nxt_rb_request_uri_str, &r->target, 739 r->target_length); 740 nxt_ruby_add_sptr(hash_env, nxt_rb_path_info_str, &r->path, r->path_length); 741 nxt_ruby_add_sptr(hash_env, nxt_rb_query_string_str, &r->query, 742 r->query_length); 743 nxt_ruby_add_sptr(hash_env, nxt_rb_server_protocol_str, &r->version, 744 r->version_length); 745 nxt_ruby_add_sptr(hash_env, nxt_rb_remote_addr_str, &r->remote, 746 r->remote_length); 747 nxt_ruby_add_sptr(hash_env, nxt_rb_server_addr_str, &r->local, 748 r->local_length); 749 nxt_ruby_add_sptr(hash_env, nxt_rb_server_name_str, &r->server_name, 750 r->server_name_length); 751 752 rb_hash_aset(hash_env, nxt_rb_server_port_str, nxt_rb_80_str); 753 754 rb_hash_aset(hash_env, nxt_rb_rack_url_scheme_str, 755 r->tls ? nxt_rb_https_str : nxt_rb_http_str); 756 757 for (i = 0; i < r->fields_count; i++) { 758 f = r->fields + i; 759 760 name = rb_str_new(nxt_unit_sptr_get(&f->name), f->name_length); 761 762 nxt_ruby_add_sptr(hash_env, name, &f->value, f->value_length); 763 } 764 765 if (r->content_length_field != NXT_UNIT_NONE_FIELD) { 766 f = r->fields + r->content_length_field; 767 768 nxt_ruby_add_sptr(hash_env, nxt_rb_content_length_str, 769 &f->value, f->value_length); 770 } 771 772 if (r->content_type_field != NXT_UNIT_NONE_FIELD) { 773 f = r->fields + r->content_type_field; 774 775 nxt_ruby_add_sptr(hash_env, nxt_rb_content_type_str, 776 &f->value, f->value_length); 777 } 778 779 return NXT_UNIT_OK; 780 } 781 782 783 nxt_inline void 784 nxt_ruby_add_sptr(VALUE hash_env, VALUE name, 785 nxt_unit_sptr_t *sptr, uint32_t len) 786 { 787 char *str; 788 789 str = nxt_unit_sptr_get(sptr); 790 791 rb_hash_aset(hash_env, name, rb_str_new(str, len)); 792 } 793 794 795 static nxt_int_t 796 nxt_ruby_rack_result_status(nxt_unit_request_info_t *req, VALUE result) 797 { 798 VALUE status; 799 800 status = rb_ary_entry(result, 0); 801 802 if (TYPE(status) == T_FIXNUM) { 803 return FIX2INT(status); 804 } 805 806 if (TYPE(status) == T_STRING) { 807 return nxt_int_parse((u_char *) RSTRING_PTR(status), 808 RSTRING_LEN(status)); 809 } 810 811 nxt_unit_req_error(req, "Ruby: Invalid response 'status' " 812 "format from application"); 813 814 return -2; 815 } 816 817 818 typedef struct { 819 int rc; 820 uint32_t fields; 821 uint32_t size; 822 nxt_unit_request_info_t *req; 823 } nxt_ruby_headers_info_t; 824 825 826 static int 827 nxt_ruby_rack_result_headers(nxt_unit_request_info_t *req, VALUE result, 828 nxt_int_t status) 829 { 830 int rc; 831 VALUE headers; 832 nxt_ruby_headers_info_t headers_info; 833 834 headers = rb_ary_entry(result, 1); 835 if (nxt_slow_path(TYPE(headers) != T_HASH)) { 836 nxt_unit_req_error(req, 837 "Ruby: Invalid response 'headers' format from " 838 "application"); 839 840 return NXT_UNIT_ERROR; 841 } 842 843 rc = NXT_UNIT_OK; 844 845 headers_info.rc = NXT_UNIT_OK; 846 headers_info.fields = 0; 847 headers_info.size = 0; 848 headers_info.req = req; 849 850 rb_hash_foreach(headers, nxt_ruby_hash_info, 851 (VALUE) (uintptr_t) &headers_info); 852 if (nxt_slow_path(headers_info.rc != NXT_UNIT_OK)) { 853 return headers_info.rc; 854 } 855 856 rc = nxt_unit_response_init(req, status, 857 headers_info.fields, headers_info.size); 858 if (nxt_slow_path(rc != NXT_UNIT_OK)) { 859 return rc; 860 } 861 862 rb_hash_foreach(headers, nxt_ruby_hash_add, 863 (VALUE) (uintptr_t) &headers_info); 864 865 return rc; 866 } 867 868 869 static int 870 nxt_ruby_hash_info(VALUE r_key, VALUE r_value, VALUE arg) 871 { 872 const char *value, *value_end, *pos; 873 nxt_ruby_headers_info_t *headers_info; 874 875 headers_info = (void *) (uintptr_t) arg; 876 877 if (nxt_slow_path(TYPE(r_key) != T_STRING)) { 878 nxt_unit_req_error(headers_info->req, 879 "Ruby: Wrong header entry 'key' from application"); 880 881 goto fail; 882 } 883 884 if (nxt_slow_path(TYPE(r_value) != T_STRING)) { 885 nxt_unit_req_error(headers_info->req, 886 "Ruby: Wrong header entry 'value' from application"); 887 888 goto fail; 889 } 890 891 value = RSTRING_PTR(r_value); 892 value_end = value + RSTRING_LEN(r_value); 893 894 pos = value; 895 896 for ( ;; ) { 897 pos = strchr(pos, '\n'); 898 899 if (pos == NULL) { 900 break; 901 } 902 903 headers_info->fields++; 904 headers_info->size += RSTRING_LEN(r_key) + (pos - value); 905 906 pos++; 907 value = pos; 908 } 909 910 if (value <= value_end) { 911 headers_info->fields++; 912 headers_info->size += RSTRING_LEN(r_key) + (value_end - value); 913 } 914 915 return ST_CONTINUE; 916 917 fail: 918 919 headers_info->rc = NXT_UNIT_ERROR; 920 921 return ST_STOP; 922 } 923 924 925 static int 926 nxt_ruby_hash_add(VALUE r_key, VALUE r_value, VALUE arg) 927 { 928 int *rc; 929 uint32_t key_len; 930 const char *value, *value_end, *pos; 931 nxt_ruby_headers_info_t *headers_info; 932 933 headers_info = (void *) (uintptr_t) arg; 934 rc = &headers_info->rc; 935 936 value = RSTRING_PTR(r_value); 937 value_end = value + RSTRING_LEN(r_value); 938 939 key_len = RSTRING_LEN(r_key); 940 941 pos = value; 942 943 for ( ;; ) { 944 pos = strchr(pos, '\n'); 945 946 if (pos == NULL) { 947 break; 948 } 949 950 *rc = nxt_unit_response_add_field(headers_info->req, 951 RSTRING_PTR(r_key), key_len, 952 value, pos - value); 953 if (nxt_slow_path(*rc != NXT_UNIT_OK)) { 954 goto fail; 955 } 956 957 pos++; 958 value = pos; 959 } 960 961 if (value <= value_end) { 962 *rc = nxt_unit_response_add_field(headers_info->req, 963 RSTRING_PTR(r_key), key_len, 964 value, value_end - value); 965 if (nxt_slow_path(*rc != NXT_UNIT_OK)) { 966 goto fail; 967 } 968 } 969 970 return ST_CONTINUE; 971 972 fail: 973 974 *rc = NXT_UNIT_ERROR; 975 976 return ST_STOP; 977 } 978 979 980 static int 981 nxt_ruby_rack_result_body(nxt_unit_request_info_t *req, VALUE result) 982 { 983 int rc; 984 VALUE fn, body; 985 986 body = rb_ary_entry(result, 2); 987 988 if (rb_respond_to(body, rb_intern("to_path"))) { 989 990 fn = rb_funcall(body, rb_intern("to_path"), 0); 991 if (nxt_slow_path(TYPE(fn) != T_STRING)) { 992 nxt_unit_req_error(req, 993 "Ruby: Failed to get 'body' file path from " 994 "application"); 995 996 return NXT_UNIT_ERROR; 997 } 998 999 rc = nxt_ruby_rack_result_body_file_write(req, fn); 1000 if (nxt_slow_path(rc != NXT_UNIT_OK)) { 1001 return rc; 1002 } 1003 1004 } else if (rb_respond_to(body, rb_intern("each"))) { 1005 rb_block_call(body, rb_intern("each"), 0, 0, 1006 nxt_ruby_rack_result_body_each, (VALUE) (uintptr_t) req); 1007 1008 } else { 1009 nxt_unit_req_error(req, 1010 "Ruby: Invalid response 'body' format " 1011 "from application"); 1012 1013 return NXT_UNIT_ERROR; 1014 } 1015 1016 if (rb_respond_to(body, rb_intern("close"))) { 1017 rb_funcall(body, rb_intern("close"), 0); 1018 } 1019 1020 return NXT_UNIT_OK; 1021 } 1022 1023 1024 typedef struct { 1025 int fd; 1026 off_t pos; 1027 off_t rest; 1028 } nxt_ruby_rack_file_t; 1029 1030 1031 static ssize_t 1032 nxt_ruby_rack_file_read(nxt_unit_read_info_t *read_info, void *dst, size_t size) 1033 { 1034 ssize_t res; 1035 nxt_ruby_rack_file_t *file; 1036 1037 file = read_info->data; 1038 1039 size = nxt_min(size, (size_t) file->rest); 1040 1041 res = pread(file->fd, dst, size, file->pos); 1042 1043 if (res >= 0) { 1044 file->pos += res; 1045 file->rest -= res; 1046 1047 if (size > (size_t) res) { 1048 file->rest = 0; 1049 } 1050 } 1051 1052 read_info->eof = file->rest == 0; 1053 1054 return res; 1055 } 1056 1057 1058 typedef struct { 1059 nxt_unit_read_info_t read_info; 1060 nxt_unit_request_info_t *req; 1061 } nxt_ruby_read_info_t; 1062 1063 1064 static int 1065 nxt_ruby_rack_result_body_file_write(nxt_unit_request_info_t *req, 1066 VALUE filepath) 1067 { 1068 int fd, rc; 1069 struct stat finfo; 1070 nxt_ruby_rack_file_t ruby_file; 1071 nxt_ruby_read_info_t ri; 1072 1073 fd = open(RSTRING_PTR(filepath), O_RDONLY, 0); 1074 if (nxt_slow_path(fd == -1)) { 1075 nxt_unit_req_error(req, 1076 "Ruby: Failed to open content file \"%s\": %s (%d)", 1077 RSTRING_PTR(filepath), strerror(errno), errno); 1078 1079 return NXT_UNIT_ERROR; 1080 } 1081 1082 rc = fstat(fd, &finfo); 1083 if (nxt_slow_path(rc == -1)) { 1084 nxt_unit_req_error(req, 1085 "Ruby: Content file fstat(\"%s\") failed: %s (%d)", 1086 RSTRING_PTR(filepath), strerror(errno), errno); 1087 1088 close(fd); 1089 1090 return NXT_UNIT_ERROR; 1091 } 1092 1093 ruby_file.fd = fd; 1094 ruby_file.pos = 0; 1095 ruby_file.rest = finfo.st_size; 1096 1097 ri.read_info.read = nxt_ruby_rack_file_read; 1098 ri.read_info.eof = ruby_file.rest == 0; 1099 ri.read_info.buf_size = ruby_file.rest; 1100 ri.read_info.data = &ruby_file; 1101 ri.req = req; 1102 1103 rc = (intptr_t) rb_thread_call_without_gvl(nxt_ruby_response_write_cb, 1104 &ri, 1105 nxt_ruby_ubf, 1106 req->ctx); 1107 1108 close(fd); 1109 1110 return rc; 1111 } 1112 1113 1114 static void * 1115 nxt_ruby_response_write_cb(void *data) 1116 { 1117 int rc; 1118 nxt_ruby_read_info_t *ri; 1119 1120 ri = data; 1121 1122 rc = nxt_unit_response_write_cb(ri->req, &ri->read_info); 1123 if (nxt_slow_path(rc != NXT_UNIT_OK)) { 1124 nxt_unit_req_error(ri->req, "Ruby: Failed to write content file."); 1125 } 1126 1127 return (void *) (intptr_t) rc; 1128 } 1129 1130 1131 typedef struct { 1132 VALUE body; 1133 nxt_unit_request_info_t *req; 1134 } nxt_ruby_write_info_t; 1135 1136 1137 static VALUE 1138 nxt_ruby_rack_result_body_each(VALUE body, VALUE arg, int argc, 1139 const VALUE *argv, VALUE blockarg) 1140 { 1141 nxt_ruby_write_info_t wi; 1142 1143 if (TYPE(body) != T_STRING) { 1144 return Qnil; 1145 } 1146 1147 wi.body = body; 1148 wi.req = (void *) (uintptr_t) arg; 1149 1150 (void) rb_thread_call_without_gvl(nxt_ruby_response_write, 1151 (void *) (uintptr_t) &wi, 1152 nxt_ruby_ubf, wi.req->ctx); 1153 1154 return Qnil; 1155 } 1156 1157 1158 static void * 1159 nxt_ruby_response_write(void *data) 1160 { 1161 int rc; 1162 nxt_ruby_write_info_t *wi; 1163 1164 wi = data; 1165 1166 rc = nxt_unit_response_write(wi->req, RSTRING_PTR(wi->body), 1167 RSTRING_LEN(wi->body)); 1168 if (nxt_slow_path(rc != NXT_UNIT_OK)) { 1169 nxt_unit_req_error(wi->req, 1170 "Ruby: Failed to write 'body' from application"); 1171 } 1172 1173 return (void *) (intptr_t) rc; 1174 } 1175 1176 1177 static void 1178 nxt_ruby_exception_log(nxt_unit_request_info_t *req, uint32_t level, 1179 const char *desc) 1180 { 1181 int i; 1182 VALUE err, ary, eclass, msg; 1183 1184 nxt_unit_req_log(req, level, "Ruby: %s", desc); 1185 1186 err = rb_errinfo(); 1187 if (nxt_slow_path(err == Qnil)) { 1188 return; 1189 } 1190 1191 eclass = rb_class_name(rb_class_of(err)); 1192 1193 msg = rb_funcall(err, rb_intern("message"), 0); 1194 ary = rb_funcall(err, rb_intern("backtrace"), 0); 1195 1196 if (RARRAY_LEN(ary) == 0) { 1197 nxt_unit_req_log(req, level, "Ruby: %s (%s)", RSTRING_PTR(msg), 1198 RSTRING_PTR(eclass)); 1199 1200 return; 1201 } 1202 1203 nxt_unit_req_log(req, level, "Ruby: %s: %s (%s)", 1204 RSTRING_PTR(RARRAY_PTR(ary)[0]), 1205 RSTRING_PTR(msg), RSTRING_PTR(eclass)); 1206 1207 for (i = 1; i < RARRAY_LEN(ary); i++) { 1208 nxt_unit_req_log(req, level, "from %s", 1209 RSTRING_PTR(RARRAY_PTR(ary)[i])); 1210 } 1211 } 1212 1213 1214 static void 1215 nxt_ruby_ctx_done(nxt_ruby_ctx_t *rctx) 1216 { 1217 if (rctx->io_input != Qnil) { 1218 rb_gc_unregister_address(&rctx->io_input); 1219 } 1220 1221 if (rctx->io_error != Qnil) { 1222 rb_gc_unregister_address(&rctx->io_error); 1223 } 1224 1225 if (rctx->env != Qnil) { 1226 rb_gc_unregister_address(&rctx->env); 1227 } 1228 } 1229 1230 1231 static void 1232 nxt_ruby_atexit(void) 1233 { 1234 if (nxt_ruby_rackup != Qnil) { 1235 rb_gc_unregister_address(&nxt_ruby_rackup); 1236 } 1237 1238 if (nxt_ruby_call != Qnil) { 1239 rb_gc_unregister_address(&nxt_ruby_call); 1240 } 1241 1242 if (nxt_ruby_hook_procs != Qnil) { 1243 rb_gc_unregister_address(&nxt_ruby_hook_procs); 1244 } 1245 1246 nxt_ruby_done_strings(); 1247 1248 ruby_cleanup(0); 1249 } 1250 1251 1252 static int 1253 nxt_ruby_ready_handler(nxt_unit_ctx_t *ctx) 1254 { 1255 VALUE res; 1256 uint32_t i; 1257 nxt_ruby_ctx_t *rctx; 1258 nxt_ruby_app_conf_t *c; 1259 1260 c = ctx->unit->data; 1261 1262 if (c->threads <= 1) { 1263 return NXT_UNIT_OK; 1264 } 1265 1266 for (i = 0; i < c->threads - 1; i++) { 1267 rctx = &nxt_ruby_ctxs[i]; 1268 1269 rctx->ctx = ctx; 1270 1271 res = (VALUE) rb_thread_call_with_gvl(nxt_ruby_thread_create_gvl, rctx); 1272 1273 if (nxt_fast_path(res != Qnil)) { 1274 nxt_unit_debug(ctx, "thread #%d created", (int) (i + 1)); 1275 1276 rctx->thread = res; 1277 1278 } else { 1279 nxt_unit_alert(ctx, "thread #%d create failed", (int) (i + 1)); 1280 1281 return NXT_UNIT_ERROR; 1282 } 1283 } 1284 1285 return NXT_UNIT_OK; 1286 } 1287 1288 1289 static void * 1290 nxt_ruby_thread_create_gvl(void *rctx) 1291 { 1292 VALUE res; 1293 1294 res = rb_thread_create(RUBY_METHOD_FUNC(nxt_ruby_thread_func), rctx); 1295 1296 return (void *) (uintptr_t) res; 1297 } 1298 1299 1300 static VALUE 1301 nxt_ruby_thread_func(VALUE arg) 1302 { 1303 int state; 1304 nxt_unit_ctx_t *ctx; 1305 nxt_ruby_ctx_t *rctx; 1306 1307 rctx = (nxt_ruby_ctx_t *) (uintptr_t) arg; 1308 1309 nxt_unit_debug(rctx->ctx, "worker thread start"); 1310 1311 ctx = nxt_unit_ctx_alloc(rctx->ctx, rctx); 1312 if (nxt_slow_path(ctx == NULL)) { 1313 goto fail; 1314 } 1315 1316 if (nxt_ruby_hook_procs != Qnil) { 1317 rb_protect(nxt_ruby_hook_call, nxt_rb_on_thread_boot, &state); 1318 if (nxt_slow_path(state != 0)) { 1319 nxt_ruby_exception_log(NULL, NXT_LOG_ERR, 1320 "Failed to call on_thread_boot()"); 1321 } 1322 } 1323 1324 (void) rb_thread_call_without_gvl(nxt_ruby_unit_run, ctx, 1325 nxt_ruby_ubf, ctx); 1326 1327 if (nxt_ruby_hook_procs != Qnil) { 1328 rb_protect(nxt_ruby_hook_call, nxt_rb_on_thread_shutdown, &state); 1329 if (nxt_slow_path(state != 0)) { 1330 nxt_ruby_exception_log(NULL, NXT_LOG_ERR, 1331 "Failed to call on_thread_shutdown()"); 1332 } 1333 } 1334 1335 nxt_unit_done(ctx); 1336 1337 fail: 1338 1339 nxt_unit_debug(NULL, "worker thread end"); 1340 1341 return Qnil; 1342 } 1343 1344 1345 static void * 1346 nxt_ruby_unit_run(void *ctx) 1347 { 1348 return (void *) (intptr_t) nxt_unit_run(ctx); 1349 } 1350 1351 1352 static void 1353 nxt_ruby_ubf(void *ctx) 1354 { 1355 nxt_unit_warn(ctx, "Ruby: UBF"); 1356 } 1357 1358 1359 static int 1360 nxt_ruby_init_threads(nxt_ruby_app_conf_t *c) 1361 { 1362 int state; 1363 uint32_t i; 1364 nxt_ruby_ctx_t *rctx; 1365 1366 if (c->threads <= 1) { 1367 return NXT_UNIT_OK; 1368 } 1369 1370 nxt_ruby_ctxs = nxt_unit_malloc(NULL, sizeof(nxt_ruby_ctx_t) 1371 * (c->threads - 1)); 1372 if (nxt_slow_path(nxt_ruby_ctxs == NULL)) { 1373 nxt_unit_alert(NULL, "Failed to allocate run contexts array"); 1374 1375 return NXT_UNIT_ERROR; 1376 } 1377 1378 for (i = 0; i < c->threads - 1; i++) { 1379 rctx = &nxt_ruby_ctxs[i]; 1380 1381 rctx->env = Qnil; 1382 rctx->io_input = Qnil; 1383 rctx->io_error = Qnil; 1384 rctx->thread = Qnil; 1385 } 1386 1387 for (i = 0; i < c->threads - 1; i++) { 1388 rctx = &nxt_ruby_ctxs[i]; 1389 1390 rctx->env = rb_protect(nxt_ruby_rack_env_create, 1391 (VALUE) (uintptr_t) rctx, &state); 1392 if (nxt_slow_path(rctx->env == Qnil || state != 0)) { 1393 nxt_ruby_exception_log(NULL, NXT_LOG_ALERT, 1394 "Failed to create 'environ' variable"); 1395 return NXT_UNIT_ERROR; 1396 } 1397 } 1398 1399 return NXT_UNIT_OK; 1400 } 1401 1402 1403 static void 1404 nxt_ruby_join_threads(nxt_unit_ctx_t *ctx, nxt_ruby_app_conf_t *c) 1405 { 1406 uint32_t i; 1407 nxt_ruby_ctx_t *rctx; 1408 1409 if (nxt_ruby_ctxs == NULL) { 1410 return; 1411 } 1412 1413 for (i = 0; i < c->threads - 1; i++) { 1414 rctx = &nxt_ruby_ctxs[i]; 1415 1416 if (rctx->thread != Qnil) { 1417 rb_funcall(rctx->thread, rb_intern("join"), 0); 1418 1419 nxt_unit_debug(ctx, "thread #%d joined", (int) (i + 1)); 1420 1421 } else { 1422 nxt_unit_debug(ctx, "thread #%d not started", (int) (i + 1)); 1423 } 1424 } 1425 1426 for (i = 0; i < c->threads - 1; i++) { 1427 nxt_ruby_ctx_done(&nxt_ruby_ctxs[i]); 1428 } 1429 1430 nxt_unit_free(ctx, nxt_ruby_ctxs); 1431 } 1432