From 6a26f5d6cbadeb2b15d9ba2a43c3edfc5e730009 Mon Sep 17 00:00:00 2001 From: Fedor Sakharov Date: Wed, 18 Oct 2017 11:56:14 +0300 Subject: [PATCH] Initial support for Ruby Rack applications --- auto/help | 3 + auto/make | 3 + auto/modules/conf | 5 +- auto/modules/ruby | 175 ++++++++++++++ auto/options | 6 + src/nxt_application.c | 3 + src/nxt_application.h | 6 + src/nxt_conf_validation.c | 26 ++ src/nxt_main_process.c | 14 +- src/nxt_router.c | 82 +++++++ src/nxt_ruby_rack.c | 484 ++++++++++++++++++++++++++++++++++++++ 11 files changed, 805 insertions(+), 2 deletions(-) create mode 100644 auto/modules/ruby create mode 100644 src/nxt_ruby_rack.c diff --git a/auto/help b/auto/help index e768a228e..14734aa88 100644 --- a/auto/help +++ b/auto/help @@ -45,4 +45,7 @@ cat << END go OPTIONS configure Go module run "./configure go --help" to see available options + ruby OPTIONS configure Ruby module + run "./configure ruby --help" to see available options + END diff --git a/auto/make b/auto/make index bd8821f7b..28fe2b26b 100644 --- a/auto/make +++ b/auto/make @@ -234,6 +234,9 @@ ${NXT_DAEMON}-uninstall: END +if [ $NXT_RUBY_MODULE != NO ]; then + . auto/modules/ruby/make +fi # Makefile. # *.dSYM is MacOSX Clang debug information. diff --git a/auto/modules/conf b/auto/modules/conf index 8c0b7c0c1..ed0fe7815 100644 --- a/auto/modules/conf +++ b/auto/modules/conf @@ -2,7 +2,6 @@ # Copyright (C) Igor Sysoev # Copyright (C) NGINX, Inc. - case "$nxt_module" in python) @@ -17,6 +16,10 @@ case "$nxt_module" in . auto/modules/go ;; + ruby) + . auto/modules/ruby + ;; + *) echo echo $0: error: invalid module \"$nxt_module\". diff --git a/auto/modules/ruby b/auto/modules/ruby new file mode 100644 index 000000000..3105856ef --- /dev/null +++ b/auto/modules/ruby @@ -0,0 +1,175 @@ + +# Copyright (C) Fedor Sakharov + + +shift + +for nxt_option; do + + case "$nxt_option" in + -*=*) value=`echo "$nxt_option" | sed -e 's/[-_a-zA-Z0-9]*=//'` ;; + *) value="" ;; + esac + + case "$nxt_option" in + --config=*) NXT_RUBY_CONFIG="$value" ;; + --module=*) NXT_RUBY_MODULE="$value" ;; + --lib-path=*) NXT_RUBY_LIB_PATH="$value" ;; + + --help) + cat << END + + --config=FILE set ruby-config filename + --module=NAME set unit ruby module name + --lib-path=DIRECTORY set directory path to libruby.so library + +END + exit 0 + ;; + + *) + echo + echo $0: error: invalid RUBY option \"$nxt_option\" + echo + exit 1 + ;; + + esac + +done + + +if [ ! -f $NXT_AUTOCONF_DATA ]; then + echo + echo Please run common $0 before configuring module \"$nxt_module\". + echo + exit 1 +fi + +. $NXT_AUTOCONF_DATA + + +NXT_RUBY_CONFIG=${NXT_RUBY_CONFIG=ruby-config} +NXT_RUBY=${NXT_RUBY_CONFIG%-config*} +NXT_RUBY_MODULE=${NXT_RUBY_MODULE=${NXT_RUBY##*/}} +NXT_RUBY_LIB_PATH=${NXT_RUBY_LIB_PATH=} + + +$echo "configuring RUBY module" +$echo "configuring RUBY module ..." >> $NXT_AUTOCONF_ERR + +$echo -n "checking for RUBY ..." +$echo "checking for RUBY ..." >> $NXT_AUTOCONF_ERR + + +NXT_RUBY_LDFLAGS= + +if /bin/sh -c "ruby --version" >> $NXT_AUTOCONF_ERR 2>&1; then + + $echo " found" + + NXT_RUBY_VERSION="`ruby --version`" + $echo " + RUBY version: ${NXT_RUBY_VERSION}" + + NXT_RUBY_INCLUDE="-I/usr/include/ruby-2.3.0/ -I/usr/include/x86_64-linux-gnu/ruby-2.3.0/" + NXT_RUBY_LIB="-lruby-2.3" + + if [ "$NXT_RUBY_LIB_PATH" != "" ]; then + NXT_RUBY_LDFLAGS="-L${NXT_RUBY_LIB_PATH} -Wl,-rpath ${NXT_RUBY_LIB_PATH}" + fi + + nxt_feature="RUBY embed RACK" + nxt_feature_name=NXT_HAVE_RUBY + nxt_feature_run=no + nxt_feature_incs="${NXT_RUBY_INCLUDE}" + nxt_feature_libs="${NXT_RUBY_LIB} ${NXT_RUBY_LDFLAGS}" + nxt_feature_test=" + #include + + int main() { + ruby_setup(); + return 0; + }" + + . auto/feature + + if [ $nxt_found = no ]; then + $echo + $echo $NXT_RUBY_LDFLAGS + $echo $NXT_RUBY_INCLUDE + $echo $0: error: no RUBY embed RACK found. + $echo + exit 1; + fi + +else + $echo + $echo $0: error: no RUBY found. + $echo + exit 1; +fi + +if grep ^$NXT_RUBY_MODULE: $NXT_MAKEFILE 2>&1 > /dev/null; then + $echo + $echo $0: error: duplicate \"$NXT_RUBY_MODULE\" module configured + $echo + exit 1; +fi + +$echo " + RUBY module: ${NXT_RUBY_MODULE}.unit.so" + +$echo >> $NXT_MAKEFILE + +NXT_RUBY_MODULES_SRCS=" \ + src/nxt_ruby_rack.c +" + +# The ruby module object files + +nxt_objs= + +for nxt_src in $NXT_RUBY_MODULES_SRCS; do + + nxt_obj=`$echo $nxt_src | sed -e "s/\.c$/-$NXT_RUBY_MODULE.o/"` + nxt_objs="$nxt_objs $NXT_BUILD_DIR/$nxt_obj" + + cat << END >> $NXT_MAKEFILE + +$NXT_BUILD_DIR/$nxt_obj: $nxt_src + \$(CC) -c \$(CFLAGS) \$(NXT_INCS) $NXT_RUBY_INCLUDE \\ + -o $NXT_BUILD_DIR/$nxt_obj $nxt_src +END + +done + + +cat << END >> $NXT_MAKEFILE + +.PHONY: ${NXT_RUBY_MODULE} +.PHONY: ${NXT_RUBY_MODULE}-install +.PHONY: ${NXT_RUBY_MODULE}-uninstall + +all: ${NXT_RUBY_MODULE} + +${NXT_RUBY_MODULE}: $NXT_BUILD_DIR/${NXT_RUBY_MODULE}.unit.so + +$NXT_BUILD_DIR/${NXT_RUBY_MODULE}.unit.so: $nxt_objs + $NXT_MODULE_LINK -o $NXT_BUILD_DIR/${NXT_RUBY_MODULE}.unit.so \\ + $nxt_objs ${NXT_RUBY_LIB} ${NXT_RUBY_LDFLAGS} + + +install: ${NXT_RUBY_MODULE}-install + +${NXT_RUBY_MODULE}-install: ${NXT_RUBY_MODULE} + install -d \$(DESTDIR)$NXT_MODULES + install -p $NXT_BUILD_DIR/${NXT_RUBY_MODULE}.unit.so \\ + \$(DESTDIR)$NXT_MODULES/ + + +uninstall: ${NXT_RUBY_MODULE}-uninstall + +${NXT_RUBY_MODULE}-uninstall: + rm -f \$(DESTDIR)$NXT_MODULES/${NXT_RUBY_MODULE}.unit.so + @rmdir -p \$(DESTDIR)$NXT_MODULES 2>/dev/null || true + +END diff --git a/auto/options b/auto/options index 9563bbc88..82f699d63 100644 --- a/auto/options +++ b/auto/options @@ -39,6 +39,9 @@ NXT_TEST_BUILD_HPUX_SENDFILE=NO NXT_TESTS=NO +NXT_RUBY=ruby +NXT_RUBY_MODULE=NO + for nxt_option do case "$nxt_option" in @@ -97,6 +100,9 @@ do exit 0 ;; + --with-ruby=*) NXT_RUBY="$value" ;; + --with-ruby_module) NXT_RUBY_MODULE=YES ;; + *) $echo $echo $0: error: invalid option \"$nxt_option\". diff --git a/src/nxt_application.c b/src/nxt_application.c index 9ce6a8ceb..ba655e01e 100644 --- a/src/nxt_application.c +++ b/src/nxt_application.c @@ -1067,6 +1067,9 @@ nxt_app_parse_type(u_char *p, size_t length) } else if (nxt_str_eq(&str, "go", 2)) { return NXT_APP_GO; + } else if (nxt_str_eq(&str, "ruby", 4)) { + return NXT_APP_RUBY; + } return NXT_APP_UNKNOWN; diff --git a/src/nxt_application.h b/src/nxt_application.h index 00659a28c..6d6dd327a 100644 --- a/src/nxt_application.h +++ b/src/nxt_application.h @@ -13,6 +13,7 @@ typedef enum { NXT_APP_PYTHON, NXT_APP_PHP, NXT_APP_GO, + NXT_APP_RUBY, NXT_APP_UNKNOWN, } nxt_app_type_t; @@ -50,6 +51,10 @@ typedef struct { char *executable; } nxt_go_app_conf_t; +typedef struct { + nxt_str_t index; + nxt_str_t root; +} nxt_ruby_app_conf_t; struct nxt_common_app_conf_s { nxt_str_t name; @@ -65,6 +70,7 @@ struct nxt_common_app_conf_s { nxt_python_app_conf_t python; nxt_php_app_conf_t php; nxt_go_app_conf_t go; + nxt_ruby_app_conf_t ruby; } u; }; diff --git a/src/nxt_conf_validation.c b/src/nxt_conf_validation.c index 80bfa8851..5dc6af0b4 100644 --- a/src/nxt_conf_validation.c +++ b/src/nxt_conf_validation.c @@ -222,6 +222,31 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_go_members[] = { }; +static nxt_conf_vldt_object_t nxt_conf_vldt_ruby_members[] = { + { nxt_string("type"), + NXT_CONF_STRING, + NULL, + NULL, }, + + { nxt_string("workers"), + NXT_CONF_INTEGER, + NULL, + NULL }, + + { nxt_string("rack_root"), + NXT_CONF_STRING, + NULL, + NULL, }, + + { nxt_string("rack_script"), + NXT_CONF_STRING, + NULL, + NULL, }, + + { nxt_null_string, 0, NULL, NULL } +}; + + nxt_int_t nxt_conf_validate(nxt_conf_validation_t *vldt) { @@ -364,6 +389,7 @@ nxt_conf_vldt_app(nxt_conf_validation_t *vldt, nxt_str_t *name, nxt_conf_vldt_python_members, nxt_conf_vldt_php_members, nxt_conf_vldt_go_members, + nxt_conf_vldt_ruby_members, }; ret = nxt_conf_vldt_type(vldt, name, value, NXT_CONF_OBJECT); diff --git a/src/nxt_main_process.c b/src/nxt_main_process.c index 819b797b8..002179503 100644 --- a/src/nxt_main_process.c +++ b/src/nxt_main_process.c @@ -153,6 +153,18 @@ static nxt_conf_map_t nxt_common_app_conf[] = { NXT_CONF_MAP_CSTRZ, offsetof(nxt_common_app_conf_t, u.go.executable), }, + + { + nxt_string("rack_script"), + NXT_CONF_MAP_STR, + offsetof(nxt_common_app_conf_t, u.ruby.index), + }, + + { + nxt_string("rack_root"), + NXT_CONF_MAP_STR, + offsetof(nxt_common_app_conf_t, u.ruby.root), + } }; @@ -1058,7 +1070,7 @@ nxt_main_port_modules_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg) return; } - nxt_debug(task, "application languages: \"%*s\"", + nxt_log(task, NXT_LOG_ALERT, "application languages: \"%*s\"", b->mem.free - b->mem.pos, b->mem.pos); conf = nxt_conf_json_parse(mp, b->mem.pos, b->mem.free, NULL); diff --git a/src/nxt_router.c b/src/nxt_router.c index 32a3b9d3c..dac244419 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -164,6 +164,8 @@ static nxt_int_t nxt_php_prepare_msg(nxt_task_t *task, nxt_app_request_t *r, nxt_app_wmsg_t *wmsg); static nxt_int_t nxt_go_prepare_msg(nxt_task_t *task, nxt_app_request_t *r, nxt_app_wmsg_t *wmsg); +static nxt_int_t nxt_ruby_prepare_msg(nxt_task_t *task, nxt_app_request_t *r, + nxt_app_wmsg_t *wmsg); static void nxt_router_conn_ready(nxt_task_t *task, void *obj, void *data); static void nxt_router_conn_close(nxt_task_t *task, void *obj, void *data); static void nxt_router_conn_free(nxt_task_t *task, void *obj, void *data); @@ -182,6 +184,7 @@ static nxt_app_prepare_msg_t nxt_app_prepare_msg[] = { nxt_python_prepare_msg, nxt_php_prepare_msg, nxt_go_prepare_msg, + nxt_ruby_prepare_msg, }; @@ -3460,6 +3463,85 @@ nxt_go_prepare_msg(nxt_task_t *task, nxt_app_request_t *r, nxt_app_wmsg_t *wmsg) } +static nxt_int_t +nxt_ruby_prepare_msg(nxt_task_t *task, nxt_app_request_t *r, nxt_app_wmsg_t *wmsg) +{ + nxt_int_t rc; + nxt_buf_t *b; + nxt_http_field_t *field; + nxt_app_request_header_t *h; + + static const nxt_str_t eof = nxt_null_string; + + h = &r->header; + +#define RC(S) \ + do { \ + rc = (S); \ + if (nxt_slow_path(rc != NXT_OK)) { \ + goto fail; \ + } \ + } while(0) + +#define NXT_WRITE(N) \ + RC(nxt_app_msg_write_str(task, wmsg, N)) + + /* TODO error handle, async mmap buffer assignment */ + + NXT_WRITE(&h->method); + NXT_WRITE(&h->target); + + if (h->path.start == h->target.start) { + NXT_WRITE(&eof); + + } else { + NXT_WRITE(&h->path); + } + + if (h->query.start != NULL) { + RC(nxt_app_msg_write_size(task, wmsg, + h->query.start - h->target.start + 1)); + } else { + RC(nxt_app_msg_write_size(task, wmsg, 0)); + } + + NXT_WRITE(&h->version); + NXT_WRITE(&r->remote); + + NXT_WRITE(&h->host); + NXT_WRITE(&h->cookie); + NXT_WRITE(&h->content_type); + NXT_WRITE(&h->content_length); + + RC(nxt_app_msg_write_size(task, wmsg, h->parsed_content_length)); + + nxt_list_each(field, h->fields) { + NXT_WRITE(&field->name); + NXT_WRITE(&field->value); + + } nxt_list_loop; + + /* end-of-headers mark */ + NXT_WRITE(&eof); + + RC(nxt_app_msg_write_size(task, wmsg, r->body.preread_size)); + + for(b = r->body.buf; b != NULL; b = b->next) { + RC(nxt_app_msg_write_raw(task, wmsg, b->mem.pos, + nxt_buf_mem_used_size(&b->mem))); + } + +#undef NXT_WRITE +#undef RC + + return NXT_OK; + +fail: + + return NXT_ERROR; + +} + static const nxt_conn_state_t nxt_router_conn_close_state nxt_aligned(64) = { diff --git a/src/nxt_ruby_rack.c b/src/nxt_ruby_rack.c new file mode 100644 index 000000000..c2e4e6af8 --- /dev/null +++ b/src/nxt_ruby_rack.c @@ -0,0 +1,484 @@ + +/* + * Copyright (C) Fedor Sakharov + */ + +#include "ruby.h" +#include "ruby/version.h" + +#include +#include + +#include + +#define STRINGIFY(V) #V +#define RUBY_VERSION_TO_STR(MAJ, MIN, TEE) STRINGIFY(MAJ) "." \ + STRINGIFY(MIN) "." \ + STRINGIFY(TEE) + +#define RUBY_API_VERSION_STR RUBY_VERSION_TO_STR(RUBY_API_VERSION_MAJOR, \ + RUBY_API_VERSION_MINOR, \ + RUBY_API_VERSION_TEENY) + +static nxt_int_t nxt_ruby_init(nxt_task_t *task, nxt_common_app_conf_t *conf); +static nxt_int_t nxt_ruby_run(nxt_task_t *task, + nxt_app_rmsg_t *rmsg, nxt_app_wmsg_t *wmsg); + +extern nxt_int_t nxt_ruby_rack_init(nxt_thread_t *thr, nxt_runtime_t *rt); + +typedef struct { + nxt_task_t *task; + nxt_app_rmsg_t *rmsg; + nxt_app_request_t r; + nxt_str_t script; + nxt_app_wmsg_t *wmsg; + nxt_mp_t *mem_pool; +} nxt_ruby_run_ctx_t; + +typedef struct nxt_ruby_ctx { + VALUE call; + VALUE dispatcher; + VALUE script; + + VALUE rack; + VALUE rackup; + nxt_ruby_run_ctx_t *current_run_ctx; +} nxt_ruby_ctx_t; + + +static nxt_ruby_ctx_t ruby_context; + +static uint32_t compat[] = { + NXT_VERNUM, +}; + +NXT_EXPORT nxt_application_module_t nxt_app_module = { + sizeof(compat), + compat, + nxt_string("ruby"), + nxt_string(RUBY_API_VERSION_STR), + nxt_ruby_init, + nxt_ruby_run +}; + +static VALUE +require_rubygems(VALUE obj1) +{ + return rb_funcall(rb_cObject, rb_intern("require"), 1, rb_str_new2("rubygems")); +} + +static VALUE +require_rack(VALUE obj1) +{ + return rb_funcall(rb_cObject, rb_intern("require"), 1, rb_str_new2("rack")); +} + +static VALUE +call_dispatch(VALUE env) +{ + return rb_funcall(ruby_context.dispatcher, ruby_context.call, 1, env); +} + +struct parse_file_data { + VALUE rack; + VALUE scr; +}; + +static VALUE +call_rackup(VALUE data) +{ + + struct parse_file_data *datastr = (struct parse_file_data*)data; + return rb_funcall(rb_const_get(datastr->rack, rb_intern("Builder")), + rb_intern("parse_file"), 1, datastr->scr); +} + +static void +nxt_ruby_print_error(nxt_log_t *log) +{ + VALUE err = rb_errinfo(); + VALUE msg = rb_funcall(err, rb_intern("message"), 0, 0); + + nxt_log_alert(log, "Ruby error: %s", RSTRING_PTR(msg)); +} + +static VALUE +nxt_ruby_load_func(VALUE obj1) +{ + int state; + + rb_load_protect(obj1, 0, &state); + + return obj1; +} + +static VALUE +nxt_ruby_script_path(nxt_common_app_conf_t *conf, int *error) +{ + VALUE ret = 0; + nxt_ruby_app_conf_t *c; + + c = &conf->u.ruby; + + if (c->index.length == 0) { + *error = NXT_ERROR; + + return ret; + } + + char *script_cstr = strndup((char*)c->index.start, + c->index.length); + + char *root_cstr = strndup((char*)c->root.start, + c->root.length); + + printf("%s %s %u %u\n", root_cstr, script_cstr, + (unsigned)strlen(root_cstr), (unsigned)strlen(script_cstr)); + + char *full_path = malloc(sizeof(char) * (strlen(root_cstr) + strlen(script_cstr))); + + full_path[0] = 0; + strcat(full_path, root_cstr); + strcat(full_path, script_cstr); + + printf("Full path %s\n", full_path); + + full_path[strlen(root_cstr) + strlen(script_cstr)] = '\0'; + + free(script_cstr); + free(root_cstr); + + ret = rb_str_new_cstr(full_path); + free(full_path); + + *error = 0; + + return ret; +} + +static nxt_int_t +nxt_ruby_init(nxt_task_t *task, nxt_common_app_conf_t *conf) +{ + int error; + VALUE dummy, result; + struct parse_file_data pfd; + + ruby_init(); + Init_stack(&dummy); + ruby_init_loadpath(); + ruby_script("nginext"); + + rb_protect(require_rubygems, 0, &error); + + if (error) { + nxt_ruby_print_error(task->log); + + return NXT_ERROR; + } + + rb_protect(require_rack, 0, &error); + + if (error) { + nxt_ruby_print_error(task->log); + + return NXT_ERROR; + } + + ruby_context.script = nxt_ruby_script_path(conf, &error); + + if (error) { + nxt_log_alert(task->log, "Failed to load ruby script"); + return NXT_ERROR; + } + + result = rb_protect(nxt_ruby_load_func, ruby_context.script, &error); + (void)result; + + if (error) { + nxt_ruby_print_error(task->log); + + return NXT_ERROR; + } + + ruby_context.call = rb_intern("my_call"); + + if (!ruby_context.call) { + nxt_ruby_print_error(task->log); + + return NXT_ERROR; + } else { + nxt_log_debug(task->log, "Found rack script entry point"); + } + + VALUE rack = rb_const_get(rb_cObject, rb_intern("Rack")); + VALUE scr = ruby_context.script; + + pfd.rack = rack; + pfd.scr = scr; + + ruby_context.rackup = rb_protect(call_rackup, (VALUE)&pfd, &error); + + if (error) { + nxt_ruby_print_error(task->log); + return NXT_ERROR; + } + + if (TYPE(ruby_context.rackup) != T_ARRAY) { + nxt_log_alert(task->log, "Failed to parse file\n"); + + return NXT_ERROR; + } + + if (RARRAY_LEN(ruby_context.rackup) < 1) { + nxt_log_alert(task->log, "Invalid rack config file\n"); + + return NXT_ERROR; + } + + ruby_context.dispatcher = RARRAY_PTR(ruby_context.rackup)[0]; + + if (ruby_context.dispatcher == Qnil) { + nxt_log_alert(task->log, "Failed to alloc dispatcher\n"); + + return NXT_ERROR; + } + + nxt_log(task, NXT_LOG_INFO, "Ruby " RUBY_API_VERSION_STR " init completed"); + + return NXT_OK; +} + +nxt_inline nxt_int_t +nxt_ruby_write(nxt_ruby_run_ctx_t *ctx, 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(ctx->task, ctx->wmsg, data, len); + + if (flush || last) { + rc = nxt_app_msg_flush(ctx->task, ctx->wmsg, last); + } + + return rc; +} + +static VALUE +append_header(VALUE obj, VALUE headers) +{ + static const u_char cr_lf[] = "\r\n"; + static const u_char colon[] = ": "; + VALUE hkey, hval; + + /* Ruby RTTI */ + if (TYPE(obj) == T_ARRAY) { + if (RARRAY_LEN(obj) >= 2) { + hkey = rb_obj_as_string(RARRAY_PTR(obj)[0]); + hval = rb_obj_as_string(RARRAY_PTR(obj)[1]); + } else { + goto clear; + } + } + else if (TYPE(obj) == T_STRING) { + hkey = obj; + hval = rb_hash_lookup(headers, obj); + } else { + goto clear; + } + + if (TYPE(hkey) != T_STRING || TYPE(hval) != T_STRING) { + goto clear; + } + + char *header_key = RSTRING_PTR(hkey); + size_t header_key_len = RSTRING_LEN(hkey); + + char *header_value = RSTRING_PTR(hval); + size_t header_value_len = RSTRING_LEN(hval); + + nxt_ruby_write(ruby_context.current_run_ctx, + (u_char*)header_key, header_key_len, 0, 0); + nxt_ruby_write(ruby_context.current_run_ctx, + colon, sizeof(colon) - 1, 0, 0); + nxt_ruby_write(ruby_context.current_run_ctx, + (u_char*)header_value, header_value_len, 0, 0); + nxt_ruby_write(ruby_context.current_run_ctx, + cr_lf, sizeof(cr_lf) - 1, 0, 0); + +clear: + + return Qnil; +} + +static VALUE +iterate_headers(VALUE headers) +{ + return rb_iterate(rb_each, headers, append_header, headers); +} + +static VALUE +append_body(VALUE obj) +{ + if (TYPE(obj) == T_STRING) { + nxt_ruby_write(ruby_context.current_run_ctx, + (u_char*)RSTRING_PTR(obj), RSTRING_LEN(obj), 0, 0); + } + + return Qnil; +} + +static VALUE +iterate_body(VALUE body) +{ + return rb_iterate(rb_each, body, append_body, 0); +} + +static nxt_int_t +nxt_ruby_read_resp(nxt_task_t *task, VALUE resp, nxt_ruby_run_ctx_t *ctx) +{ + int error; + VALUE status, headers, body; + u_char *str_status; + + static const u_char http_11[] = "HTTP/1.1 "; + static const u_char cr_lf[] = "\r\n"; + + static const u_char default_response[] + = "HTTP/1.1 200 OK\r\n" + "Server: nginext/0.1\r\n" + "Content-Type: text/html; charset=UTF-8\r\n" + "Connection: close\r\n" + "\r\n"; + + ruby_context.current_run_ctx = ctx; + + if (RARRAY_LEN(resp) != 3) { + nxt_log_error(NXT_LOG_ERR, task->log, + "Invalid RACK response size: %ld", + RARRAY_LEN(resp)); + + goto error; + } + + status = rb_obj_as_string(RARRAY_PTR(resp)[0]); + str_status = (u_char*)RSTRING_PTR(status); + + nxt_ruby_write(ctx, http_11, sizeof(http_11) - 1, 0, 0); + + nxt_ruby_write(ctx, str_status, strlen((char*)str_status), 0, 0); + nxt_ruby_write(ctx, cr_lf, sizeof(cr_lf) - 1, 0, 0); + + headers = RARRAY_PTR(resp)[1]; + + if (rb_respond_to(headers, rb_intern("each"))) { + rb_protect(iterate_headers, headers, &error); + + if (error) { + nxt_log_error(NXT_LOG_INFO, task->log, + "Failed to iterate through headers"); + } + } + + body = RARRAY_PTR(resp)[2]; + + if (rb_respond_to(body, rb_intern("each"))) { + rb_protect(iterate_body, body, &error); + + if (error) { + nxt_log_error(NXT_LOG_INFO, task->log, + "Failed to iterate through body"); + } + } + + nxt_ruby_write(ctx, cr_lf, sizeof(cr_lf) - 1, 0, 1); + + return NXT_OK; + +error: + + nxt_ruby_write(ctx, default_response, sizeof(default_response) - 1, 0, 1); + + return NXT_ERROR; +} + +static nxt_int_t +nxt_ruby_run(nxt_task_t *task, + nxt_app_rmsg_t *rmsg, nxt_app_wmsg_t *wmsg) +{ + int error; + size_t s; + VALUE env, retval, rvb; + nxt_ruby_run_ctx_t run_ctx, *ctx; + nxt_app_request_header_t *h; + + nxt_memzero(&run_ctx, sizeof(run_ctx)); + + run_ctx.task = task; + run_ctx.rmsg = rmsg; + run_ctx.wmsg = wmsg; + + run_ctx.mem_pool = nxt_mp_create(1024, 128, 256, 32); + + ctx = &run_ctx; + + h = &ctx->r.header; + + nxt_app_msg_read_str(task, rmsg, &h->method); + nxt_app_msg_read_str(task, rmsg, &h->path); + //h->path_no_query = h->path; + + nxt_app_msg_read_size(task, rmsg, &s); + + nxt_app_msg_read_str(task, rmsg, &h->version); + + nxt_app_msg_read_str(task, rmsg, &h->cookie); + nxt_app_msg_read_str(task, rmsg, &h->content_type); + nxt_app_msg_read_str(task, rmsg, &h->content_length); + + env = rb_hash_new(); + + rb_hash_aset(env, rb_str_new2("REQUEST_METHOD"), + rb_str_new((char*)h->method.start, h->method.length)); + rb_hash_aset(env, rb_str_new2("SCRIPT_NAME"), + rb_str_new2("nginext")); + rb_hash_aset(env, rb_str_new2("QUERY_STRING"), + rb_str_new2((char*)h->path.start)); + rb_hash_aset(env, rb_str_new2("SERVER_NAME"), + rb_str_new2("testhost")); + rb_hash_aset(env, rb_str_new2("SERVER_PORT"), + rb_str_new2("80")); + + rvb = rb_ary_new(); + rb_ary_store(rvb, 0, INT2NUM(1)); + rb_ary_store(rvb, 1, INT2NUM(1)); + + rb_hash_aset(env, rb_str_new2("rack.version"), rvb); + rb_hash_aset(env, rb_str_new2("rack.url_scheme"), + rb_str_new2("http")); + rb_hash_aset(env, rb_str_new2("rack.multithread"), Qfalse); + rb_hash_aset(env, rb_str_new2("rack.run_once"), Qfalse); + + retval = rb_protect(call_dispatch, env, &error); + + if (error) { + goto fail; + } + + if (TYPE(retval) == T_ARRAY) { + if (RARRAY_LEN(retval) != 3) { + goto fail; + } + } + + nxt_ruby_read_resp(task, retval, ctx); + + nxt_mp_destroy(run_ctx.mem_pool); + + return NXT_OK; + +fail: + + nxt_mp_destroy(run_ctx.mem_pool); + + return NXT_ERROR; +}