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