Files
uwsgi/plugins/rack/rack_plugin.c
T

1028 lines
27 KiB
C

#include "uwsgi_rack.h"
extern struct uwsgi_server uwsgi;
struct uwsgi_rack ur;
struct uwsgi_option uwsgi_rack_options[] = {
{"rails", required_argument, 0, "load a rails <= 2.x app", uwsgi_opt_set_str, &ur.rails, UWSGI_OPT_POST_BUFFERING},
{"rack", required_argument, 0, "load a rack app", uwsgi_opt_set_str, &ur.rack, UWSGI_OPT_POST_BUFFERING},
{"ruby-gc-freq", required_argument, 0, "set ruby GC frequency", uwsgi_opt_set_int, &ur.gc_freq, 0},
{"rb-gc-freq", required_argument, 0, "set ruby GC frequency", uwsgi_opt_set_int, &ur.gc_freq, 0},
{"rb-require", required_argument, 0, "import/require a ruby module/script", uwsgi_opt_add_string_list, &ur.rbrequire, 0},
{"ruby-require", required_argument, 0, "import/require a ruby module/script", uwsgi_opt_add_string_list, &ur.rbrequire, 0},
{"rbrequire", required_argument, 0, "import/require a ruby module/script", uwsgi_opt_add_string_list, &ur.rbrequire, 0},
{"rubyrequire", required_argument, 0, "import/require a ruby module/script", uwsgi_opt_add_string_list, &ur.rbrequire, 0},
{"require", required_argument, 0, "import/require a ruby module/script", uwsgi_opt_add_string_list, &ur.rbrequire, 0},
{"rbshell", optional_argument, 0, "run a ruby/irb shell", uwsgi_opt_true, &ur.rb_shell, 0},
{0, 0, 0, 0, 0, 0 ,0},
};
void uwsgi_ruby_exception(void) {
VALUE lasterr = rb_gv_get("$!");
VALUE message = rb_obj_as_string(lasterr);
uwsgi_log("%s\n", RSTRING_PTR(message));
if(!NIL_P(rb_errinfo())) {
VALUE ary = rb_funcall(rb_errinfo(), rb_intern("backtrace"), 0);
int i;
for (i=0; i<RARRAY_LEN(ary); i++) {
uwsgi_log("%s\n", RSTRING_PTR(RARRAY_PTR(ary)[i]));
}
}
}
extern struct http_status_codes hsc[];
VALUE rb_uwsgi_io_new(VALUE class, VALUE wr) {
struct wsgi_request *wsgi_req;
Data_Get_Struct(wr, struct wsgi_request, wsgi_req);
VALUE self = Data_Wrap_Struct(class , 0, 0, wsgi_req);
rb_obj_call_init(self, 0, NULL);
return self;
}
VALUE rb_uwsgi_io_init(int argc, VALUE *argv, VALUE self) {
return self;
}
VALUE rb_uwsgi_io_gets(VALUE obj, VALUE args) {
size_t i;
struct wsgi_request *wsgi_req;
VALUE line;
Data_Get_Struct(obj, struct wsgi_request, wsgi_req);
// return a line of body
for(i=wsgi_req->buf_pos;i<wsgi_req->post_cl;i++) {
if (wsgi_req->post_buffering_buf[i] == '\n') {
line = rb_str_new(wsgi_req->post_buffering_buf+wsgi_req->buf_pos, (i+1)-wsgi_req->buf_pos);
wsgi_req->buf_pos = i+1;
return line;
}
}
if (wsgi_req->buf_pos < wsgi_req->post_cl) {
line = rb_str_new(wsgi_req->post_buffering_buf+wsgi_req->buf_pos, wsgi_req->post_cl-wsgi_req->buf_pos);
wsgi_req->buf_pos = wsgi_req->post_cl;
return line;
}
return Qnil;
}
VALUE rb_uwsgi_io_each(VALUE obj, VALUE args) {
struct wsgi_request *wsgi_req;
Data_Get_Struct(obj, struct wsgi_request, wsgi_req);
// yield strings chunks
rb_raise(rb_eRuntimeError, "rack.input::each is not implemented (req %p)\n", wsgi_req);
return Qnil;
}
VALUE rb_uwsgi_io_read(VALUE obj, VALUE args) {
struct wsgi_request *wsgi_req;
Data_Get_Struct(obj, struct wsgi_request, wsgi_req);
VALUE chunk;
int chunk_size;
if (!wsgi_req->post_cl || wsgi_req->buf_pos >= wsgi_req->post_cl) {
return Qnil;
}
if (RARRAY_LEN(args) == 0) {
chunk = rb_str_new(wsgi_req->post_buffering_buf+wsgi_req->buf_pos, wsgi_req->post_cl-wsgi_req->buf_pos);
wsgi_req->buf_pos += (wsgi_req->post_cl-wsgi_req->buf_pos);
return chunk;
}
else if (RARRAY_LEN(args) > 0) {
chunk_size = NUM2INT(RARRAY_PTR(args)[0]);
if (wsgi_req->buf_pos+chunk_size > wsgi_req->post_cl) {
chunk_size = wsgi_req->post_cl-wsgi_req->buf_pos;
}
if (RARRAY_LEN(args) > 1) {
rb_str_cat(RARRAY_PTR(args)[1], wsgi_req->post_buffering_buf+wsgi_req->buf_pos, chunk_size);
wsgi_req->buf_pos+=chunk_size;
return RARRAY_PTR(args)[1];
}
chunk = rb_str_new(wsgi_req->post_buffering_buf+wsgi_req->buf_pos, chunk_size);
wsgi_req->buf_pos+=chunk_size;
return chunk;
}
return Qnil;
}
VALUE rb_uwsgi_io_rewind(VALUE obj, VALUE args) {
struct wsgi_request *wsgi_req;
Data_Get_Struct(obj, struct wsgi_request, wsgi_req);
if (!wsgi_req->post_cl) {
return Qnil;
}
wsgi_req->buf_pos = 0;
return Qnil;
}
#ifdef RUBY19
RUBY_GLOBAL_SETUP
#endif
VALUE uwsgi_require_file(VALUE arg) {
return rb_funcall(rb_cObject, rb_intern("require"), 1, arg);
}
VALUE require_rack(VALUE arg) {
return rb_funcall(rb_cObject, rb_intern("require"), 1, rb_str_new2("rack"));
}
VALUE require_rails(VALUE arg) {
#ifdef RUBY19
return rb_require("./config/environment");
#else
return rb_require("config/environment");
#endif
}
VALUE require_thin(VALUE arg) {
return rb_funcall(rb_cObject, rb_intern("require"), 1, rb_str_new2("thin"));
}
VALUE init_rack_app(VALUE);
VALUE rack_call_rpc_handler(VALUE args) {
VALUE rpc_args = rb_ary_entry(args, 1);
return rb_funcall2(rb_ary_entry(args, 0), rb_intern("call"), RARRAY_LEN(rpc_args), RARRAY_PTR(rpc_args));
}
uint16_t uwsgi_ruby_rpc(void *func, uint8_t argc, char **argv, uint16_t argvs[], char *buffer) {
uint8_t i;
VALUE rb_args = rb_ary_new2(2);
VALUE rb_rpc_args = rb_ary_new2(argc);
VALUE ret;
int error = 0;
char *rv;
size_t rl;
rb_ary_store(rb_args, 0, (VALUE) func);
for (i = 0; i < argc; i++) {
rb_ary_store(rb_rpc_args, i, rb_str_new(argv[i], argvs[i]));
}
rb_ary_store(rb_args, 1, rb_rpc_args);
ret = rb_protect(rack_call_rpc_handler, rb_args, &error);
if (error) {
uwsgi_ruby_exception();
return 0;
}
if (TYPE(ret) == T_STRING) {
rv = RSTRING_PTR(ret);
rl = RSTRING_LEN(ret);
if (rl <= 0xffff) {
memcpy(buffer, rv, rl);
return rl;
}
}
return 0;
}
int uwsgi_rack_init(){
struct http_status_codes *http_sc;
#ifdef RUBY19
int argc = 2;
char *sargv[] = { (char *) "uwsgi", (char *) "-e0" };
char **argv = sargv;
#endif
// filling http status codes
for (http_sc = hsc; http_sc->message != NULL; http_sc++) {
http_sc->message_size = (int) strlen(http_sc->message);
}
#ifdef RUBY19
ruby_sysinit(&argc, &argv);
RUBY_INIT_STACK
ruby_init();
ruby_process_options(argc, argv);
#else
ruby_init();
ruby_init_loadpath();
#endif
ruby_show_version();
ruby_script("uwsgi");
ur.signals_protector = rb_ary_new();
ur.rpc_protector = rb_ary_new();
rb_gc_register_address(&ur.signals_protector);
rb_gc_register_address(&ur.rpc_protector);
#ifdef UWSGI_EMBEDDED
uwsgi_rack_init_api();
#endif
return 0;
}
VALUE uwsgi_rb_call_new(VALUE obj) {
return rb_funcall(obj, rb_intern("new"), 0);
}
void uwsgi_rack_init_apps(void) {
int error;
ur.app_id = uwsgi_apps_cnt;
struct uwsgi_string_list *usl = ur.rbrequire;
time_t now = uwsgi_now();
while(usl) {
error = 0;
rb_protect( uwsgi_require_file, rb_str_new2(usl->value), &error ) ;
if (error) {
uwsgi_ruby_exception();
}
usl = usl->next;
}
if (ur.rack) {
ur.dispatcher = rb_protect(init_rack_app, rb_str_new2(ur.rack), &error);
if (error) {
uwsgi_ruby_exception();
exit(1);
}
if (ur.dispatcher == Qnil) {
uwsgi_log("unable to find RACK entry point\n");
exit(1);
}
rb_gc_register_address(&ur.dispatcher);
goto ready;
}
else if (ur.rails) {
if (chdir(ur.rails)) {
uwsgi_error("chdir()");
exit(1);
}
if (!access("config.ru", R_OK)) {
uwsgi_log("!!! a config.ru file has been found in yor rails app, please use --rack <configfile> instead of the old --rails <app> !!!\n");
}
uwsgi_log("loading rails app %s\n", ur.rails);
rb_protect( require_rails, 0, &error ) ;
if (error) {
uwsgi_ruby_exception();
exit(1);
}
uwsgi_log("rails app %s ready\n", ur.rails);
VALUE ac = rb_const_get(rb_cObject, rb_intern("ActionController"));
ur.dispatcher = Qnil;
if (rb_funcall(ac, rb_intern("const_defined?"), 1, ID2SYM(rb_intern("Dispatcher"))) == Qtrue) {
VALUE ac_dispatcher = rb_const_get(ac, rb_intern("Dispatcher"));
VALUE acd_instance_methods = rb_funcall( ac_dispatcher, rb_intern("instance_methods"), 0);
VALUE acim_call = rb_funcall( acd_instance_methods, rb_intern("include?"), 1, ID2SYM(rb_intern("call")));
if (acim_call == Qfalse) {
acim_call = rb_funcall( acd_instance_methods, rb_intern("include?"), 1, rb_str_new2("call"));
}
if (acim_call == Qtrue) {
ur.dispatcher = rb_protect(uwsgi_rb_call_new, ac_dispatcher, &error);
if (error) {
uwsgi_ruby_exception();
exit(1);
}
}
}
if (ur.dispatcher == Qnil) {
uwsgi_log("non-rack rails version detected...loading thin adapter...\n");
rb_protect( require_thin, 0, &error ) ;
if (error) {
uwsgi_ruby_exception();
exit(1);
}
VALUE thin_rack = rb_const_get(rb_cObject, rb_intern("Rack"));
VALUE thin_rack_adapter = rb_const_get(thin_rack, rb_intern("Adapter"));
VALUE thin_rack_adapter_rails = rb_const_get(thin_rack_adapter, rb_intern("Rails"));
ur.dispatcher = rb_protect( uwsgi_rb_call_new, thin_rack_adapter_rails, &error);
if (error) {
uwsgi_ruby_exception();
exit(1);
}
}
if (ur.dispatcher == Qnil) {
uwsgi_log("unable to load rails dispatcher\n");
exit(1);
}
rb_gc_register_address(&ur.dispatcher);
goto ready;
}
return;
ready:
ur.call = rb_intern("call");
if (!ur.call) {
uwsgi_log("unable to find RACK entry point\n");
return;
}
rb_gc_register_address(&ur.call);
ur.rb_uwsgi_io_class = rb_define_class("Uwsgi_IO", rb_cObject);
rb_gc_register_address(&ur.rb_uwsgi_io_class);
rb_define_singleton_method(ur.rb_uwsgi_io_class, "new", rb_uwsgi_io_new, 1);
rb_define_method(ur.rb_uwsgi_io_class, "initialize", rb_uwsgi_io_init, -1);
rb_define_method(ur.rb_uwsgi_io_class, "gets", rb_uwsgi_io_gets, 0);
rb_define_method(ur.rb_uwsgi_io_class, "each", rb_uwsgi_io_each, 0);
rb_define_method(ur.rb_uwsgi_io_class, "read", rb_uwsgi_io_read, -2);
rb_define_method(ur.rb_uwsgi_io_class, "rewind", rb_uwsgi_io_rewind, 0);
struct uwsgi_app *ua = uwsgi_add_app(ur.app_id, 7, (char*)"", 0, NULL, NULL);
ua->started_at = now;
ua->startup_time = uwsgi_now() - now;
if (ur.gc_freq <= 1) {
uwsgi_log("RACK app %d loaded in %d seconds at %p (GC frequency: AGGRESSIVE)\n", ur.app_id, (int) ua->startup_time, ur.call);
}
else {
uwsgi_log("RACK app %d loaded in %d seconds at %p (GC frequency: %d)\n", ur.app_id, (int) ua->startup_time, ur.call, ur.gc_freq);
}
}
VALUE call_dispatch(VALUE env) {
return rb_funcall(ur.dispatcher, ur.call, 1, env);
}
static VALUE send_body(VALUE obj) {
struct wsgi_request *wsgi_req = current_wsgi_req();
ssize_t len = 0;
//uwsgi_log("sending body\n");
if (TYPE(obj) == T_STRING) {
len = wsgi_req->socket->proto_write( wsgi_req, RSTRING_PTR(obj), RSTRING_LEN(obj));
}
else {
uwsgi_log("UNMANAGED BODY TYPE %d\n", TYPE(obj));
}
wsgi_req->response_size += len;
return Qnil;
}
VALUE body_to_path(VALUE body) {
return rb_funcall( body, rb_intern("to_path"), 0);
}
VALUE close_body(VALUE body) {
return rb_funcall( body, rb_intern("close"), 0);
}
VALUE iterate_body(VALUE body) {
#ifdef RUBY19
return rb_block_call(body, rb_intern("each"), 0, 0, send_body, 0);
#else
return rb_iterate(rb_each, body, send_body, 0);
#endif
}
VALUE send_header(VALUE obj, VALUE headers) {
struct wsgi_request *wsgi_req = current_wsgi_req();
size_t len;
VALUE hkey, hval;
//uwsgi_log("HEADERS %d\n", TYPE(obj));
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;
#ifdef RUBY19
hval = rb_hash_lookup(headers, obj);
#else
hval = rb_hash_aref(headers, obj);
#endif
}
else {
goto clear;
}
if (TYPE(hkey) != T_STRING || TYPE(hval) != T_STRING) {
goto clear;
}
char *header_value = RSTRING_PTR(hval);
size_t header_value_len = RSTRING_LEN(hval);
size_t i,cnt=0;
char *this_header = header_value;
for(i=0;i<header_value_len;i++) {
// multiline header, send it !!!
if (header_value[i] == '\n') {
len = wsgi_req->socket->proto_write_header( wsgi_req, RSTRING_PTR(hkey), RSTRING_LEN(hkey));
wsgi_req->headers_size += len;
len = wsgi_req->socket->proto_write_header( wsgi_req, (char *)": ", 2);
wsgi_req->headers_size += len;
len = wsgi_req->socket->proto_write_header( wsgi_req, this_header, cnt);
wsgi_req->headers_size += len;
len = wsgi_req->socket->proto_write_header( wsgi_req, (char *)"\r\n", 2);
wsgi_req->headers_size += len;
//uwsgi_log("(multi) --%.*s: %.*s--\n", RSTRING_LEN(hkey), RSTRING_PTR(hkey), cnt, this_header);
wsgi_req->header_cnt++;
this_header += cnt+1;
cnt = 0;
continue;
}
cnt++;
}
if (cnt > 0) {
len = wsgi_req->socket->proto_write_header( wsgi_req, RSTRING_PTR(hkey), RSTRING_LEN(hkey));
wsgi_req->headers_size += len;
len = wsgi_req->socket->proto_write_header( wsgi_req, (char *)": ", 2);
wsgi_req->headers_size += len;
len = wsgi_req->socket->proto_write_header( wsgi_req, this_header, cnt);
wsgi_req->headers_size += len;
len = wsgi_req->socket->proto_write_header( wsgi_req, (char *)"\r\n", 2);
wsgi_req->headers_size += len;
wsgi_req->header_cnt++;
//uwsgi_log("--%.*s: %.*s--\n", RSTRING_LEN(hkey), RSTRING_PTR(hkey), cnt, this_header);
}
clear:
return Qnil;
}
VALUE iterate_headers(VALUE headers) {
#ifdef RUBY19
return rb_block_call(headers, rb_intern("each"), 0, 0, send_header, headers );
#else
return rb_iterate(rb_each, headers, send_header, headers);
#endif
}
int uwsgi_rack_request(struct wsgi_request *wsgi_req) {
int error = 0;
int i;
VALUE env, ret, status, headers, body;
struct http_status_codes *http_sc;
/* Standard RACK request */
if (!wsgi_req->uh.pktsize) {
uwsgi_log("Invalid RACK request. skip.\n");
return -1;
}
if (uwsgi_parse_vars(wsgi_req)) {
return -1;
}
wsgi_req->app_id = ur.app_id;
uwsgi_apps[wsgi_req->app_id].requests++;
env = rb_hash_new();
// fill ruby hash
for(i=0;i<wsgi_req->var_cnt;i++) {
// put the var only if it is not 0 size or required (rack requirement... very inefficient)
if (wsgi_req->hvec[i+1].iov_len > 0 ||
!uwsgi_strncmp((char *)"REQUEST_METHOD", 14, wsgi_req->hvec[i].iov_base, (int) wsgi_req->hvec[i].iov_len) ||
!uwsgi_strncmp((char *)"SCRIPT_NAME", 11, wsgi_req->hvec[i].iov_base, (int) wsgi_req->hvec[i].iov_len) ||
!uwsgi_strncmp((char *)"PATH_INFO", 10, wsgi_req->hvec[i].iov_base, (int) wsgi_req->hvec[i].iov_len) ||
!uwsgi_strncmp((char *)"QUERY_STRING", 12, wsgi_req->hvec[i].iov_base, (int) wsgi_req->hvec[i].iov_len) ||
!uwsgi_strncmp((char *)"SERVER_NAME", 11, wsgi_req->hvec[i].iov_base, (int) wsgi_req->hvec[i].iov_len) ||
!uwsgi_strncmp((char *)"SERVER_PORT", 11, wsgi_req->hvec[i].iov_base, (int) wsgi_req->hvec[i].iov_len)
) {
rb_hash_aset(env, rb_str_new(wsgi_req->hvec[i].iov_base, wsgi_req->hvec[i].iov_len),
rb_str_new(wsgi_req->hvec[i+1].iov_base, wsgi_req->hvec[i+1].iov_len));
//uwsgi_log("%.*s = %.*s\n", wsgi_req->hvec[i].iov_len, wsgi_req->hvec[i].iov_base, wsgi_req->hvec[i+1].iov_len, wsgi_req->hvec[i+1].iov_base);
}
i++;
}
VALUE rbv = rb_ary_new();
rb_ary_store(rbv, 0, INT2NUM(1));
rb_ary_store(rbv, 1, INT2NUM(1));
rb_hash_aset(env, rb_str_new2("rack.version"), rbv);
if (wsgi_req->scheme_len > 0) {
rb_hash_aset(env, rb_str_new2("rack.url_scheme"), rb_str_new(wsgi_req->scheme, wsgi_req->scheme_len));
}
else if (wsgi_req->https_len > 0) {
if (!strncasecmp(wsgi_req->https, "on", 2) || wsgi_req->https[0] == '1') {
rb_hash_aset(env, rb_str_new2("rack.url_scheme"), rb_str_new2("https"));
}
else {
rb_hash_aset(env, rb_str_new2("rack.url_scheme"), rb_str_new2("http"));
}
}
else {
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.multiprocess"), Qtrue);
rb_hash_aset(env, rb_str_new2("rack.run_once"), Qfalse);
VALUE dws_wr = Data_Wrap_Struct(ur.rb_uwsgi_io_class, 0, 0, wsgi_req);
if (wsgi_req->async_post) {
rb_hash_aset(env, rb_str_new2("rack.input"), rb_funcall( rb_const_get(rb_cObject, rb_intern("IO")), rb_intern("new"), 2, INT2NUM(fileno(wsgi_req->async_post)), rb_str_new("r",1) ));
}
else {
rb_hash_aset(env, rb_str_new2("rack.input"), rb_funcall(ur.rb_uwsgi_io_class, rb_intern("new"), 1, dws_wr ));
}
rb_hash_aset(env, rb_str_new2("rack.errors"), rb_funcall( rb_const_get(rb_cObject, rb_intern("IO")), rb_intern("new"), 2, INT2NUM(2), rb_str_new("w",1) ));
ret = rb_protect( call_dispatch, env, &error);
if (error) {
uwsgi_ruby_exception();
goto clear;
}
if (TYPE(ret) == T_ARRAY) {
if (RARRAY_LEN(ret) != 3) {
uwsgi_log("Invalid RACK response size: %d\n", RARRAY_LEN(ret));
return -1;
}
// manage Status
status = rb_obj_as_string(RARRAY_PTR(ret)[0]);
// get the status code
wsgi_req->hvec[0].iov_base = wsgi_req->protocol;
wsgi_req->hvec[0].iov_len = wsgi_req->protocol_len ;
wsgi_req->hvec[1].iov_base = (char *) " ";
wsgi_req->hvec[1].iov_len = 1 ;
wsgi_req->hvec[2].iov_base = RSTRING_PTR(status);
wsgi_req->hvec[2].iov_len = 3 ;
wsgi_req->status = atoi(RSTRING_PTR(status));
wsgi_req->hvec[3].iov_base = (char *) " ";
wsgi_req->hvec[3].iov_len = 1 ;
wsgi_req->hvec[4].iov_len = 0 ;
for (http_sc = hsc; http_sc->message != NULL; http_sc++) {
if (!strncmp(http_sc->key, RSTRING_PTR(status), 3)) {
wsgi_req->hvec[4].iov_base = (char *) http_sc->message ;
wsgi_req->hvec[4].iov_len = http_sc->message_size ;
break;
}
}
wsgi_req->hvec[5].iov_base = (char *) "\r\n";
wsgi_req->hvec[5].iov_len = 2 ;
if ( !(wsgi_req->headers_size = wsgi_req->socket->proto_writev_header(wsgi_req, wsgi_req->hvec, 6)) ) {
uwsgi_error("writev()");
}
headers = RARRAY_PTR(ret)[1] ;
if (rb_respond_to( headers, rb_intern("each") )) {
rb_protect( iterate_headers, headers, &error);
if (error) {
uwsgi_ruby_exception();
goto clear;
}
}
if (wsgi_req->socket->proto_write(wsgi_req, (char *)"\r\n", 2) != 2) {
uwsgi_error("write()");
}
body = RARRAY_PTR(ret)[2] ;
if (rb_respond_to( body, rb_intern("to_path") )) {
VALUE sendfile_path = rb_protect( body_to_path, body, &error);
if (error) {
uwsgi_ruby_exception();
}
else {
wsgi_req->sendfile_fd = open(RSTRING_PTR(sendfile_path), O_RDONLY);
wsgi_req->response_size = uwsgi_sendfile(wsgi_req);
if (wsgi_req->response_size > 0) {
while(wsgi_req->response_size < wsgi_req->sendfile_fd_size) {
//uwsgi_log("sendfile_fd_size = %d\n", wsgi_req->sendfile_fd_size);
wsgi_req->response_size += uwsgi_sendfile(wsgi_req);
}
}
// we need to close it...
close(wsgi_req->sendfile_fd);
}
}
else if (rb_respond_to( body, rb_intern("each") )) {
if (ur.unprotected) {
iterate_body(body);
}
else {
rb_protect( iterate_body, body, &error);
if (error) {
uwsgi_ruby_exception();
}
}
}
if (rb_respond_to( body, rb_intern("close") )) {
//uwsgi_log("calling close\n");
rb_protect( close_body, body, &error);
if (error) {
uwsgi_ruby_exception();
}
}
}
else {
internal_server_error(wsgi_req, (char *)"Invalid RACK response");
}
clear:
if (ur.gc_freq <= 1 || ur.cycles%ur.gc_freq == 0) {
#ifdef UWSGI_DEBUG
uwsgi_log("calling ruby GC\n");
#endif
rb_gc();
}
ur.cycles++;
return 0;
}
void uwsgi_rack_after_request(struct wsgi_request *wsgi_req) {
if (uwsgi.shared->options[UWSGI_OPTION_LOGGING])
log_request(wsgi_req);
}
void uwsgi_rack_suspend(struct wsgi_request *wsgi_req) {
uwsgi_log("SUSPENDING RUBY\n");
}
void uwsgi_rack_resume(struct wsgi_request *wsgi_req) {
uwsgi_log("RESUMING RUBY\n");
}
#ifdef RUBY19
VALUE uwsgi_call_block(VALUE body, VALUE block) {
return rb_funcall(block, rb_intern("call"), 1, body );
}
VALUE uwsgi_rack_patch_body_proxy_each(int argc, VALUE *argv, VALUE self) {
VALUE block = Qnil;
rb_scan_args(argc, argv, "0&", &block);
if(!RTEST(block)) {
rb_raise(rb_eArgError, "a block is required");
return Qnil;
}
VALUE original_body = rb_iv_get(self, "@body");
if (original_body != Qnil) {
return rb_block_call(original_body, rb_intern("each"), 0, 0, uwsgi_call_block, block);
}
return Qnil;
}
VALUE uwsgi_rack_patch_body_proxy(VALUE foo) {
VALUE rack = rb_const_get(rb_cObject, rb_intern("Rack"));
VALUE rack_body_proxy = rb_const_get(rack, rb_intern("BodyProxy"));
if (!rb_respond_to(rack_body_proxy, rb_intern("each"))) {
rb_define_method(rack_body_proxy, "each", uwsgi_rack_patch_body_proxy_each, -1);
return Qtrue;
}
return Qnil;
}
#endif
VALUE init_rack_app( VALUE script ) {
int error;
#ifndef RUBY19
rb_require("rubygems");
#endif
rb_protect( require_rack, 0, &error ) ;
if (error) {
uwsgi_ruby_exception();
return Qnil;
}
VALUE rack = rb_const_get(rb_cObject, rb_intern("Rack"));
#ifdef RUBY19
VALUE ret = rb_protect(uwsgi_rack_patch_body_proxy, rack, &error);
if (!error && ret != Qnil) {
uwsgi_log("Rack::BodyProxy successfully patched for ruby 1.9.x\n");
}
#endif
VALUE rackup = rb_funcall( rb_const_get(rack, rb_intern("Builder")), rb_intern("parse_file"), 1, script);
if (TYPE(rackup) != T_ARRAY) {
uwsgi_log("unable to parse %s file\n", RSTRING_PTR(script));
return Qnil;
}
if (RARRAY_LEN(rackup) < 1) {
uwsgi_log("invalid rack config file: %s\n", RSTRING_PTR(script));
return Qnil;
}
return RARRAY_PTR(rackup)[0] ;
}
int uwsgi_rack_magic(char *mountpoint, char *lazy) {
if (!strcmp(lazy+strlen(lazy)-3, ".ru")) {
ur.rack = lazy;
return 1;
}
else if (!strcmp(lazy+strlen(lazy)-3, ".rb")) {
ur.rack = lazy;
return 1;
}
return 0;
}
/*
int uwsgi_rack_mount_app(char *mountpoint, char *app) {
if ( !strcmp(what+strlen(what)-3, ".ru") || !strcmp(what+strlen(what)-3, ".rb")) {
return = uwsgi_rack_load(mountpoint, what);
}
return -1;
}
*/
void uwsgi_rack_hijack(void) {
}
int uwsgi_rack_mule(char *opt) {
int error = 0;
if (uwsgi_endswith(opt, (char *)".rb")) {
rb_protect( uwsgi_require_file, rb_str_new2(opt), &error ) ;
if (error) {
uwsgi_ruby_exception();
return 0;
}
return 1;
}
return 0;
}
VALUE uwsgi_rb_pfh(VALUE args) {
VALUE uwsgi_rb_embedded = rb_const_get(rb_cObject, rb_intern("UWSGI"));
if (rb_respond_to(uwsgi_rb_embedded, rb_intern("post_fork_hook"))) {
return rb_funcall(uwsgi_rb_embedded, rb_intern("post_fork_hook"), 0);
}
return Qnil;
}
void uwsgi_rb_post_fork() {
int error = 0;
// call the post_fork_hook
rb_protect(uwsgi_rb_pfh, 0, &error);
if (error) {
uwsgi_ruby_exception();
}
}
VALUE uwsgi_rb_mmh(VALUE args) {
VALUE uwsgi_rb_embedded = rb_const_get(rb_cObject, rb_intern("UWSGI"));
return rb_funcall(uwsgi_rb_embedded, rb_intern("mule_msg_hook"), 1, args);
}
int uwsgi_rack_mule_msg(char *message, size_t len) {
int error = 0;
VALUE uwsgi_rb_embedded = rb_const_get(rb_cObject, rb_intern("UWSGI"));
if (rb_respond_to(uwsgi_rb_embedded, rb_intern("mule_msg_hook"))) {
VALUE arg = rb_str_new(message, len);
rb_protect(uwsgi_rb_mmh, arg, &error);
if (error) {
uwsgi_ruby_exception();
}
return 1;
}
return 0;
}
VALUE rack_call_signal_handler(VALUE args) {
return rb_funcall(rb_ary_entry(args, 0), rb_intern("call"), 1, rb_ary_entry(args, 1));
}
int uwsgi_rack_signal_handler(uint8_t sig, void *handler) {
int error = 0;
VALUE rbhandler = (VALUE) handler;
VALUE args = rb_ary_new2(2);
rb_ary_store(args, 0, rbhandler);
VALUE rbsig = INT2NUM(sig);
rb_ary_store(args, 1, rbsig);
rb_protect(rack_call_signal_handler, args, &error);
if (error) {
uwsgi_ruby_exception();
rb_gc();
return -1;
}
rb_gc();
return 0;
}
VALUE uwsgi_rb_do_spooler(VALUE args) {
VALUE uwsgi_rb_embedded = rb_const_get(rb_cObject, rb_intern("UWSGI"));
return rb_funcall(uwsgi_rb_embedded, rb_intern("spooler"), 1, args);
}
void uwsgi_ruby_add_item(char *key, uint16_t keylen, char *val, uint16_t vallen, void *data) {
VALUE *spool_dict = (VALUE*) data;
rb_hash_aset(*spool_dict, rb_str_new(key, keylen), rb_str_new(val, vallen));
}
int uwsgi_rack_spooler(char *filename, char *buf, uint16_t len, char *body, size_t body_len) {
int error = 0;
VALUE uwsgi_rb_embedded = rb_const_get(rb_cObject, rb_intern("UWSGI"));
if (!rb_respond_to(uwsgi_rb_embedded, rb_intern("spooler"))) {
rb_gc();
return 0;
}
VALUE spool_dict = rb_hash_new();
if (uwsgi_hooked_parse(buf, len, uwsgi_ruby_add_item, (void *) &spool_dict)) {
rb_gc();
// malformed packet, destroy it
return 0;
}
rb_hash_aset(spool_dict, rb_str_new2("spooler_task_name"), rb_str_new2(filename));
if (body && body_len > 0) {
rb_hash_aset(spool_dict, rb_str_new2("body"), rb_str_new(body, body_len));
}
VALUE ret = rb_protect(uwsgi_rb_do_spooler, spool_dict, &error);
if (error) {
uwsgi_ruby_exception();
rb_gc();
return -1;
}
if (TYPE(ret) == T_FIXNUM) {
rb_gc();
return NUM2INT(ret);
}
// error, retry
rb_gc();
return -1;
}
struct uwsgi_plugin rack_plugin = {
.name = "rack",
.modifier1 = 7,
.init = uwsgi_rack_init,
.options = uwsgi_rack_options,
.request = uwsgi_rack_request,
.after_request = uwsgi_rack_after_request,
.signal_handler = uwsgi_rack_signal_handler,
.hijack_worker = uwsgi_rack_hijack,
.post_fork = uwsgi_rb_post_fork,
.spooler = uwsgi_rack_spooler,
.init_apps = uwsgi_rack_init_apps,
//.mount_app = uwsgi_rack_mount_app,
.magic = uwsgi_rack_magic,
.mule = uwsgi_rack_mule,
.mule_msg = uwsgi_rack_mule_msg,
.rpc = uwsgi_ruby_rpc,
.suspend = uwsgi_rack_suspend,
.resume = uwsgi_rack_resume,
};