/* * Copyright (C) Alexander Borisov * Copyright (C) NGINX, Inc. */ #include #define NXT_RUBY_RACK_API_VERSION_MAJOR 1 #define NXT_RUBY_RACK_API_VERSION_MINOR 3 #define NXT_RUBY_STRINGIZE_HELPER(x) #x #define NXT_RUBY_STRINGIZE(x) NXT_RUBY_STRINGIZE_HELPER(x) #define NXT_RUBY_LIB_VERSION \ NXT_RUBY_STRINGIZE(RUBY_API_VERSION_MAJOR) \ "." NXT_RUBY_STRINGIZE(RUBY_API_VERSION_MINOR) \ "." NXT_RUBY_STRINGIZE(RUBY_API_VERSION_TEENY) typedef struct { nxt_task_t *task; nxt_str_t *script; VALUE builder; } nxt_ruby_rack_init_t; static nxt_int_t nxt_ruby_init(nxt_task_t *task, nxt_common_app_conf_t *conf); static VALUE nxt_ruby_init_basic(VALUE arg); static nxt_int_t nxt_ruby_init_io(nxt_task_t *task); static VALUE nxt_ruby_rack_init(nxt_ruby_rack_init_t *rack_init); static VALUE nxt_ruby_require_rubygems(VALUE arg); static VALUE nxt_ruby_require_rack(VALUE arg); static VALUE nxt_ruby_rack_parse_script(VALUE ctx); static VALUE nxt_ruby_rack_env_create(VALUE arg); static nxt_int_t nxt_ruby_run(nxt_task_t *task, nxt_app_rmsg_t *rmsg, nxt_app_wmsg_t *wmsg); static VALUE nxt_ruby_rack_app_run(VALUE arg); static nxt_int_t nxt_ruby_read_request(nxt_ruby_run_ctx_t *run_ctx, VALUE hash_env); nxt_inline nxt_int_t nxt_ruby_read_add_env(nxt_task_t *task, nxt_app_rmsg_t *rmsg, VALUE hash_env, const char *name, nxt_str_t *str); static nxt_int_t nxt_ruby_rack_result_status(VALUE result); nxt_inline nxt_int_t nxt_ruby_write(nxt_task_t *task, nxt_app_wmsg_t *wmsg, const u_char *data, size_t len, nxt_bool_t flush, nxt_bool_t last); static nxt_int_t nxt_ruby_rack_result_headers(VALUE result); static int nxt_ruby_hash_foreach(VALUE r_key, VALUE r_value, VALUE arg); static nxt_int_t nxt_ruby_head_send_part(const char *key, size_t key_size, const char *value, size_t value_size); static nxt_int_t nxt_ruby_rack_result_body(VALUE result); static nxt_int_t nxt_ruby_rack_result_body_file_write(VALUE filepath); static VALUE nxt_ruby_rack_result_body_each(VALUE body); static void nxt_ruby_exception_log(nxt_task_t *task, uint32_t level, const char *desc); static void nxt_ruby_atexit(nxt_task_t *task); static uint32_t compat[] = { NXT_VERNUM, NXT_DEBUG, }; static VALUE nxt_ruby_rackup; static VALUE nxt_ruby_call; static VALUE nxt_ruby_env; static VALUE nxt_ruby_io_input; static VALUE nxt_ruby_io_error; static nxt_ruby_run_ctx_t nxt_ruby_run_ctx; NXT_EXPORT nxt_application_module_t nxt_app_module = { sizeof(compat), compat, nxt_string("ruby"), ruby_version, nxt_ruby_init, nxt_ruby_run, nxt_ruby_atexit, }; static nxt_int_t nxt_ruby_init(nxt_task_t *task, nxt_common_app_conf_t *conf) { int state; VALUE dummy, res; nxt_ruby_rack_init_t rack_init; ruby_init(); Init_stack(&dummy); ruby_init_loadpath(); ruby_script("NGINX_Unit"); rack_init.task = task; rack_init.script = &conf->u.ruby.script; res = rb_protect(nxt_ruby_init_basic, (VALUE) (uintptr_t) &rack_init, &state); if (nxt_slow_path(res == Qnil || state != 0)) { nxt_ruby_exception_log(task, NXT_LOG_ALERT, "Failed to init basic variables"); return NXT_ERROR; } nxt_ruby_rackup = nxt_ruby_rack_init(&rack_init); if (nxt_slow_path(nxt_ruby_rackup == Qnil)) { return NXT_ERROR; } nxt_ruby_call = rb_intern("call"); if (nxt_slow_path(nxt_ruby_call == Qnil)) { nxt_alert(task, "Ruby: Unable to find rack entry point"); return NXT_ERROR; } nxt_ruby_env = rb_protect(nxt_ruby_rack_env_create, Qnil, &state); if (nxt_slow_path(state != 0)) { nxt_ruby_exception_log(task, NXT_LOG_ALERT, "Failed to create 'environ' variable"); return NXT_ERROR; } rb_gc_register_address(&nxt_ruby_rackup); rb_gc_register_address(&nxt_ruby_call); rb_gc_register_address(&nxt_ruby_env); return NXT_OK; } static VALUE nxt_ruby_init_basic(VALUE arg) { int state; nxt_int_t rc; nxt_ruby_rack_init_t *rack_init; rack_init = (nxt_ruby_rack_init_t *) (uintptr_t) arg; state = rb_enc_find_index("encdb"); if (nxt_slow_path(state == 0)) { nxt_alert(rack_init->task, "Ruby: Failed to find encoding index 'encdb'"); return Qnil; } rb_funcall(rb_cObject, rb_intern("require"), 1, rb_str_new2("enc/trans/transdb")); rc = nxt_ruby_init_io(rack_init->task); if (nxt_slow_path(rc != NXT_OK)) { return Qnil; } return arg; } static nxt_int_t nxt_ruby_init_io(nxt_task_t *task) { VALUE rb, io_input, io_error; io_input = nxt_ruby_stream_io_input_init(); rb = Data_Wrap_Struct(io_input, 0, 0, &nxt_ruby_run_ctx); nxt_ruby_io_input = rb_funcall(io_input, rb_intern("new"), 1, rb); if (nxt_slow_path(nxt_ruby_io_input == Qnil)) { nxt_alert(task, "Ruby: Failed to create environment 'rack.input' var"); return NXT_ERROR; } io_error = nxt_ruby_stream_io_error_init(); rb = Data_Wrap_Struct(io_error, 0, 0, &nxt_ruby_run_ctx); nxt_ruby_io_error = rb_funcall(io_error, rb_intern("new"), 1, rb); if (nxt_slow_path(nxt_ruby_io_error == Qnil)) { nxt_alert(task, "Ruby: Failed to create environment 'rack.error' var"); return NXT_ERROR; } rb_gc_register_address(&nxt_ruby_io_input); rb_gc_register_address(&nxt_ruby_io_error); return NXT_OK; } static VALUE nxt_ruby_rack_init(nxt_ruby_rack_init_t *rack_init) { int state; VALUE rack, rackup; rb_protect(nxt_ruby_require_rubygems, Qnil, &state); if (nxt_slow_path(state != 0)) { nxt_ruby_exception_log(rack_init->task, NXT_LOG_ALERT, "Failed to require 'rubygems' package"); return Qnil; } rb_protect(nxt_ruby_require_rack, Qnil, &state); if (nxt_slow_path(state != 0)) { nxt_ruby_exception_log(rack_init->task, NXT_LOG_ALERT, "Failed to require 'rack' package"); return Qnil; } rack = rb_const_get(rb_cObject, rb_intern("Rack")); rack_init->builder = rb_const_get(rack, rb_intern("Builder")); rackup = rb_protect(nxt_ruby_rack_parse_script, (VALUE) (uintptr_t) rack_init, &state); if (nxt_slow_path(TYPE(rackup) != T_ARRAY || state != 0)) { nxt_ruby_exception_log(rack_init->task, NXT_LOG_ALERT, "Failed to parse rack script"); return Qnil; } if (nxt_slow_path(RARRAY_LEN(rackup) < 1)) { nxt_alert(rack_init->task, "Ruby: Invalid rack config file"); return Qnil; } return RARRAY_PTR(rackup)[0]; } static VALUE nxt_ruby_require_rubygems(VALUE arg) { return rb_funcall(rb_cObject, rb_intern("require"), 1, rb_str_new2("rubygems")); } static VALUE nxt_ruby_require_rack(VALUE arg) { return rb_funcall(rb_cObject, rb_intern("require"), 1, rb_str_new2("rack")); } static VALUE nxt_ruby_rack_parse_script(VALUE ctx) { VALUE script, res; nxt_ruby_rack_init_t *rack_init; rack_init = (nxt_ruby_rack_init_t *) (uintptr_t) ctx; script = rb_str_new((const char *) rack_init->script->start, (long) rack_init->script->length); res = rb_funcall(rack_init->builder, rb_intern("parse_file"), 1, script); rb_str_free(script); return res; } static VALUE nxt_ruby_rack_env_create(VALUE arg) { VALUE hash_env, version; hash_env = rb_hash_new(); version = rb_ary_new(); rb_ary_push(version, UINT2NUM(NXT_RUBY_RACK_API_VERSION_MAJOR)); rb_ary_push(version, UINT2NUM(NXT_RUBY_RACK_API_VERSION_MINOR)); rb_hash_aset(hash_env, rb_str_new2("rack.version"), version); rb_hash_aset(hash_env, rb_str_new2("rack.url_scheme"), rb_str_new2("http")); rb_hash_aset(hash_env, rb_str_new2("rack.input"), nxt_ruby_io_input); rb_hash_aset(hash_env, rb_str_new2("rack.errors"), nxt_ruby_io_error); rb_hash_aset(hash_env, rb_str_new2("rack.multithread"), Qfalse); rb_hash_aset(hash_env, rb_str_new2("rack.multiprocess"), Qtrue); rb_hash_aset(hash_env, rb_str_new2("rack.run_once"), Qfalse); rb_hash_aset(hash_env, rb_str_new2("rack.hijack?"), Qfalse); rb_hash_aset(hash_env, rb_str_new2("rack.hijack"), Qnil); rb_hash_aset(hash_env, rb_str_new2("rack.hijack_io"), Qnil); return hash_env; } static nxt_int_t nxt_ruby_run(nxt_task_t *task, nxt_app_rmsg_t *rmsg, nxt_app_wmsg_t *wmsg) { int state; VALUE res; nxt_ruby_run_ctx.task = task; nxt_ruby_run_ctx.rmsg = rmsg; nxt_ruby_run_ctx.wmsg = wmsg; res = rb_protect(nxt_ruby_rack_app_run, Qnil, &state); if (nxt_slow_path(state != 0)) { nxt_ruby_exception_log(nxt_ruby_run_ctx.task, NXT_LOG_ERR, "Failed to run ruby script"); return NXT_ERROR; } if (nxt_slow_path(res == Qnil)) { return NXT_ERROR; } return NXT_OK; } static VALUE nxt_ruby_rack_app_run(VALUE arg) { VALUE env, result; nxt_int_t rc; env = rb_hash_dup(nxt_ruby_env); rc = nxt_ruby_read_request(&nxt_ruby_run_ctx, env); if (nxt_slow_path(rc != NXT_OK)) { nxt_alert(nxt_ruby_run_ctx.task, "Ruby: Failed to process incoming request"); goto fail; } result = rb_funcall(nxt_ruby_rackup, nxt_ruby_call, 1, env); if (nxt_slow_path(TYPE(result) != T_ARRAY)) { nxt_log(nxt_ruby_run_ctx.task, NXT_LOG_ERR, "Ruby: Invalid response format from application"); goto fail; } if (nxt_slow_path(RARRAY_LEN(result) != 3)) { nxt_log(nxt_ruby_run_ctx.task, NXT_LOG_ERR, "Ruby: Invalid response format from application. " "Need 3 entries [Status, Headers, Body]"); goto fail; } rc = nxt_ruby_rack_result_status(result); if (nxt_slow_path(rc != NXT_OK)) { goto fail; } rc = nxt_ruby_rack_result_headers(result); if (nxt_slow_path(rc != NXT_OK)) { goto fail; } rc = nxt_ruby_rack_result_body(result); if (nxt_slow_path(rc != NXT_OK)) { goto fail; } rc = nxt_app_msg_flush(nxt_ruby_run_ctx.task, nxt_ruby_run_ctx.wmsg, 1); if (nxt_slow_path(rc != NXT_OK)) { goto fail; } rb_hash_delete(env, rb_obj_id(env)); return result; fail: rb_hash_delete(env, rb_obj_id(env)); return Qnil; } static nxt_int_t nxt_ruby_read_request(nxt_ruby_run_ctx_t *run_ctx, VALUE hash_env) { u_char *colon; size_t query_size; nxt_int_t rc; nxt_str_t str, value, path, target; nxt_str_t host, server_name, server_port; nxt_task_t *task; nxt_app_rmsg_t *rmsg; static nxt_str_t def_host = nxt_string("localhost"); static nxt_str_t def_port = nxt_string("80"); task = run_ctx->task; rmsg = run_ctx->rmsg; rc = nxt_ruby_read_add_env(task, rmsg, hash_env, "REQUEST_METHOD", &str); if (nxt_slow_path(rc != NXT_OK)) { return NXT_ERROR; } rc = nxt_ruby_read_add_env(task, rmsg, hash_env, "REQUEST_URI", &target); if (nxt_slow_path(rc != NXT_OK)) { return NXT_ERROR; } rc = nxt_app_msg_read_str(task, rmsg, &path); if (nxt_slow_path(rc != NXT_OK)) { return NXT_ERROR; } rc = nxt_app_msg_read_size(task, rmsg, &query_size); if (nxt_slow_path(rc != NXT_OK)) { return NXT_ERROR; } if (path.start == NULL || path.length == 0) { path = target; } rb_hash_aset(hash_env, rb_str_new2("PATH_INFO"), rb_str_new((const char *) path.start, (long) path.length)); if (query_size > 0) { query_size--; if (nxt_slow_path(target.length < query_size)) { return NXT_ERROR; } str.start = &target.start[query_size]; str.length = target.length - query_size; rb_hash_aset(hash_env, rb_str_new2("QUERY_STRING"), rb_str_new((const char *) str.start, (long) str.length)); } rc = nxt_ruby_read_add_env(task, rmsg, hash_env, "SERVER_PROTOCOL", &str); if (nxt_slow_path(rc != NXT_OK)) { return NXT_ERROR; } rc = nxt_ruby_read_add_env(task, rmsg, hash_env, "REMOTE_ADDR", &str); if (nxt_slow_path(rc != NXT_OK)) { return NXT_ERROR; } rc = nxt_ruby_read_add_env(task, rmsg, hash_env, "SERVER_ADDR", &str); if (nxt_slow_path(rc != NXT_OK)) { return NXT_ERROR; } rc = nxt_app_msg_read_str(task, rmsg, &host); if (nxt_slow_path(rc != NXT_OK)) { return NXT_ERROR; } if (host.length == 0) { host = def_host; } colon = nxt_memchr(host.start, ':', host.length); server_name = host; if (colon != NULL) { server_name.length = colon - host.start; server_port.start = colon + 1; server_port.length = host.length - server_name.length - 1; } else { server_port = def_port; } rb_hash_aset(hash_env, rb_str_new2("SERVER_NAME"), rb_str_new((const char *) server_name.start, (long) server_name.length)); rb_hash_aset(hash_env, rb_str_new2("SERVER_PORT"), rb_str_new((const char *) server_port.start, (long) server_port.length)); rc = nxt_ruby_read_add_env(task, rmsg, hash_env, "CONTENT_TYPE", &str); if (nxt_slow_path(rc != NXT_OK)) { return NXT_ERROR; } rc = nxt_ruby_read_add_env(task, rmsg, hash_env, "CONTENT_LENGTH", &str); if (nxt_slow_path(rc != NXT_OK)) { return NXT_ERROR; } for ( ;; ) { rc = nxt_app_msg_read_str(task, rmsg, &str); if (nxt_slow_path(rc != NXT_OK)) { return NXT_ERROR; } if (nxt_slow_path(str.length == 0)) { break; } rc = nxt_app_msg_read_str(task, rmsg, &value); if (nxt_slow_path(rc != NXT_OK)) { return NXT_ERROR; } rb_hash_aset(hash_env, rb_str_new((char *) str.start, (long) str.length), rb_str_new((const char *) value.start, (long) value.length)); } rc = nxt_app_msg_read_size(task, rmsg, &run_ctx->body_preread_size); if (nxt_slow_path(rc != NXT_OK)) { return NXT_ERROR; } return NXT_OK; } nxt_inline nxt_int_t nxt_ruby_read_add_env(nxt_task_t *task, nxt_app_rmsg_t *rmsg, VALUE hash_env, const char *name, nxt_str_t *str) { nxt_int_t rc; rc = nxt_app_msg_read_str(task, rmsg, str); if (nxt_slow_path(rc != NXT_OK)) { return rc; } if (str->start == NULL) { rb_hash_aset(hash_env, rb_str_new2(name), Qnil); return NXT_OK; } rb_hash_aset(hash_env, rb_str_new2(name), rb_str_new((const char *) str->start, (long) str->length)); return NXT_OK; } static nxt_int_t nxt_ruby_rack_result_status(VALUE result) { VALUE status; u_char *p; size_t len; nxt_int_t rc; u_char buf[3]; status = rb_ary_entry(result, 0); if (TYPE(status) == T_FIXNUM) { nxt_sprintf(buf, buf + 3, "%03d", FIX2INT(status)); p = buf; len = 3; } else if (TYPE(status) == T_STRING) { p = (u_char *) RSTRING_PTR(status); len = RSTRING_LEN(status); } else { nxt_log(nxt_ruby_run_ctx.task, NXT_LOG_ERR, "Ruby: Invalid response 'status' format from application"); return NXT_ERROR; } rc = nxt_ruby_write(nxt_ruby_run_ctx.task, nxt_ruby_run_ctx.wmsg, (u_char *) "Status: ", (sizeof("Status: ") - 1), 0, 0); if (nxt_slow_path(rc != NXT_OK)) { return NXT_ERROR; } rc = nxt_ruby_write(nxt_ruby_run_ctx.task, nxt_ruby_run_ctx.wmsg, p, len, 0, 0); if (nxt_slow_path(rc != NXT_OK)) { return NXT_ERROR; } rc = nxt_ruby_write(nxt_ruby_run_ctx.task, nxt_ruby_run_ctx.wmsg, (u_char *) "\r\n", (sizeof("\r\n") - 1), 0, 0); if (nxt_slow_path(rc != NXT_OK)) { return NXT_ERROR; } return NXT_OK; } nxt_inline nxt_int_t nxt_ruby_write(nxt_task_t *task, nxt_app_wmsg_t *wmsg, const u_char *data, size_t len, nxt_bool_t flush, nxt_bool_t last) { nxt_int_t rc; rc = nxt_app_msg_write_raw(task, wmsg, data, len); if (nxt_slow_path(rc != NXT_OK)) { return rc; } if (flush || last) { rc = nxt_app_msg_flush(task, wmsg, last); } return rc; } static nxt_int_t nxt_ruby_rack_result_headers(VALUE result) { VALUE headers; nxt_int_t rc; headers = rb_ary_entry(result, 1); if (nxt_slow_path(TYPE(headers) != T_HASH)) { nxt_log(nxt_ruby_run_ctx.task, NXT_LOG_ERR, "Ruby: Invalid response 'headers' format from application"); return NXT_ERROR; } rc = NXT_OK; rb_hash_foreach(headers, nxt_ruby_hash_foreach, (VALUE) (uintptr_t) &rc); if (nxt_slow_path(rc != NXT_OK)) { return NXT_ERROR; } rc = nxt_ruby_write(nxt_ruby_run_ctx.task, nxt_ruby_run_ctx.wmsg, (u_char *) "\r\n", (sizeof("\r\n") - 1), 0, 0); if (nxt_slow_path(rc != NXT_OK)) { return NXT_ERROR; } return NXT_OK; } static int nxt_ruby_hash_foreach(VALUE r_key, VALUE r_value, VALUE arg) { nxt_int_t rc, *rc_p; const char *value, *value_end, *pos; rc_p = (nxt_int_t *) (uintptr_t) arg; if (nxt_slow_path(TYPE(r_key) != T_STRING)) { nxt_log(nxt_ruby_run_ctx.task, NXT_LOG_ERR, "Ruby: Wrong header entry 'key' from application"); goto fail; } if (nxt_slow_path(TYPE(r_value) != T_STRING)) { nxt_log(nxt_ruby_run_ctx.task, NXT_LOG_ERR, "Ruby: Wrong header entry 'value' from application"); goto fail; } value = RSTRING_PTR(r_value); value_end = value + RSTRING_LEN(r_value); pos = value; for ( ;; ) { pos = strchr(pos, '\n'); if (pos == NULL) { break; } rc = nxt_ruby_head_send_part(RSTRING_PTR(r_key), RSTRING_LEN(r_key), value, pos - value); if (nxt_slow_path(rc != NXT_OK)) { goto fail; } pos++; value = pos; } if (value <= value_end) { rc = nxt_ruby_head_send_part(RSTRING_PTR(r_key), RSTRING_LEN(r_key), value, value_end - value); if (nxt_slow_path(rc != NXT_OK)) { goto fail; } } *rc_p = NXT_OK; return ST_CONTINUE; fail: *rc_p = NXT_ERROR; return ST_STOP; } static nxt_int_t nxt_ruby_head_send_part(const char *key, size_t key_size, const char *value, size_t value_size) { nxt_int_t rc; rc = nxt_app_msg_write_raw(nxt_ruby_run_ctx.task, nxt_ruby_run_ctx.wmsg, (u_char *) key, key_size); if (nxt_slow_path(rc != NXT_OK)) { return rc; } rc = nxt_app_msg_write_raw(nxt_ruby_run_ctx.task, nxt_ruby_run_ctx.wmsg, (u_char *) ": ", (sizeof(": ") - 1)); if (nxt_slow_path(rc != NXT_OK)) { return rc; } rc = nxt_app_msg_write_raw(nxt_ruby_run_ctx.task, nxt_ruby_run_ctx.wmsg, (u_char *) value, value_size); if (nxt_slow_path(rc != NXT_OK)) { return rc; } return nxt_app_msg_write_raw(nxt_ruby_run_ctx.task, nxt_ruby_run_ctx.wmsg, (u_char *) "\r\n", (sizeof("\r\n") - 1)); } static nxt_int_t nxt_ruby_rack_result_body(VALUE result) { VALUE fn, body; nxt_int_t rc; body = rb_ary_entry(result, 2); if (rb_respond_to(body, rb_intern("to_path"))) { fn = rb_funcall(body, rb_intern("to_path"), 0); if (nxt_slow_path(TYPE(fn) != T_STRING)) { nxt_log(nxt_ruby_run_ctx.task, NXT_LOG_ERR, "Ruby: Failed to get 'body' file path from application"); return NXT_ERROR; } rc = nxt_ruby_rack_result_body_file_write(fn); if (nxt_slow_path(rc != NXT_OK)) { return NXT_ERROR; } } else if (rb_respond_to(body, rb_intern("each"))) { rb_iterate(rb_each, body, nxt_ruby_rack_result_body_each, 0); } else { nxt_log(nxt_ruby_run_ctx.task, NXT_LOG_ERR, "Ruby: Invalid response 'body' format from application"); return NXT_ERROR; } if (rb_respond_to(body, rb_intern("close"))) { rb_funcall(body, rb_intern("close"), 0); } return NXT_OK; } static nxt_int_t nxt_ruby_rack_result_body_file_write(VALUE filepath) { size_t len; ssize_t n; nxt_off_t rest; nxt_int_t rc; nxt_file_t file; nxt_file_info_t finfo; u_char buf[8192]; nxt_memzero(&file, sizeof(nxt_file_t)); file.name = (nxt_file_name_t *) RSTRING_PTR(filepath); rc = nxt_file_open(nxt_ruby_run_ctx.task, &file, NXT_FILE_RDONLY, NXT_FILE_OPEN, 0); if (nxt_slow_path(rc != NXT_OK)) { nxt_log(nxt_ruby_run_ctx.task, NXT_LOG_ERR, "Ruby: Failed to open 'body' file: %s", (const char *) file.name); return NXT_ERROR; } rc = nxt_file_info(&file, &finfo); if (nxt_slow_path(rc != NXT_OK)) { nxt_log(nxt_ruby_run_ctx.task, NXT_LOG_ERR, "Ruby: Failed to get 'body' file information: %s", (const char *) file.name); goto fail; } rest = nxt_file_size(&finfo); while (rest != 0) { len = nxt_min(rest, (nxt_off_t) sizeof(buf)); n = nxt_file_read(&file, buf, len, nxt_file_size(&finfo) - rest); if (nxt_slow_path(n != (ssize_t) len)) { nxt_log(nxt_ruby_run_ctx.task, NXT_LOG_ERR, "Ruby: Failed to read 'body' file"); goto fail; } rest -= len; rc = nxt_app_msg_write_raw(nxt_ruby_run_ctx.task, nxt_ruby_run_ctx.wmsg, buf, len); if (nxt_slow_path(rc != NXT_OK)) { nxt_log(nxt_ruby_run_ctx.task, NXT_LOG_ERR, "Ruby: Failed to write 'body' from application"); goto fail; } } nxt_file_close(nxt_ruby_run_ctx.task, &file); return NXT_OK; fail: nxt_file_close(nxt_ruby_run_ctx.task, &file); return NXT_ERROR; } static VALUE nxt_ruby_rack_result_body_each(VALUE body) { nxt_int_t rc; if (TYPE(body) != T_STRING) { return Qnil; } rc = nxt_app_msg_write_raw(nxt_ruby_run_ctx.task, nxt_ruby_run_ctx.wmsg, (u_char *) RSTRING_PTR(body), RSTRING_LEN(body)); if (nxt_slow_path(rc != NXT_OK)) { nxt_log(nxt_ruby_run_ctx.task, NXT_LOG_ERR, "Ruby: Failed to write 'body' from application"); } return Qnil; } static void nxt_ruby_exception_log(nxt_task_t *task, uint32_t level, const char *desc) { int i; VALUE err, ary, eclass, msg; nxt_log(task, level, "Ruby: %s", desc); err = rb_errinfo(); ary = rb_funcall(err, rb_intern("backtrace"), 0); if (RARRAY_LEN(ary) == 0) { return; } eclass = rb_class_name(rb_class_of(err)); msg = rb_funcall(err, rb_intern("message"), 0); nxt_log(task, level, "Ruby: %s: %s (%s)", RSTRING_PTR(RARRAY_PTR(ary)[0]), RSTRING_PTR(msg), RSTRING_PTR(eclass)); for (i = 1; i < RARRAY_LEN(ary); i++) { nxt_log(task, level, "from %s", RSTRING_PTR(RARRAY_PTR(ary)[i])); } } static void nxt_ruby_atexit(nxt_task_t *task) { rb_gc_unregister_address(&nxt_ruby_io_input); rb_gc_unregister_address(&nxt_ruby_io_error); rb_gc_unregister_address(&nxt_ruby_rackup); rb_gc_unregister_address(&nxt_ruby_call); rb_gc_unregister_address(&nxt_ruby_env); ruby_cleanup(0); }