/* 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
extern config_t *Config;
/** 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->stat){
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' message */
VALUE log_message(VALUE _, VALUE message_type, VALUE severity, VALUE message) {
a_log(StringValuePtr(message_type), FIX2INT(severity), StringValuePtr(message));
return Qnil;
}
/** for handling of $0 */
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_message", log_message, 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);
}
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)*(Config->Worker.File.app_loader.len +1));
if(!v[1]) {
LOG_ERROR(WARN,"Memory allocation to pseudo argv failed.");
retval = -1;
goto err;
}
strcpy(v[1], Config->Worker.File.app_loader.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;
}
/** Load rack adapter */
int load_rack_adapter(wkr_tmp_t *tmp) {
LOG_FUNCTION
int state = 0;
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);
rb_protect(RUBY_METHOD_FUNC(rb_require), (VALUE)Config->Worker.File.app_loader.str, &state);
LOG_DEBUG(DEBUG, "state=%d", state);
if ( state != 0 ) {
LOG_ERROR(FATAL, "Some problem occurred while loading application.");
return -1;
}
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();
h->stat = NULL;
if(h->req==NULL || h->resp == NULL) {
http_free(&h);
return NULL;
}
if(w->tmp->is_static){
h->stat = static_server_new(w);
if(h->stat == NULL){
http_free(&h);
return NULL;
}
}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);
}
g_http = h;
return h;
}
void http_free(http_t** http) {
LOG_FUNCTION
http_t *h = *http;
if(h) {
if(h->stat == NULL){
ruby_finalize();
}
if(h->resp) http_resp_free(&h->resp);
if(h->req) http_req_free(&h->req);
if(h->stat) static_server_free(h->stat);
free(h);
}
*http = NULL;
}