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