/* WebROaR - Ruby Application Server - http://webroar.in/
* Copyright (C) 2009 Goonj LLC
*
* This file is part of WebROaR.
*
* WebROaR is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* WebROaR is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with WebROaR. If not, see .
*/
#include
#include
#include "wkr_static.h"
/** for handling of $0, courtesy http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/16221 */
static VALUE gProgName = Qnil;
static VALUE cReq;
static VALUE cRequestHandler;
static VALUE rb_req;
static http_t *g_http;
#ifndef RSTRING_PTR
# define RSTRING_PTR(s) (RSTRING(s)->ptr)
# define RSTRING_LEN(s) (RSTRING(s)->len)
#endif
#ifndef RARRAY_PTR
# define RARRAY_PTR(s) (RARRAY(s)->ptr)
# define RARRAY_LEN(s) (RARRAY(s)->len)
#endif
/** Call 'Ruby' method 'RequestHandler.process' */
void http_req_process() {
LOG_FUNCTION
if(g_http->is_static){
static_file_process(g_http);
}else{
rb_funcall(cRequestHandler, rb_intern("process"),1,rb_req);
// New thread could be created during request processing, start idle watcher to keep scheduling it
start_idle_watcher();
}
}
/** Read request by 'Ruby' */
VALUE read_request(VALUE _, VALUE req, VALUE size) {
LOG_FUNCTION
VALUE string;
int _size = FIX2INT(size);
string = rb_str_buf_new( _size );
int nread = http_req_body_read(g_http->req, RSTRING_PTR(string), _size);
#if RUBY_VERSION < 190
RSTRING_LEN(string) = nread;
#else
rb_str_set_len(string, nread);
#endif
if(nread < 0)
rb_raise(rb_eRuntimeError,"There was a problem reading from input (bad tmp file?)");
if(nread == 0)
return Qnil;
return string;
}
/** HTTP response headers callback */
VALUE req_write_headers(VALUE _, VALUE req, VALUE status, VALUE headers, VALUE body_length) {
LOG_FUNCTION
size_t resp_body_len;
http_resp_t *rsp = g_http->resp;
http_req_set(g_http->req);
rsp->resp_code = FIX2INT(status);
resp_body_len = FIX2INT(body_length);
char *str = RSTRING_PTR(headers);
size_t len = RSTRING_LEN(headers);
wr_string_new(rsp->header,str, len);
if(resp_body_len > 0) {
wr_buffer_create(rsp->resp_body, resp_body_len);
}
LOG_DEBUG(DEBUG, "req_write_headers() status=%d header_len = %d, body_length = %d",
rsp->resp_code, rsp->header.len, resp_body_len);
return Qnil;
}
/** HTTP response body callback */
VALUE req_write_body(VALUE _, VALUE req, VALUE string) {
LOG_FUNCTION
http_resp_t *rsp = g_http->resp;
char *str = RSTRING_PTR(string);
size_t len = RSTRING_LEN(string);
wr_buffer_add(rsp->resp_body, str, len);
return Qnil;
}
/** HTTP response completed callback */
VALUE req_resp_completed(VALUE _, VALUE req) {
LOG_FUNCTION
wkr_t *w = (wkr_t*) g_http->wkr;
http_resp_process(g_http->resp);
ev_io_init(&(w->w_req), http_resp_scgi_write_cb, w->req_fd,EV_WRITE);
ev_io_start(w->loop, &w->w_req);
return Qnil;
}
/** Read SCGI headers by 'Ruby' and convert it into hash */
VALUE req_env(VALUE _, VALUE rb_req) {
LOG_FUNCTION
scgi_t *scgi;
scgi_header_t *header;
VALUE field, value, hash = rb_hash_new();
scgi = g_http->req->scgi;
if(scgi) {
header = scgi->header_list;
while(header) {
LOG_DEBUG(DEBUG,"%s : %s", scgi->header + header->field_offset, scgi->header + header->value_offset);
field = rb_str_new2(scgi->header + header->field_offset);
value = rb_str_new2(scgi->header + header->value_offset);
rb_hash_aset(hash, field, value);
header = header->next;
}
}
return hash;
}
/** Logging 'Ruby' messsage */
VALUE log(VALUE _, VALUE message_type, VALUE severity, VALUE log_message) {
a_log(StringValuePtr(message_type), FIX2INT(severity), StringValuePtr(log_message));
return Qnil;
}
/** for handling of $0 */
static void setProgName(VALUE name, ID id) {
LOG_FUNCTION
gProgName = rb_obj_as_string(name);
rb_obj_taint(gProgName);
}
/** Initialize ruby interface */
void init_ruby_interface(http_t *h) {
LOG_FUNCTION
VALUE mObj;
mObj = rb_define_module("Webroar");
cReq = rb_define_class_under(mObj, "Client", rb_cObject);
cRequestHandler = rb_define_class_under(mObj, "RequestHandler", rb_cObject);
rb_define_singleton_method(mObj, "log", log, 3);
rb_define_singleton_method(mObj, "read_request", read_request, 2);
rb_define_singleton_method(mObj, "client_env", req_env, 1);
rb_define_singleton_method(mObj, "client_write_headers", req_write_headers, 4);
rb_define_singleton_method(mObj, "client_write_body", req_write_body, 2);
rb_define_singleton_method(mObj, "client_resp_completed", req_resp_completed, 1);
/** override Ruby's hooked handlers for $0 so that $0 can be
* treated as pure Ruby value and modified without restriction
*/
rb_define_hooked_variable("$0", &gProgName, 0, setProgName);
}
static int init_ruby_interpreter(wkr_t *w) {
LOG_FUNCTION
//initializing ruby interpreter ... referred from main.c and ruby.c of ruby-dev
//preparing pseudo argc and argv to set ruby_option
int c = 2, retval = 0;
char **v = NULL;
v=(char**)malloc(sizeof(char*)*c);
if(!v) {
LOG_ERROR(WARN,"Memory allocation to pseudo argv failed.");
retval = -1;
goto err;
}
v[0] = v[1] = NULL;
v[0] = (char*) malloc(sizeof(char)*20);
if(!v[0]) {
LOG_ERROR(WARN,"Memory allocation to pseudo argv failed.");
retval = -1;
goto err;
}
strcpy(v[0],"webroar-worker");
v[1] = (char*) malloc(sizeof(char)*(w->tmp->script_path.len+1));
if(!v[1]) {
LOG_ERROR(WARN,"Memory allocation to pseudo argv failed.");
retval = -1;
goto err;
}
strcpy(v[1], w->tmp->script_path.str);
//RUBY_INIT_STACK;
ruby_init();
ruby_script("webroar-worker");
ruby_init_loadpath();
ruby_options(c, v);
LOG_INFO("Ruby interpreter initialized successfully");
err:
if(v && v[0]) {
free(v[0]);
}
if(v && v[1]) {
free(v[1]);
}
if(v) {
free(v);
}
return retval;
}
#if RUBY_VERSION < 190
/* Exception handling code for Ruby script, loaded from embedded interpreter is
based on code given at http://zzz.zggg.com/perl_ruby_multithreading_embedding.html
*/
static inline void show_error_pos() {
LOG_FUNCTION
ID this_func = rb_frame_last_func();
if (ruby_sourcefile) {
if (this_func) {
LOG_INFO("show_error_pos() %s:%s:in %s", ruby_sourcefile,ruby_sourceline,rb_id2name(this_func));
} else {
LOG_INFO("show_error_pos() %s:%s", ruby_sourcefile,ruby_sourceline);
}
}
}
static inline void show_exception_info() {
LOG_FUNCTION
if (NIL_P(ruby_errinfo))
return;
VALUE errat = rb_funcall(ruby_errinfo, rb_intern("backtrace"), 0);
if (!NIL_P(errat)) {
VALUE mesg = RARRAY_PTR(errat)[0];
if (NIL_P(mesg)) {
show_error_pos();
} else {
LOG_INFO("%s",RSTRING_PTR(mesg));
}
}
VALUE eclass = CLASS_OF(ruby_errinfo);
char* einfo;
int elen;
int state;
VALUE estr = rb_protect(rb_obj_as_string, ruby_errinfo, &state);
if (state) {
einfo = "";
elen = 0;
} else {
einfo = RSTRING_PTR(estr);
elen = RSTRING_LEN(estr);
}
if (eclass == rb_eRuntimeError && elen == 0) {
LOG_INFO(": unhandled exception");
} else {
VALUE epath;
epath = rb_class_path(eclass);
if (elen == 0) {
LOG_INFO("%s: ",RSTRING_PTR(epath));
} else {
char* tail = 0;
int len = elen;
if ((RSTRING_PTR(epath))[0] == '#')
epath = 0;
if (tail = strchr(einfo, '\n')) {
len = tail - einfo;
tail++;
}
LOG_INFO("%s: ", einfo);
if (epath) {
LOG_INFO(" (%s",RSTRING_PTR(epath));
}
if (tail) {
LOG_INFO("%s",tail);
}
}
}
if (!NIL_P(errat)) {
const int TRACE_HEAD = 8;
const int TRACE_TAIL = 5;
const int TRACE_MAX = TRACE_HEAD + TRACE_TAIL + 5;
long len = RARRAY_LEN(errat);
int i = 1;
for (i = 1; i < len; ++i) {
if (TYPE(RARRAY_PTR(errat)[i]) == T_STRING) {
LOG_INFO(" from %s ",RSTRING_PTR(RARRAY_PTR(errat)[i]));
}
if (i == TRACE_HEAD && len > TRACE_MAX) {
LOG_INFO(" ... %d ld levels... ",len - TRACE_HEAD - TRACE_TAIL);
i = len - TRACE_TAIL;
}
}
}
}
#endif
/** Load rack adapter */
int load_rack_adapter(wkr_tmp_t *tmp) {
LOG_FUNCTION
int state = 0, path_length=0;
char *path;
LOG_DEBUG(DEBUG,"load_rack_adapter() Application path is = %s",tmp->path.str);
// TODO: Keys are being setup as strings. Doesn't allow standard symbol based access in ruby script.
VALUE g_options=rb_hash_new();
rb_hash_aset(g_options,rb_str_new("root",4),rb_str_new2(tmp->path.str));
rb_hash_aset(g_options,rb_str_new("environment",11),rb_str_new2(tmp->env.str));
rb_hash_aset(g_options,rb_str_new("app_type",8),rb_str_new2(tmp->type.str));
rb_hash_aset(g_options,rb_str_new("app_name",8),rb_str_new2(tmp->name.str));
rb_hash_aset(g_options, rb_str_new("webroar_root",12), rb_str_new2(tmp->root_path.str));
if(tmp->resolver.len > 1) {
rb_hash_aset(g_options,rb_str_new("prefix",6),rb_str_new2(tmp->resolver.str));
} else {
rb_hash_aset(g_options,rb_str_new("prefix",6),rb_str_new2(""));
}
if(tmp->profiler == 'y') {
LOG_DEBUG(DEBUG,"Analytics ebabled");
rb_hash_aset(g_options,rb_str_new("app_profiling",13),rb_str_new("yes",3));
} else {
LOG_DEBUG(DEBUG,"Analytics disabled");
rb_hash_aset(g_options,rb_str_new("app_profiling",13),rb_str_new("no",2));
}
if(tmp->keep_alive) {
LOG_DEBUG(DEBUG,"setting keep-alive true");
rb_hash_aset(g_options,rb_str_new("keep_alive",10),Qtrue);
} else {
LOG_DEBUG(DEBUG,"setting keep-alive false");
rb_hash_aset(g_options,rb_str_new("keep_alive",10),Qfalse);
}
#ifdef L_DEBUG
LOG_DEBUG(DEBUG,"setting debug true");
rb_hash_aset(g_options,rb_str_new("debug",5),Qtrue);
#else
LOG_DEBUG(DEBUG,"setting debug false");
rb_hash_aset(g_options,rb_str_new("debug",5),Qfalse);
#endif
rb_gv_set("g_options",g_options);
#if RUBY_VERSION < 190
rb_load_protect(rb_str_new2(tmp->script_path.str), 0, &state);
//free(path);
LOG_DEBUG(DEBUG,"state=%d",state);
if (state) {
switch (state) {
case 0x1: // TAG_RETURN
LOG_INFO("unexpected return");
show_error_pos();
break;
case 0x2: // TAG_BREAK
LOG_INFO("unexpected break");
show_error_pos();
break;
case 0x3: // TAG_NEXT
LOG_INFO("unexpected next");
show_error_pos();
break;
case 0x4: // TAG_RETRY
LOG_INFO("retry outside of rescue clause");
show_error_pos();
break;
case 0x5: // TAG_REDO
LOG_INFO("unexpected redo");
show_error_pos();
break;
case 0x6: // TAG_RAISE
case 0x8: // TAG_FATAL
show_exception_info();
break;
default:
LOG_INFO("unknown long jump status ");
}
// TODO: No magic numbers please
return -1;
}
#else
rb_require(tmp->script_path.str);
#endif
return 0;
}
http_t* http_new(void *ptr) {
LOG_FUNCTION
wkr_t *w = (wkr_t*) ptr;
http_t *h = wr_malloc(http_t);
if(h==NULL) {
return NULL;
}
w->http = h;
h->wkr = ptr;
h->req = http_req_new();
h->resp = http_resp_new();
if(h->req==NULL || h->resp == NULL) {
http_free(&h);
return NULL;
}
if(w->tmp->is_static){
if(static_module_init(w->tmp->root_path.str)!=0){
http_free(&h);
return NULL;
}
h->is_static = 1;
}else{
if(init_ruby_interpreter(w)!=0) {
http_free(&h);
return NULL;
}
init_ruby_interface(h);
if(load_rack_adapter(w->tmp) < 0) {
http_free(&h);
return NULL;
}
rb_req = Data_Wrap_Struct(cReq, 0, 0, h->req);
h->is_static = 0;
}
g_http = h;
return h;
}
void http_free(http_t** http) {
LOG_FUNCTION
http_t *h = *http;
if(h) {
if(h->is_static){
static_module_free();
}else{
ruby_finalize();
}
if(h->resp)
http_resp_free(&h->resp);
if(h->req)
http_req_free(&h->req);
free(h);
}
*http = NULL;
}
void http_set(http_t* h) {
LOG_FUNCTION
if(h->resp)
http_resp_set(h->resp);
if(h->req)
http_req_set(h->req);
#ifdef L_DEBUG
h->conn_id = h->req_id = 0;
#endif
}