13#define NXT_RUBY_RACK_API_VERSION_MAJOR 1 14#define NXT_RUBY_RACK_API_VERSION_MINOR 3 15 16#define NXT_RUBY_STRINGIZE_HELPER(x) #x 17#define NXT_RUBY_STRINGIZE(x) NXT_RUBY_STRINGIZE_HELPER(x) 18 19#define NXT_RUBY_LIB_VERSION \ 20 NXT_RUBY_STRINGIZE(RUBY_API_VERSION_MAJOR) \ 21 "." NXT_RUBY_STRINGIZE(RUBY_API_VERSION_MINOR) \ 22 "." NXT_RUBY_STRINGIZE(RUBY_API_VERSION_TEENY) 23 24 25typedef struct { 26 nxt_task_t *task; 27 nxt_str_t *script; 28 VALUE builder; 29} nxt_ruby_rack_init_t; 30 31 32static nxt_int_t nxt_ruby_start(nxt_task_t *task, 33 nxt_process_data_t *data); 34static VALUE nxt_ruby_init_basic(VALUE arg); 35static nxt_int_t nxt_ruby_init_io(nxt_task_t *task); 36static VALUE nxt_ruby_rack_init(nxt_ruby_rack_init_t *rack_init); 37 38static VALUE nxt_ruby_require_rubygems(VALUE arg); 39static VALUE nxt_ruby_bundler_setup(VALUE arg); 40static VALUE nxt_ruby_require_rack(VALUE arg); 41static VALUE nxt_ruby_rack_parse_script(VALUE ctx); 42static VALUE nxt_ruby_rack_env_create(VALUE arg); 43static void nxt_ruby_request_handler(nxt_unit_request_info_t *req); 44 45static VALUE nxt_ruby_rack_app_run(VALUE arg); 46static int nxt_ruby_read_request(VALUE hash_env); 47nxt_inline void nxt_ruby_add_sptr(VALUE hash_env, 48 const char *name, uint32_t name_len, nxt_unit_sptr_t *sptr, uint32_t len); 49nxt_inline void nxt_ruby_add_str(VALUE hash_env, 50 const char *name, uint32_t name_len, const char *str, uint32_t len); 51static nxt_int_t nxt_ruby_rack_result_status(VALUE result); 52static int nxt_ruby_rack_result_headers(VALUE result, nxt_int_t status); 53static int nxt_ruby_hash_info(VALUE r_key, VALUE r_value, VALUE arg); 54static int nxt_ruby_hash_add(VALUE r_key, VALUE r_value, VALUE arg); 55static int nxt_ruby_rack_result_body(VALUE result); 56static int nxt_ruby_rack_result_body_file_write(VALUE filepath); 57static VALUE nxt_ruby_rack_result_body_each(VALUE body, VALUE arg, 58 int argc, const VALUE *argv, VALUE blockarg); 59 60static void nxt_ruby_exception_log(nxt_task_t *task, uint32_t level, 61 const char *desc); 62 63static void nxt_ruby_atexit(void); 64 65 66static uint32_t compat[] = { 67 NXT_VERNUM, NXT_DEBUG, 68}; 69 70static VALUE nxt_ruby_rackup; 71static VALUE nxt_ruby_call; 72static VALUE nxt_ruby_env; 73static VALUE nxt_ruby_io_input; 74static VALUE nxt_ruby_io_error; 75static nxt_ruby_run_ctx_t nxt_ruby_run_ctx; 76 77NXT_EXPORT nxt_app_module_t nxt_app_module = { 78 sizeof(compat), 79 compat, 80 nxt_string("ruby"), 81 ruby_version, 82 nxt_ruby_mounts, 83 nxt_nitems(nxt_ruby_mounts), 84 NULL, 85 nxt_ruby_start, 86}; 87 88 89static nxt_int_t 90nxt_ruby_start(nxt_task_t *task, nxt_process_data_t *data) 91{ 92 int state, rc; 93 VALUE res; 94 nxt_unit_ctx_t *unit_ctx; 95 nxt_unit_init_t ruby_unit_init; 96 nxt_ruby_rack_init_t rack_init; 97 nxt_common_app_conf_t *conf; 98 99 static char *argv[2] = { (char *) "NGINX_Unit", (char *) "-e0" }; 100 101 conf = data->app; 102 103 RUBY_INIT_STACK 104 ruby_init(); 105 ruby_options(2, argv); 106 ruby_script("NGINX_Unit"); 107 108 rack_init.task = task; 109 rack_init.script = &conf->u.ruby.script; 110 111 res = rb_protect(nxt_ruby_init_basic, 112 (VALUE) (uintptr_t) &rack_init, &state); 113 if (nxt_slow_path(res == Qnil || state != 0)) { 114 nxt_ruby_exception_log(task, NXT_LOG_ALERT, 115 "Failed to init basic variables"); 116 return NXT_ERROR; 117 } 118 119 nxt_ruby_rackup = nxt_ruby_rack_init(&rack_init); 120 if (nxt_slow_path(nxt_ruby_rackup == Qnil)) { 121 return NXT_ERROR; 122 } 123 124 nxt_ruby_call = rb_intern("call"); 125 if (nxt_slow_path(nxt_ruby_call == Qnil)) { 126 nxt_alert(task, "Ruby: Unable to find rack entry point"); 127 128 return NXT_ERROR; 129 } 130 131 nxt_ruby_env = rb_protect(nxt_ruby_rack_env_create, Qnil, &state); 132 if (nxt_slow_path(state != 0)) { 133 nxt_ruby_exception_log(task, NXT_LOG_ALERT, 134 "Failed to create 'environ' variable"); 135 return NXT_ERROR; 136 } 137 138 rb_gc_register_address(&nxt_ruby_rackup); 139 rb_gc_register_address(&nxt_ruby_call); 140 rb_gc_register_address(&nxt_ruby_env); 141 142 nxt_unit_default_init(task, &ruby_unit_init); 143 144 ruby_unit_init.callbacks.request_handler = nxt_ruby_request_handler; 145 ruby_unit_init.shm_limit = conf->shm_limit; 146 147 unit_ctx = nxt_unit_init(&ruby_unit_init); 148 if (nxt_slow_path(unit_ctx == NULL)) { 149 return NXT_ERROR; 150 } 151 152 nxt_ruby_run_ctx.unit_ctx = unit_ctx; 153 154 rc = nxt_unit_run(unit_ctx); 155 156 nxt_ruby_atexit(); 157 158 nxt_ruby_run_ctx.unit_ctx = NULL; 159 160 nxt_unit_done(unit_ctx); 161 162 exit(rc); 163 164 return NXT_OK; 165} 166 167 168static VALUE 169nxt_ruby_init_basic(VALUE arg) 170{ 171 int state; 172 nxt_int_t rc; 173 nxt_ruby_rack_init_t *rack_init; 174 175 rack_init = (nxt_ruby_rack_init_t *) (uintptr_t) arg; 176 177 state = rb_enc_find_index("encdb"); 178 if (nxt_slow_path(state == 0)) { 179 nxt_alert(rack_init->task, 180 "Ruby: Failed to find encoding index 'encdb'"); 181 182 return Qnil; 183 } 184 185 rb_funcall(rb_cObject, rb_intern("require"), 1, 186 rb_str_new2("enc/trans/transdb")); 187 188 rc = nxt_ruby_init_io(rack_init->task); 189 if (nxt_slow_path(rc != NXT_OK)) { 190 return Qnil; 191 } 192 193 return arg; 194} 195 196 197static nxt_int_t 198nxt_ruby_init_io(nxt_task_t *task) 199{ 200 VALUE rb, io_input, io_error; 201 202 io_input = nxt_ruby_stream_io_input_init(); 203 rb = Data_Wrap_Struct(io_input, 0, 0, &nxt_ruby_run_ctx); 204 205 nxt_ruby_io_input = rb_funcall(io_input, rb_intern("new"), 1, rb); 206 if (nxt_slow_path(nxt_ruby_io_input == Qnil)) { 207 nxt_alert(task, "Ruby: Failed to create environment 'rack.input' var"); 208 209 return NXT_ERROR; 210 } 211 212 io_error = nxt_ruby_stream_io_error_init(); 213 rb = Data_Wrap_Struct(io_error, 0, 0, &nxt_ruby_run_ctx); 214 215 nxt_ruby_io_error = rb_funcall(io_error, rb_intern("new"), 1, rb); 216 if (nxt_slow_path(nxt_ruby_io_error == Qnil)) { 217 nxt_alert(task, "Ruby: Failed to create environment 'rack.error' var"); 218 219 return NXT_ERROR; 220 } 221 222 rb_gc_register_address(&nxt_ruby_io_input); 223 rb_gc_register_address(&nxt_ruby_io_error); 224 225 return NXT_OK; 226} 227 228 229static VALUE 230nxt_ruby_rack_init(nxt_ruby_rack_init_t *rack_init) 231{ 232 int state; 233 VALUE rack, rackup, err; 234 235 rb_protect(nxt_ruby_require_rubygems, Qnil, &state); 236 if (nxt_slow_path(state != 0)) { 237 nxt_ruby_exception_log(rack_init->task, NXT_LOG_ALERT, 238 "Failed to require 'rubygems' package"); 239 return Qnil; 240 } 241 242 rb_protect(nxt_ruby_bundler_setup, Qnil, &state); 243 if (state != 0) { 244 err = rb_errinfo(); 245 246 if (rb_obj_is_kind_of(err, rb_eLoadError) == Qfalse) { 247 nxt_ruby_exception_log(rack_init->task, NXT_LOG_ALERT, 248 "Failed to require 'bundler/setup' package"); 249 return Qnil; 250 } 251 252 rb_set_errinfo(Qnil); 253 } 254 255 rb_protect(nxt_ruby_require_rack, Qnil, &state); 256 if (nxt_slow_path(state != 0)) { 257 nxt_ruby_exception_log(rack_init->task, NXT_LOG_ALERT, 258 "Failed to require 'rack' package"); 259 return Qnil; 260 } 261 262 rack = rb_const_get(rb_cObject, rb_intern("Rack")); 263 rack_init->builder = rb_const_get(rack, rb_intern("Builder")); 264 265 rackup = rb_protect(nxt_ruby_rack_parse_script, 266 (VALUE) (uintptr_t) rack_init, &state); 267 if (nxt_slow_path(TYPE(rackup) != T_ARRAY || state != 0)) { 268 nxt_ruby_exception_log(rack_init->task, NXT_LOG_ALERT, 269 "Failed to parse rack script"); 270 return Qnil; 271 } 272 273 if (nxt_slow_path(RARRAY_LEN(rackup) < 1)) { 274 nxt_alert(rack_init->task, "Ruby: Invalid rack config file"); 275 return Qnil; 276 } 277 278 return RARRAY_PTR(rackup)[0]; 279} 280 281 282static VALUE 283nxt_ruby_require_rubygems(VALUE arg) 284{ 285 return rb_funcall(rb_cObject, rb_intern("require"), 1, 286 rb_str_new2("rubygems")); 287} 288 289 290static VALUE 291nxt_ruby_bundler_setup(VALUE arg) 292{ 293 return rb_funcall(rb_cObject, rb_intern("require"), 1, 294 rb_str_new2("bundler/setup")); 295} 296 297 298static VALUE 299nxt_ruby_require_rack(VALUE arg) 300{ 301 return rb_funcall(rb_cObject, rb_intern("require"), 1, rb_str_new2("rack")); 302} 303 304 305static VALUE 306nxt_ruby_rack_parse_script(VALUE ctx) 307{ 308 VALUE script, res; 309 nxt_ruby_rack_init_t *rack_init; 310 311 rack_init = (nxt_ruby_rack_init_t *) (uintptr_t) ctx; 312 313 script = rb_str_new((const char *) rack_init->script->start, 314 (long) rack_init->script->length); 315 316 res = rb_funcall(rack_init->builder, rb_intern("parse_file"), 1, script); 317 318 rb_str_free(script); 319 320 return res; 321} 322 323 324static VALUE 325nxt_ruby_rack_env_create(VALUE arg) 326{ 327 VALUE hash_env, version; 328 329 hash_env = rb_hash_new(); 330 331 rb_hash_aset(hash_env, rb_str_new2("SERVER_SOFTWARE"), 332 rb_str_new((const char *) nxt_server.start, 333 (long) nxt_server.length)); 334 335 version = rb_ary_new(); 336 337 rb_ary_push(version, UINT2NUM(NXT_RUBY_RACK_API_VERSION_MAJOR)); 338 rb_ary_push(version, UINT2NUM(NXT_RUBY_RACK_API_VERSION_MINOR)); 339 340 rb_hash_aset(hash_env, rb_str_new2("rack.version"), version); 341 rb_hash_aset(hash_env, rb_str_new2("rack.input"), nxt_ruby_io_input); 342 rb_hash_aset(hash_env, rb_str_new2("rack.errors"), nxt_ruby_io_error); 343 rb_hash_aset(hash_env, rb_str_new2("rack.multithread"), Qfalse); 344 rb_hash_aset(hash_env, rb_str_new2("rack.multiprocess"), Qtrue); 345 rb_hash_aset(hash_env, rb_str_new2("rack.run_once"), Qfalse); 346 rb_hash_aset(hash_env, rb_str_new2("rack.hijack?"), Qfalse); 347 rb_hash_aset(hash_env, rb_str_new2("rack.hijack"), Qnil); 348 rb_hash_aset(hash_env, rb_str_new2("rack.hijack_io"), Qnil); 349 350 return hash_env; 351} 352 353 354static void 355nxt_ruby_request_handler(nxt_unit_request_info_t *req) 356{ 357 int state; 358 VALUE res; 359 360 nxt_ruby_run_ctx.req = req; 361 362 res = rb_protect(nxt_ruby_rack_app_run, Qnil, &state); 363 if (nxt_slow_path(res == Qnil || state != 0)) { 364 nxt_ruby_exception_log(NULL, NXT_LOG_ERR, 365 "Failed to run ruby script"); 366 } 367} 368 369 370static VALUE 371nxt_ruby_rack_app_run(VALUE arg) 372{ 373 int rc; 374 VALUE env, result; 375 nxt_int_t status; 376 377 env = rb_hash_dup(nxt_ruby_env); 378 379 rc = nxt_ruby_read_request(env); 380 if (nxt_slow_path(rc != NXT_UNIT_OK)) { 381 nxt_unit_req_alert(nxt_ruby_run_ctx.req, 382 "Ruby: Failed to process incoming request"); 383 384 goto fail; 385 } 386 387 result = rb_funcall(nxt_ruby_rackup, nxt_ruby_call, 1, env); 388 if (nxt_slow_path(TYPE(result) != T_ARRAY)) { 389 nxt_unit_req_error(nxt_ruby_run_ctx.req, 390 "Ruby: Invalid response format from application"); 391 392 goto fail; 393 } 394 395 if (nxt_slow_path(RARRAY_LEN(result) != 3)) { 396 nxt_unit_req_error(nxt_ruby_run_ctx.req, 397 "Ruby: Invalid response format from application. " 398 "Need 3 entries [Status, Headers, Body]"); 399 400 goto fail; 401 } 402 403 status = nxt_ruby_rack_result_status(result); 404 if (nxt_slow_path(status < 0)) { 405 nxt_unit_req_error(nxt_ruby_run_ctx.req, 406 "Ruby: Invalid response status from application."); 407 408 goto fail; 409 } 410 411 rc = nxt_ruby_rack_result_headers(result, status); 412 if (nxt_slow_path(rc != NXT_UNIT_OK)) { 413 goto fail; 414 } 415 416 rc = nxt_ruby_rack_result_body(result); 417 if (nxt_slow_path(rc != NXT_UNIT_OK)) { 418 goto fail; 419 } 420 421 nxt_unit_request_done(nxt_ruby_run_ctx.req, rc); 422 nxt_ruby_run_ctx.req = NULL; 423 424 rb_hash_delete(env, rb_obj_id(env)); 425 426 return result; 427 428fail: 429 430 nxt_unit_request_done(nxt_ruby_run_ctx.req, NXT_UNIT_ERROR); 431 nxt_ruby_run_ctx.req = NULL; 432 433 rb_hash_delete(env, rb_obj_id(env)); 434 435 return Qnil; 436} 437 438 439static int 440nxt_ruby_read_request(VALUE hash_env) 441{ 442 uint32_t i; 443 nxt_unit_field_t *f; 444 nxt_unit_request_t *r; 445 446 r = nxt_ruby_run_ctx.req->request; 447 448#define NL(S) (S), sizeof(S)-1 449 450 nxt_ruby_add_sptr(hash_env, NL("REQUEST_METHOD"), &r->method, 451 r->method_length); 452 nxt_ruby_add_sptr(hash_env, NL("REQUEST_URI"), &r->target, 453 r->target_length); 454 nxt_ruby_add_sptr(hash_env, NL("PATH_INFO"), &r->path, r->path_length); 455 nxt_ruby_add_sptr(hash_env, NL("QUERY_STRING"), &r->query, 456 r->query_length); 457 nxt_ruby_add_sptr(hash_env, NL("SERVER_PROTOCOL"), &r->version, 458 r->version_length); 459 nxt_ruby_add_sptr(hash_env, NL("REMOTE_ADDR"), &r->remote, 460 r->remote_length); 461 nxt_ruby_add_sptr(hash_env, NL("SERVER_ADDR"), &r->local, r->local_length); 462 463 nxt_ruby_add_sptr(hash_env, NL("SERVER_NAME"), &r->server_name, 464 r->server_name_length); 465 nxt_ruby_add_str(hash_env, NL("SERVER_PORT"), "80", 2); 466 467 rb_hash_aset(hash_env, rb_str_new2("rack.url_scheme"), 468 r->tls ? rb_str_new2("https") : rb_str_new2("http")); 469 470 for (i = 0; i < r->fields_count; i++) { 471 f = r->fields + i; 472 473 nxt_ruby_add_sptr(hash_env, nxt_unit_sptr_get(&f->name), f->name_length, 474 &f->value, f->value_length); 475 } 476 477 if (r->content_length_field != NXT_UNIT_NONE_FIELD) { 478 f = r->fields + r->content_length_field; 479 480 nxt_ruby_add_sptr(hash_env, NL("CONTENT_LENGTH"), 481 &f->value, f->value_length); 482 } 483 484 if (r->content_type_field != NXT_UNIT_NONE_FIELD) { 485 f = r->fields + r->content_type_field; 486 487 nxt_ruby_add_sptr(hash_env, NL("CONTENT_TYPE"), 488 &f->value, f->value_length); 489 } 490 491#undef NL 492 493 return NXT_UNIT_OK; 494} 495 496 497nxt_inline void 498nxt_ruby_add_sptr(VALUE hash_env, 499 const char *name, uint32_t name_len, nxt_unit_sptr_t *sptr, uint32_t len) 500{ 501 char *str; 502 503 str = nxt_unit_sptr_get(sptr); 504 505 rb_hash_aset(hash_env, rb_str_new(name, name_len), rb_str_new(str, len)); 506} 507 508 509nxt_inline void 510nxt_ruby_add_str(VALUE hash_env, 511 const char *name, uint32_t name_len, const char *str, uint32_t len) 512{ 513 rb_hash_aset(hash_env, rb_str_new(name, name_len), rb_str_new(str, len)); 514} 515 516 517static nxt_int_t 518nxt_ruby_rack_result_status(VALUE result) 519{ 520 VALUE status; 521 522 status = rb_ary_entry(result, 0); 523 524 if (TYPE(status) == T_FIXNUM) { 525 return FIX2INT(status); 526 } 527 528 if (TYPE(status) == T_STRING) { 529 return nxt_int_parse((u_char *) RSTRING_PTR(status), 530 RSTRING_LEN(status)); 531 } 532 533 nxt_unit_req_error(nxt_ruby_run_ctx.req, "Ruby: Invalid response 'status' " 534 "format from application"); 535 536 return -2; 537} 538 539 540typedef struct { 541 int rc; 542 uint32_t fields; 543 uint32_t size; 544} nxt_ruby_headers_info_t; 545 546 547static int 548nxt_ruby_rack_result_headers(VALUE result, nxt_int_t status) 549{ 550 int rc; 551 VALUE headers; 552 nxt_ruby_headers_info_t headers_info; 553 554 headers = rb_ary_entry(result, 1); 555 if (nxt_slow_path(TYPE(headers) != T_HASH)) { 556 nxt_unit_req_error(nxt_ruby_run_ctx.req, 557 "Ruby: Invalid response 'headers' format from " 558 "application"); 559 560 return NXT_UNIT_ERROR; 561 } 562 563 rc = NXT_UNIT_OK; 564 565 headers_info.rc = NXT_UNIT_OK; 566 headers_info.fields = 0; 567 headers_info.size = 0; 568 569 rb_hash_foreach(headers, nxt_ruby_hash_info, 570 (VALUE) (uintptr_t) &headers_info); 571 if (nxt_slow_path(headers_info.rc != NXT_UNIT_OK)) { 572 return headers_info.rc; 573 } 574 575 rc = nxt_unit_response_init(nxt_ruby_run_ctx.req, status, 576 headers_info.fields, headers_info.size); 577 if (nxt_slow_path(rc != NXT_UNIT_OK)) { 578 return rc; 579 } 580 581 rb_hash_foreach(headers, nxt_ruby_hash_add, (VALUE) (uintptr_t) &rc); 582 583 return rc; 584} 585 586 587static int 588nxt_ruby_hash_info(VALUE r_key, VALUE r_value, VALUE arg) 589{ 590 const char *value, *value_end, *pos; 591 nxt_ruby_headers_info_t *headers_info; 592 593 headers_info = (void *) (uintptr_t) arg; 594 595 if (nxt_slow_path(TYPE(r_key) != T_STRING)) { 596 nxt_unit_req_error(nxt_ruby_run_ctx.req, 597 "Ruby: Wrong header entry 'key' from application"); 598 599 goto fail; 600 } 601 602 if (nxt_slow_path(TYPE(r_value) != T_STRING)) { 603 nxt_unit_req_error(nxt_ruby_run_ctx.req, 604 "Ruby: Wrong header entry 'value' from application"); 605 606 goto fail; 607 } 608 609 value = RSTRING_PTR(r_value); 610 value_end = value + RSTRING_LEN(r_value); 611 612 pos = value; 613 614 for ( ;; ) { 615 pos = strchr(pos, '\n'); 616 617 if (pos == NULL) { 618 break; 619 } 620 621 headers_info->fields++; 622 headers_info->size += RSTRING_LEN(r_key) + (pos - value); 623 624 pos++; 625 value = pos; 626 } 627 628 if (value <= value_end) { 629 headers_info->fields++; 630 headers_info->size += RSTRING_LEN(r_key) + (value_end - value); 631 } 632 633 return ST_CONTINUE; 634 635fail: 636 637 headers_info->rc = NXT_UNIT_ERROR; 638 639 return ST_STOP; 640} 641 642 643static int 644nxt_ruby_hash_add(VALUE r_key, VALUE r_value, VALUE arg) 645{ 646 int *rc; 647 uint32_t key_len; 648 const char *value, *value_end, *pos; 649 650 rc = (int *) (uintptr_t) arg; 651 652 value = RSTRING_PTR(r_value); 653 value_end = value + RSTRING_LEN(r_value); 654 655 key_len = RSTRING_LEN(r_key); 656 657 pos = value; 658 659 for ( ;; ) { 660 pos = strchr(pos, '\n'); 661 662 if (pos == NULL) { 663 break; 664 } 665 666 *rc = nxt_unit_response_add_field(nxt_ruby_run_ctx.req, 667 RSTRING_PTR(r_key), key_len, 668 value, pos - value); 669 if (nxt_slow_path(*rc != NXT_UNIT_OK)) { 670 goto fail; 671 } 672 673 pos++; 674 value = pos; 675 } 676 677 if (value <= value_end) { 678 *rc = nxt_unit_response_add_field(nxt_ruby_run_ctx.req, 679 RSTRING_PTR(r_key), key_len, 680 value, value_end - value); 681 if (nxt_slow_path(*rc != NXT_UNIT_OK)) { 682 goto fail; 683 } 684 } 685 686 return ST_CONTINUE; 687 688fail: 689 690 *rc = NXT_UNIT_ERROR; 691 692 return ST_STOP; 693} 694 695 696static int 697nxt_ruby_rack_result_body(VALUE result) 698{ 699 int rc; 700 VALUE fn, body; 701 702 body = rb_ary_entry(result, 2); 703 704 if (rb_respond_to(body, rb_intern("to_path"))) { 705 706 fn = rb_funcall(body, rb_intern("to_path"), 0); 707 if (nxt_slow_path(TYPE(fn) != T_STRING)) { 708 nxt_unit_req_error(nxt_ruby_run_ctx.req, 709 "Ruby: Failed to get 'body' file path from " 710 "application"); 711 712 return NXT_UNIT_ERROR; 713 } 714 715 rc = nxt_ruby_rack_result_body_file_write(fn); 716 if (nxt_slow_path(rc != NXT_UNIT_OK)) { 717 return rc; 718 } 719 720 } else if (rb_respond_to(body, rb_intern("each"))) { 721 rb_block_call(body, rb_intern("each"), 0, 0, 722 nxt_ruby_rack_result_body_each, 0); 723 724 } else { 725 nxt_unit_req_error(nxt_ruby_run_ctx.req, 726 "Ruby: Invalid response 'body' format " 727 "from application"); 728 729 return NXT_UNIT_ERROR; 730 } 731 732 if (rb_respond_to(body, rb_intern("close"))) { 733 rb_funcall(body, rb_intern("close"), 0); 734 } 735 736 return NXT_UNIT_OK; 737} 738 739 740typedef struct { 741 int fd; 742 off_t pos; 743 off_t rest; 744} nxt_ruby_rack_file_t; 745 746 747static ssize_t 748nxt_ruby_rack_file_read(nxt_unit_read_info_t *read_info, void *dst, size_t size) 749{ 750 ssize_t res; 751 nxt_ruby_rack_file_t *file; 752 753 file = read_info->data; 754 755 size = nxt_min(size, (size_t) file->rest); 756 757 res = pread(file->fd, dst, size, file->pos); 758 759 if (res >= 0) { 760 file->pos += res; 761 file->rest -= res; 762 763 if (size > (size_t) res) { 764 file->rest = 0; 765 } 766 } 767 768 read_info->eof = file->rest == 0; 769 770 return res; 771} 772 773 774static int 775nxt_ruby_rack_result_body_file_write(VALUE filepath) 776{ 777 int fd, rc; 778 struct stat finfo; 779 nxt_ruby_rack_file_t ruby_file; 780 nxt_unit_read_info_t read_info; 781 782 fd = open(RSTRING_PTR(filepath), O_RDONLY, 0); 783 if (nxt_slow_path(fd == -1)) { 784 nxt_unit_req_error(nxt_ruby_run_ctx.req, 785 "Ruby: Failed to open content file \"%s\": %s (%d)", 786 RSTRING_PTR(filepath), strerror(errno), errno); 787 788 return NXT_UNIT_ERROR; 789 } 790 791 rc = fstat(fd, &finfo); 792 if (nxt_slow_path(rc == -1)) { 793 nxt_unit_req_error(nxt_ruby_run_ctx.req, 794 "Ruby: Content file fstat(\"%s\") failed: %s (%d)", 795 RSTRING_PTR(filepath), strerror(errno), errno); 796 797 close(fd); 798 799 return NXT_UNIT_ERROR; 800 } 801 802 ruby_file.fd = fd; 803 ruby_file.pos = 0; 804 ruby_file.rest = finfo.st_size; 805 806 read_info.read = nxt_ruby_rack_file_read; 807 read_info.eof = ruby_file.rest == 0; 808 read_info.buf_size = ruby_file.rest; 809 read_info.data = &ruby_file; 810 811 rc = nxt_unit_response_write_cb(nxt_ruby_run_ctx.req, &read_info); 812 if (nxt_slow_path(rc != NXT_UNIT_OK)) { 813 nxt_unit_req_error(nxt_ruby_run_ctx.req, 814 "Ruby: Failed to write content file."); 815 } 816 817 close(fd); 818 819 return rc; 820} 821 822 823static VALUE 824nxt_ruby_rack_result_body_each(VALUE body, VALUE arg, int argc, 825 const VALUE *argv, VALUE blockarg) 826{ 827 int rc; 828 829 if (TYPE(body) != T_STRING) { 830 return Qnil; 831 } 832 833 rc = nxt_unit_response_write(nxt_ruby_run_ctx.req, RSTRING_PTR(body), 834 RSTRING_LEN(body)); 835 if (nxt_slow_path(rc != NXT_UNIT_OK)) { 836 nxt_unit_req_error(nxt_ruby_run_ctx.req, 837 "Ruby: Failed to write 'body' from application"); 838 } 839 840 return Qnil; 841} 842 843 844static void 845nxt_ruby_exception_log(nxt_task_t *task, uint32_t level, const char *desc) 846{ 847 int i; 848 VALUE err, ary, eclass, msg; 849 850 if (task != NULL) { 851 nxt_log(task, level, "Ruby: %s", desc); 852 853 } else { 854 nxt_unit_log(nxt_ruby_run_ctx.unit_ctx, level, "Ruby: %s", desc); 855 } 856 857 err = rb_errinfo(); 858 if (nxt_slow_path(err == Qnil)) { 859 return; 860 } 861 862 ary = rb_funcall(err, rb_intern("backtrace"), 0); 863 if (nxt_slow_path(RARRAY_LEN(ary) == 0)) { 864 return; 865 } 866 867 eclass = rb_class_name(rb_class_of(err)); 868 msg = rb_funcall(err, rb_intern("message"), 0); 869 870 if (task != NULL) { 871 nxt_log(task, level, "Ruby: %s: %s (%s)", 872 RSTRING_PTR(RARRAY_PTR(ary)[0]), 873 RSTRING_PTR(msg), RSTRING_PTR(eclass)); 874 875 } else { 876 nxt_unit_log(nxt_ruby_run_ctx.unit_ctx, level, "Ruby: %s: %s (%s)", 877 RSTRING_PTR(RARRAY_PTR(ary)[0]), 878 RSTRING_PTR(msg), RSTRING_PTR(eclass)); 879 } 880 881 for (i = 1; i < RARRAY_LEN(ary); i++) { 882 if (task != NULL) { 883 nxt_log(task, level, "from %s", RSTRING_PTR(RARRAY_PTR(ary)[i])); 884 885 } else { 886 nxt_unit_log(nxt_ruby_run_ctx.unit_ctx, level, "from %s", 887 RSTRING_PTR(RARRAY_PTR(ary)[i])); 888 } 889 } 890} 891 892 893static void 894nxt_ruby_atexit(void) 895{ 896 rb_gc_unregister_address(&nxt_ruby_io_input); 897 rb_gc_unregister_address(&nxt_ruby_io_error); 898 899 rb_gc_unregister_address(&nxt_ruby_rackup); 900 rb_gc_unregister_address(&nxt_ruby_call); 901 rb_gc_unregister_address(&nxt_ruby_env); 902 903 ruby_cleanup(0); 904}
| 14#define NXT_RUBY_RACK_API_VERSION_MAJOR 1 15#define NXT_RUBY_RACK_API_VERSION_MINOR 3 16 17#define NXT_RUBY_STRINGIZE_HELPER(x) #x 18#define NXT_RUBY_STRINGIZE(x) NXT_RUBY_STRINGIZE_HELPER(x) 19 20#define NXT_RUBY_LIB_VERSION \ 21 NXT_RUBY_STRINGIZE(RUBY_API_VERSION_MAJOR) \ 22 "." NXT_RUBY_STRINGIZE(RUBY_API_VERSION_MINOR) \ 23 "." NXT_RUBY_STRINGIZE(RUBY_API_VERSION_TEENY) 24 25 26typedef struct { 27 nxt_task_t *task; 28 nxt_str_t *script; 29 VALUE builder; 30} nxt_ruby_rack_init_t; 31 32 33static nxt_int_t nxt_ruby_start(nxt_task_t *task, 34 nxt_process_data_t *data); 35static VALUE nxt_ruby_init_basic(VALUE arg); 36static nxt_int_t nxt_ruby_init_io(nxt_task_t *task); 37static VALUE nxt_ruby_rack_init(nxt_ruby_rack_init_t *rack_init); 38 39static VALUE nxt_ruby_require_rubygems(VALUE arg); 40static VALUE nxt_ruby_bundler_setup(VALUE arg); 41static VALUE nxt_ruby_require_rack(VALUE arg); 42static VALUE nxt_ruby_rack_parse_script(VALUE ctx); 43static VALUE nxt_ruby_rack_env_create(VALUE arg); 44static void nxt_ruby_request_handler(nxt_unit_request_info_t *req); 45 46static VALUE nxt_ruby_rack_app_run(VALUE arg); 47static int nxt_ruby_read_request(VALUE hash_env); 48nxt_inline void nxt_ruby_add_sptr(VALUE hash_env, 49 const char *name, uint32_t name_len, nxt_unit_sptr_t *sptr, uint32_t len); 50nxt_inline void nxt_ruby_add_str(VALUE hash_env, 51 const char *name, uint32_t name_len, const char *str, uint32_t len); 52static nxt_int_t nxt_ruby_rack_result_status(VALUE result); 53static int nxt_ruby_rack_result_headers(VALUE result, nxt_int_t status); 54static int nxt_ruby_hash_info(VALUE r_key, VALUE r_value, VALUE arg); 55static int nxt_ruby_hash_add(VALUE r_key, VALUE r_value, VALUE arg); 56static int nxt_ruby_rack_result_body(VALUE result); 57static int nxt_ruby_rack_result_body_file_write(VALUE filepath); 58static VALUE nxt_ruby_rack_result_body_each(VALUE body, VALUE arg, 59 int argc, const VALUE *argv, VALUE blockarg); 60 61static void nxt_ruby_exception_log(nxt_task_t *task, uint32_t level, 62 const char *desc); 63 64static void nxt_ruby_atexit(void); 65 66 67static uint32_t compat[] = { 68 NXT_VERNUM, NXT_DEBUG, 69}; 70 71static VALUE nxt_ruby_rackup; 72static VALUE nxt_ruby_call; 73static VALUE nxt_ruby_env; 74static VALUE nxt_ruby_io_input; 75static VALUE nxt_ruby_io_error; 76static nxt_ruby_run_ctx_t nxt_ruby_run_ctx; 77 78NXT_EXPORT nxt_app_module_t nxt_app_module = { 79 sizeof(compat), 80 compat, 81 nxt_string("ruby"), 82 ruby_version, 83 nxt_ruby_mounts, 84 nxt_nitems(nxt_ruby_mounts), 85 NULL, 86 nxt_ruby_start, 87}; 88 89 90static nxt_int_t 91nxt_ruby_start(nxt_task_t *task, nxt_process_data_t *data) 92{ 93 int state, rc; 94 VALUE res; 95 nxt_unit_ctx_t *unit_ctx; 96 nxt_unit_init_t ruby_unit_init; 97 nxt_ruby_rack_init_t rack_init; 98 nxt_common_app_conf_t *conf; 99 100 static char *argv[2] = { (char *) "NGINX_Unit", (char *) "-e0" }; 101 102 conf = data->app; 103 104 RUBY_INIT_STACK 105 ruby_init(); 106 ruby_options(2, argv); 107 ruby_script("NGINX_Unit"); 108 109 rack_init.task = task; 110 rack_init.script = &conf->u.ruby.script; 111 112 res = rb_protect(nxt_ruby_init_basic, 113 (VALUE) (uintptr_t) &rack_init, &state); 114 if (nxt_slow_path(res == Qnil || state != 0)) { 115 nxt_ruby_exception_log(task, NXT_LOG_ALERT, 116 "Failed to init basic variables"); 117 return NXT_ERROR; 118 } 119 120 nxt_ruby_rackup = nxt_ruby_rack_init(&rack_init); 121 if (nxt_slow_path(nxt_ruby_rackup == Qnil)) { 122 return NXT_ERROR; 123 } 124 125 nxt_ruby_call = rb_intern("call"); 126 if (nxt_slow_path(nxt_ruby_call == Qnil)) { 127 nxt_alert(task, "Ruby: Unable to find rack entry point"); 128 129 return NXT_ERROR; 130 } 131 132 nxt_ruby_env = rb_protect(nxt_ruby_rack_env_create, Qnil, &state); 133 if (nxt_slow_path(state != 0)) { 134 nxt_ruby_exception_log(task, NXT_LOG_ALERT, 135 "Failed to create 'environ' variable"); 136 return NXT_ERROR; 137 } 138 139 rb_gc_register_address(&nxt_ruby_rackup); 140 rb_gc_register_address(&nxt_ruby_call); 141 rb_gc_register_address(&nxt_ruby_env); 142 143 nxt_unit_default_init(task, &ruby_unit_init); 144 145 ruby_unit_init.callbacks.request_handler = nxt_ruby_request_handler; 146 ruby_unit_init.shm_limit = conf->shm_limit; 147 148 unit_ctx = nxt_unit_init(&ruby_unit_init); 149 if (nxt_slow_path(unit_ctx == NULL)) { 150 return NXT_ERROR; 151 } 152 153 nxt_ruby_run_ctx.unit_ctx = unit_ctx; 154 155 rc = nxt_unit_run(unit_ctx); 156 157 nxt_ruby_atexit(); 158 159 nxt_ruby_run_ctx.unit_ctx = NULL; 160 161 nxt_unit_done(unit_ctx); 162 163 exit(rc); 164 165 return NXT_OK; 166} 167 168 169static VALUE 170nxt_ruby_init_basic(VALUE arg) 171{ 172 int state; 173 nxt_int_t rc; 174 nxt_ruby_rack_init_t *rack_init; 175 176 rack_init = (nxt_ruby_rack_init_t *) (uintptr_t) arg; 177 178 state = rb_enc_find_index("encdb"); 179 if (nxt_slow_path(state == 0)) { 180 nxt_alert(rack_init->task, 181 "Ruby: Failed to find encoding index 'encdb'"); 182 183 return Qnil; 184 } 185 186 rb_funcall(rb_cObject, rb_intern("require"), 1, 187 rb_str_new2("enc/trans/transdb")); 188 189 rc = nxt_ruby_init_io(rack_init->task); 190 if (nxt_slow_path(rc != NXT_OK)) { 191 return Qnil; 192 } 193 194 return arg; 195} 196 197 198static nxt_int_t 199nxt_ruby_init_io(nxt_task_t *task) 200{ 201 VALUE rb, io_input, io_error; 202 203 io_input = nxt_ruby_stream_io_input_init(); 204 rb = Data_Wrap_Struct(io_input, 0, 0, &nxt_ruby_run_ctx); 205 206 nxt_ruby_io_input = rb_funcall(io_input, rb_intern("new"), 1, rb); 207 if (nxt_slow_path(nxt_ruby_io_input == Qnil)) { 208 nxt_alert(task, "Ruby: Failed to create environment 'rack.input' var"); 209 210 return NXT_ERROR; 211 } 212 213 io_error = nxt_ruby_stream_io_error_init(); 214 rb = Data_Wrap_Struct(io_error, 0, 0, &nxt_ruby_run_ctx); 215 216 nxt_ruby_io_error = rb_funcall(io_error, rb_intern("new"), 1, rb); 217 if (nxt_slow_path(nxt_ruby_io_error == Qnil)) { 218 nxt_alert(task, "Ruby: Failed to create environment 'rack.error' var"); 219 220 return NXT_ERROR; 221 } 222 223 rb_gc_register_address(&nxt_ruby_io_input); 224 rb_gc_register_address(&nxt_ruby_io_error); 225 226 return NXT_OK; 227} 228 229 230static VALUE 231nxt_ruby_rack_init(nxt_ruby_rack_init_t *rack_init) 232{ 233 int state; 234 VALUE rack, rackup, err; 235 236 rb_protect(nxt_ruby_require_rubygems, Qnil, &state); 237 if (nxt_slow_path(state != 0)) { 238 nxt_ruby_exception_log(rack_init->task, NXT_LOG_ALERT, 239 "Failed to require 'rubygems' package"); 240 return Qnil; 241 } 242 243 rb_protect(nxt_ruby_bundler_setup, Qnil, &state); 244 if (state != 0) { 245 err = rb_errinfo(); 246 247 if (rb_obj_is_kind_of(err, rb_eLoadError) == Qfalse) { 248 nxt_ruby_exception_log(rack_init->task, NXT_LOG_ALERT, 249 "Failed to require 'bundler/setup' package"); 250 return Qnil; 251 } 252 253 rb_set_errinfo(Qnil); 254 } 255 256 rb_protect(nxt_ruby_require_rack, Qnil, &state); 257 if (nxt_slow_path(state != 0)) { 258 nxt_ruby_exception_log(rack_init->task, NXT_LOG_ALERT, 259 "Failed to require 'rack' package"); 260 return Qnil; 261 } 262 263 rack = rb_const_get(rb_cObject, rb_intern("Rack")); 264 rack_init->builder = rb_const_get(rack, rb_intern("Builder")); 265 266 rackup = rb_protect(nxt_ruby_rack_parse_script, 267 (VALUE) (uintptr_t) rack_init, &state); 268 if (nxt_slow_path(TYPE(rackup) != T_ARRAY || state != 0)) { 269 nxt_ruby_exception_log(rack_init->task, NXT_LOG_ALERT, 270 "Failed to parse rack script"); 271 return Qnil; 272 } 273 274 if (nxt_slow_path(RARRAY_LEN(rackup) < 1)) { 275 nxt_alert(rack_init->task, "Ruby: Invalid rack config file"); 276 return Qnil; 277 } 278 279 return RARRAY_PTR(rackup)[0]; 280} 281 282 283static VALUE 284nxt_ruby_require_rubygems(VALUE arg) 285{ 286 return rb_funcall(rb_cObject, rb_intern("require"), 1, 287 rb_str_new2("rubygems")); 288} 289 290 291static VALUE 292nxt_ruby_bundler_setup(VALUE arg) 293{ 294 return rb_funcall(rb_cObject, rb_intern("require"), 1, 295 rb_str_new2("bundler/setup")); 296} 297 298 299static VALUE 300nxt_ruby_require_rack(VALUE arg) 301{ 302 return rb_funcall(rb_cObject, rb_intern("require"), 1, rb_str_new2("rack")); 303} 304 305 306static VALUE 307nxt_ruby_rack_parse_script(VALUE ctx) 308{ 309 VALUE script, res; 310 nxt_ruby_rack_init_t *rack_init; 311 312 rack_init = (nxt_ruby_rack_init_t *) (uintptr_t) ctx; 313 314 script = rb_str_new((const char *) rack_init->script->start, 315 (long) rack_init->script->length); 316 317 res = rb_funcall(rack_init->builder, rb_intern("parse_file"), 1, script); 318 319 rb_str_free(script); 320 321 return res; 322} 323 324 325static VALUE 326nxt_ruby_rack_env_create(VALUE arg) 327{ 328 VALUE hash_env, version; 329 330 hash_env = rb_hash_new(); 331 332 rb_hash_aset(hash_env, rb_str_new2("SERVER_SOFTWARE"), 333 rb_str_new((const char *) nxt_server.start, 334 (long) nxt_server.length)); 335 336 version = rb_ary_new(); 337 338 rb_ary_push(version, UINT2NUM(NXT_RUBY_RACK_API_VERSION_MAJOR)); 339 rb_ary_push(version, UINT2NUM(NXT_RUBY_RACK_API_VERSION_MINOR)); 340 341 rb_hash_aset(hash_env, rb_str_new2("rack.version"), version); 342 rb_hash_aset(hash_env, rb_str_new2("rack.input"), nxt_ruby_io_input); 343 rb_hash_aset(hash_env, rb_str_new2("rack.errors"), nxt_ruby_io_error); 344 rb_hash_aset(hash_env, rb_str_new2("rack.multithread"), Qfalse); 345 rb_hash_aset(hash_env, rb_str_new2("rack.multiprocess"), Qtrue); 346 rb_hash_aset(hash_env, rb_str_new2("rack.run_once"), Qfalse); 347 rb_hash_aset(hash_env, rb_str_new2("rack.hijack?"), Qfalse); 348 rb_hash_aset(hash_env, rb_str_new2("rack.hijack"), Qnil); 349 rb_hash_aset(hash_env, rb_str_new2("rack.hijack_io"), Qnil); 350 351 return hash_env; 352} 353 354 355static void 356nxt_ruby_request_handler(nxt_unit_request_info_t *req) 357{ 358 int state; 359 VALUE res; 360 361 nxt_ruby_run_ctx.req = req; 362 363 res = rb_protect(nxt_ruby_rack_app_run, Qnil, &state); 364 if (nxt_slow_path(res == Qnil || state != 0)) { 365 nxt_ruby_exception_log(NULL, NXT_LOG_ERR, 366 "Failed to run ruby script"); 367 } 368} 369 370 371static VALUE 372nxt_ruby_rack_app_run(VALUE arg) 373{ 374 int rc; 375 VALUE env, result; 376 nxt_int_t status; 377 378 env = rb_hash_dup(nxt_ruby_env); 379 380 rc = nxt_ruby_read_request(env); 381 if (nxt_slow_path(rc != NXT_UNIT_OK)) { 382 nxt_unit_req_alert(nxt_ruby_run_ctx.req, 383 "Ruby: Failed to process incoming request"); 384 385 goto fail; 386 } 387 388 result = rb_funcall(nxt_ruby_rackup, nxt_ruby_call, 1, env); 389 if (nxt_slow_path(TYPE(result) != T_ARRAY)) { 390 nxt_unit_req_error(nxt_ruby_run_ctx.req, 391 "Ruby: Invalid response format from application"); 392 393 goto fail; 394 } 395 396 if (nxt_slow_path(RARRAY_LEN(result) != 3)) { 397 nxt_unit_req_error(nxt_ruby_run_ctx.req, 398 "Ruby: Invalid response format from application. " 399 "Need 3 entries [Status, Headers, Body]"); 400 401 goto fail; 402 } 403 404 status = nxt_ruby_rack_result_status(result); 405 if (nxt_slow_path(status < 0)) { 406 nxt_unit_req_error(nxt_ruby_run_ctx.req, 407 "Ruby: Invalid response status from application."); 408 409 goto fail; 410 } 411 412 rc = nxt_ruby_rack_result_headers(result, status); 413 if (nxt_slow_path(rc != NXT_UNIT_OK)) { 414 goto fail; 415 } 416 417 rc = nxt_ruby_rack_result_body(result); 418 if (nxt_slow_path(rc != NXT_UNIT_OK)) { 419 goto fail; 420 } 421 422 nxt_unit_request_done(nxt_ruby_run_ctx.req, rc); 423 nxt_ruby_run_ctx.req = NULL; 424 425 rb_hash_delete(env, rb_obj_id(env)); 426 427 return result; 428 429fail: 430 431 nxt_unit_request_done(nxt_ruby_run_ctx.req, NXT_UNIT_ERROR); 432 nxt_ruby_run_ctx.req = NULL; 433 434 rb_hash_delete(env, rb_obj_id(env)); 435 436 return Qnil; 437} 438 439 440static int 441nxt_ruby_read_request(VALUE hash_env) 442{ 443 uint32_t i; 444 nxt_unit_field_t *f; 445 nxt_unit_request_t *r; 446 447 r = nxt_ruby_run_ctx.req->request; 448 449#define NL(S) (S), sizeof(S)-1 450 451 nxt_ruby_add_sptr(hash_env, NL("REQUEST_METHOD"), &r->method, 452 r->method_length); 453 nxt_ruby_add_sptr(hash_env, NL("REQUEST_URI"), &r->target, 454 r->target_length); 455 nxt_ruby_add_sptr(hash_env, NL("PATH_INFO"), &r->path, r->path_length); 456 nxt_ruby_add_sptr(hash_env, NL("QUERY_STRING"), &r->query, 457 r->query_length); 458 nxt_ruby_add_sptr(hash_env, NL("SERVER_PROTOCOL"), &r->version, 459 r->version_length); 460 nxt_ruby_add_sptr(hash_env, NL("REMOTE_ADDR"), &r->remote, 461 r->remote_length); 462 nxt_ruby_add_sptr(hash_env, NL("SERVER_ADDR"), &r->local, r->local_length); 463 464 nxt_ruby_add_sptr(hash_env, NL("SERVER_NAME"), &r->server_name, 465 r->server_name_length); 466 nxt_ruby_add_str(hash_env, NL("SERVER_PORT"), "80", 2); 467 468 rb_hash_aset(hash_env, rb_str_new2("rack.url_scheme"), 469 r->tls ? rb_str_new2("https") : rb_str_new2("http")); 470 471 for (i = 0; i < r->fields_count; i++) { 472 f = r->fields + i; 473 474 nxt_ruby_add_sptr(hash_env, nxt_unit_sptr_get(&f->name), f->name_length, 475 &f->value, f->value_length); 476 } 477 478 if (r->content_length_field != NXT_UNIT_NONE_FIELD) { 479 f = r->fields + r->content_length_field; 480 481 nxt_ruby_add_sptr(hash_env, NL("CONTENT_LENGTH"), 482 &f->value, f->value_length); 483 } 484 485 if (r->content_type_field != NXT_UNIT_NONE_FIELD) { 486 f = r->fields + r->content_type_field; 487 488 nxt_ruby_add_sptr(hash_env, NL("CONTENT_TYPE"), 489 &f->value, f->value_length); 490 } 491 492#undef NL 493 494 return NXT_UNIT_OK; 495} 496 497 498nxt_inline void 499nxt_ruby_add_sptr(VALUE hash_env, 500 const char *name, uint32_t name_len, nxt_unit_sptr_t *sptr, uint32_t len) 501{ 502 char *str; 503 504 str = nxt_unit_sptr_get(sptr); 505 506 rb_hash_aset(hash_env, rb_str_new(name, name_len), rb_str_new(str, len)); 507} 508 509 510nxt_inline void 511nxt_ruby_add_str(VALUE hash_env, 512 const char *name, uint32_t name_len, const char *str, uint32_t len) 513{ 514 rb_hash_aset(hash_env, rb_str_new(name, name_len), rb_str_new(str, len)); 515} 516 517 518static nxt_int_t 519nxt_ruby_rack_result_status(VALUE result) 520{ 521 VALUE status; 522 523 status = rb_ary_entry(result, 0); 524 525 if (TYPE(status) == T_FIXNUM) { 526 return FIX2INT(status); 527 } 528 529 if (TYPE(status) == T_STRING) { 530 return nxt_int_parse((u_char *) RSTRING_PTR(status), 531 RSTRING_LEN(status)); 532 } 533 534 nxt_unit_req_error(nxt_ruby_run_ctx.req, "Ruby: Invalid response 'status' " 535 "format from application"); 536 537 return -2; 538} 539 540 541typedef struct { 542 int rc; 543 uint32_t fields; 544 uint32_t size; 545} nxt_ruby_headers_info_t; 546 547 548static int 549nxt_ruby_rack_result_headers(VALUE result, nxt_int_t status) 550{ 551 int rc; 552 VALUE headers; 553 nxt_ruby_headers_info_t headers_info; 554 555 headers = rb_ary_entry(result, 1); 556 if (nxt_slow_path(TYPE(headers) != T_HASH)) { 557 nxt_unit_req_error(nxt_ruby_run_ctx.req, 558 "Ruby: Invalid response 'headers' format from " 559 "application"); 560 561 return NXT_UNIT_ERROR; 562 } 563 564 rc = NXT_UNIT_OK; 565 566 headers_info.rc = NXT_UNIT_OK; 567 headers_info.fields = 0; 568 headers_info.size = 0; 569 570 rb_hash_foreach(headers, nxt_ruby_hash_info, 571 (VALUE) (uintptr_t) &headers_info); 572 if (nxt_slow_path(headers_info.rc != NXT_UNIT_OK)) { 573 return headers_info.rc; 574 } 575 576 rc = nxt_unit_response_init(nxt_ruby_run_ctx.req, status, 577 headers_info.fields, headers_info.size); 578 if (nxt_slow_path(rc != NXT_UNIT_OK)) { 579 return rc; 580 } 581 582 rb_hash_foreach(headers, nxt_ruby_hash_add, (VALUE) (uintptr_t) &rc); 583 584 return rc; 585} 586 587 588static int 589nxt_ruby_hash_info(VALUE r_key, VALUE r_value, VALUE arg) 590{ 591 const char *value, *value_end, *pos; 592 nxt_ruby_headers_info_t *headers_info; 593 594 headers_info = (void *) (uintptr_t) arg; 595 596 if (nxt_slow_path(TYPE(r_key) != T_STRING)) { 597 nxt_unit_req_error(nxt_ruby_run_ctx.req, 598 "Ruby: Wrong header entry 'key' from application"); 599 600 goto fail; 601 } 602 603 if (nxt_slow_path(TYPE(r_value) != T_STRING)) { 604 nxt_unit_req_error(nxt_ruby_run_ctx.req, 605 "Ruby: Wrong header entry 'value' from application"); 606 607 goto fail; 608 } 609 610 value = RSTRING_PTR(r_value); 611 value_end = value + RSTRING_LEN(r_value); 612 613 pos = value; 614 615 for ( ;; ) { 616 pos = strchr(pos, '\n'); 617 618 if (pos == NULL) { 619 break; 620 } 621 622 headers_info->fields++; 623 headers_info->size += RSTRING_LEN(r_key) + (pos - value); 624 625 pos++; 626 value = pos; 627 } 628 629 if (value <= value_end) { 630 headers_info->fields++; 631 headers_info->size += RSTRING_LEN(r_key) + (value_end - value); 632 } 633 634 return ST_CONTINUE; 635 636fail: 637 638 headers_info->rc = NXT_UNIT_ERROR; 639 640 return ST_STOP; 641} 642 643 644static int 645nxt_ruby_hash_add(VALUE r_key, VALUE r_value, VALUE arg) 646{ 647 int *rc; 648 uint32_t key_len; 649 const char *value, *value_end, *pos; 650 651 rc = (int *) (uintptr_t) arg; 652 653 value = RSTRING_PTR(r_value); 654 value_end = value + RSTRING_LEN(r_value); 655 656 key_len = RSTRING_LEN(r_key); 657 658 pos = value; 659 660 for ( ;; ) { 661 pos = strchr(pos, '\n'); 662 663 if (pos == NULL) { 664 break; 665 } 666 667 *rc = nxt_unit_response_add_field(nxt_ruby_run_ctx.req, 668 RSTRING_PTR(r_key), key_len, 669 value, pos - value); 670 if (nxt_slow_path(*rc != NXT_UNIT_OK)) { 671 goto fail; 672 } 673 674 pos++; 675 value = pos; 676 } 677 678 if (value <= value_end) { 679 *rc = nxt_unit_response_add_field(nxt_ruby_run_ctx.req, 680 RSTRING_PTR(r_key), key_len, 681 value, value_end - value); 682 if (nxt_slow_path(*rc != NXT_UNIT_OK)) { 683 goto fail; 684 } 685 } 686 687 return ST_CONTINUE; 688 689fail: 690 691 *rc = NXT_UNIT_ERROR; 692 693 return ST_STOP; 694} 695 696 697static int 698nxt_ruby_rack_result_body(VALUE result) 699{ 700 int rc; 701 VALUE fn, body; 702 703 body = rb_ary_entry(result, 2); 704 705 if (rb_respond_to(body, rb_intern("to_path"))) { 706 707 fn = rb_funcall(body, rb_intern("to_path"), 0); 708 if (nxt_slow_path(TYPE(fn) != T_STRING)) { 709 nxt_unit_req_error(nxt_ruby_run_ctx.req, 710 "Ruby: Failed to get 'body' file path from " 711 "application"); 712 713 return NXT_UNIT_ERROR; 714 } 715 716 rc = nxt_ruby_rack_result_body_file_write(fn); 717 if (nxt_slow_path(rc != NXT_UNIT_OK)) { 718 return rc; 719 } 720 721 } else if (rb_respond_to(body, rb_intern("each"))) { 722 rb_block_call(body, rb_intern("each"), 0, 0, 723 nxt_ruby_rack_result_body_each, 0); 724 725 } else { 726 nxt_unit_req_error(nxt_ruby_run_ctx.req, 727 "Ruby: Invalid response 'body' format " 728 "from application"); 729 730 return NXT_UNIT_ERROR; 731 } 732 733 if (rb_respond_to(body, rb_intern("close"))) { 734 rb_funcall(body, rb_intern("close"), 0); 735 } 736 737 return NXT_UNIT_OK; 738} 739 740 741typedef struct { 742 int fd; 743 off_t pos; 744 off_t rest; 745} nxt_ruby_rack_file_t; 746 747 748static ssize_t 749nxt_ruby_rack_file_read(nxt_unit_read_info_t *read_info, void *dst, size_t size) 750{ 751 ssize_t res; 752 nxt_ruby_rack_file_t *file; 753 754 file = read_info->data; 755 756 size = nxt_min(size, (size_t) file->rest); 757 758 res = pread(file->fd, dst, size, file->pos); 759 760 if (res >= 0) { 761 file->pos += res; 762 file->rest -= res; 763 764 if (size > (size_t) res) { 765 file->rest = 0; 766 } 767 } 768 769 read_info->eof = file->rest == 0; 770 771 return res; 772} 773 774 775static int 776nxt_ruby_rack_result_body_file_write(VALUE filepath) 777{ 778 int fd, rc; 779 struct stat finfo; 780 nxt_ruby_rack_file_t ruby_file; 781 nxt_unit_read_info_t read_info; 782 783 fd = open(RSTRING_PTR(filepath), O_RDONLY, 0); 784 if (nxt_slow_path(fd == -1)) { 785 nxt_unit_req_error(nxt_ruby_run_ctx.req, 786 "Ruby: Failed to open content file \"%s\": %s (%d)", 787 RSTRING_PTR(filepath), strerror(errno), errno); 788 789 return NXT_UNIT_ERROR; 790 } 791 792 rc = fstat(fd, &finfo); 793 if (nxt_slow_path(rc == -1)) { 794 nxt_unit_req_error(nxt_ruby_run_ctx.req, 795 "Ruby: Content file fstat(\"%s\") failed: %s (%d)", 796 RSTRING_PTR(filepath), strerror(errno), errno); 797 798 close(fd); 799 800 return NXT_UNIT_ERROR; 801 } 802 803 ruby_file.fd = fd; 804 ruby_file.pos = 0; 805 ruby_file.rest = finfo.st_size; 806 807 read_info.read = nxt_ruby_rack_file_read; 808 read_info.eof = ruby_file.rest == 0; 809 read_info.buf_size = ruby_file.rest; 810 read_info.data = &ruby_file; 811 812 rc = nxt_unit_response_write_cb(nxt_ruby_run_ctx.req, &read_info); 813 if (nxt_slow_path(rc != NXT_UNIT_OK)) { 814 nxt_unit_req_error(nxt_ruby_run_ctx.req, 815 "Ruby: Failed to write content file."); 816 } 817 818 close(fd); 819 820 return rc; 821} 822 823 824static VALUE 825nxt_ruby_rack_result_body_each(VALUE body, VALUE arg, int argc, 826 const VALUE *argv, VALUE blockarg) 827{ 828 int rc; 829 830 if (TYPE(body) != T_STRING) { 831 return Qnil; 832 } 833 834 rc = nxt_unit_response_write(nxt_ruby_run_ctx.req, RSTRING_PTR(body), 835 RSTRING_LEN(body)); 836 if (nxt_slow_path(rc != NXT_UNIT_OK)) { 837 nxt_unit_req_error(nxt_ruby_run_ctx.req, 838 "Ruby: Failed to write 'body' from application"); 839 } 840 841 return Qnil; 842} 843 844 845static void 846nxt_ruby_exception_log(nxt_task_t *task, uint32_t level, const char *desc) 847{ 848 int i; 849 VALUE err, ary, eclass, msg; 850 851 if (task != NULL) { 852 nxt_log(task, level, "Ruby: %s", desc); 853 854 } else { 855 nxt_unit_log(nxt_ruby_run_ctx.unit_ctx, level, "Ruby: %s", desc); 856 } 857 858 err = rb_errinfo(); 859 if (nxt_slow_path(err == Qnil)) { 860 return; 861 } 862 863 ary = rb_funcall(err, rb_intern("backtrace"), 0); 864 if (nxt_slow_path(RARRAY_LEN(ary) == 0)) { 865 return; 866 } 867 868 eclass = rb_class_name(rb_class_of(err)); 869 msg = rb_funcall(err, rb_intern("message"), 0); 870 871 if (task != NULL) { 872 nxt_log(task, level, "Ruby: %s: %s (%s)", 873 RSTRING_PTR(RARRAY_PTR(ary)[0]), 874 RSTRING_PTR(msg), RSTRING_PTR(eclass)); 875 876 } else { 877 nxt_unit_log(nxt_ruby_run_ctx.unit_ctx, level, "Ruby: %s: %s (%s)", 878 RSTRING_PTR(RARRAY_PTR(ary)[0]), 879 RSTRING_PTR(msg), RSTRING_PTR(eclass)); 880 } 881 882 for (i = 1; i < RARRAY_LEN(ary); i++) { 883 if (task != NULL) { 884 nxt_log(task, level, "from %s", RSTRING_PTR(RARRAY_PTR(ary)[i])); 885 886 } else { 887 nxt_unit_log(nxt_ruby_run_ctx.unit_ctx, level, "from %s", 888 RSTRING_PTR(RARRAY_PTR(ary)[i])); 889 } 890 } 891} 892 893 894static void 895nxt_ruby_atexit(void) 896{ 897 rb_gc_unregister_address(&nxt_ruby_io_input); 898 rb_gc_unregister_address(&nxt_ruby_io_error); 899 900 rb_gc_unregister_address(&nxt_ruby_rackup); 901 rb_gc_unregister_address(&nxt_ruby_call); 902 rb_gc_unregister_address(&nxt_ruby_env); 903 904 ruby_cleanup(0); 905}
|