Merge /Users/ry/gnosek_ebb
[ebb.git] / src / ebb_ruby.c
blobb44592a0bc59fc7a6fe4341c16d50b007cc595f1
1 /* A ruby binding to the ebb web server
2 * Copyright (c) 2008 Ry Dahl. This software is released under the MIT
3 * License. See README file for details.
4 */
5 #include <ruby.h>
6 #include <rubyio.h>
7 #include <rubysig.h>
8 #include <assert.h>
9 #include <fcntl.h>
10 #include <ebb.h>
11 #include <ev.h>
13 static VALUE cClient;
14 static VALUE global_fragment;
15 static VALUE global_path_info;
16 static VALUE global_query_string;
17 static VALUE global_request_body;
18 static VALUE global_request_method;
19 static VALUE global_request_path;
20 static VALUE global_request_uri;
21 static VALUE global_server_port;
22 static VALUE global_content_length;
23 static VALUE global_content_type;
24 static VALUE global_http_client_ip;
25 static VALUE global_http_prefix;
26 static VALUE global_http_version;
29 /* You don't want to run more than one server per Ruby VM. Really
30 * I'm making this explicit by not defining a Ebb::Server class but instead
31 * initializing a single server and single event loop on module load.
33 static ebb_server *server;
34 struct ev_loop *loop;
35 struct ev_idle idle_watcher;
37 /* Variables with a leading underscore are C-level variables */
39 #define ASCII_UPPER(ch) ('a' <= ch && ch <= 'z' ? ch - 'a' + 'A' : ch)
40 #ifndef RSTRING_PTR
41 # define RSTRING_PTR(s) (RSTRING(s)->ptr)
42 # define RSTRING_LEN(s) (RSTRING(s)->len)
43 #endif
45 static void attach_idle_watcher()
47 if(!ev_is_active(&idle_watcher)) {
48 ev_idle_start (loop, &idle_watcher);
53 static void detach_idle_watcher()
55 ev_idle_stop(loop, &idle_watcher);
58 static int clients_in_use_p()
60 int i;
61 for(i = 0; i < EBB_MAX_CLIENTS; i++)
62 if(server->clients[i].in_use) return TRUE;
63 return FALSE;
66 void request_cb(ebb_client *client, void *data)
68 VALUE waiting_clients = (VALUE)data;
69 VALUE rb_client = Data_Wrap_Struct(cClient, 0, 0, client);
70 rb_ary_push(waiting_clients, rb_client);
71 attach_idle_watcher();
74 VALUE server_listen_on_fd(VALUE _, VALUE sfd)
76 if(ebb_server_listen_on_fd(server, FIX2INT(sfd)) < 0)
77 rb_sys_fail("Problem listening on FD");
78 return Qnil;
81 VALUE server_listen_on_port(VALUE _, VALUE port)
83 if(ebb_server_listen_on_port(server, FIX2INT(port)) < 0)
84 rb_sys_fail("Problem listening on port");
85 return Qnil;
88 static void
89 idle_cb (struct ev_loop *loop, struct ev_idle *w, int revents) {
90 if(clients_in_use_p()) {
91 rb_thread_schedule();
92 } else if(!rb_thread_alone()) {
93 /* if you have another long running thread running besides the ones used
94 * for the webapp's requests you will run into performance problems in
95 * ruby 1.8.x because rb_thread_select is slow.
96 * (Don't worry - you're probably not doing this.)
98 struct timeval select_timeout = { tv_sec: 0, tv_usec: 50000 };
99 fd_set server_fd_set;
100 FD_ZERO(&server_fd_set);
101 FD_SET(server->fd, &server_fd_set);
102 rb_thread_select(server->fd+1, &server_fd_set, 0, 0, &select_timeout);
103 } else {
104 detach_idle_watcher();
108 VALUE server_process_connections(VALUE _)
110 TRAP_BEG;
111 ev_loop(loop, EVLOOP_ONESHOT);
112 TRAP_END;
113 return Qnil;
117 VALUE server_unlisten(VALUE _)
119 ebb_server_unlisten(server);
120 return Qnil;
123 VALUE server_open(VALUE _)
125 return server->open ? Qtrue : Qfalse;
128 VALUE env_field(struct ebb_env_item *item)
130 if(item->field) {
131 VALUE f = rb_str_new(NULL, RSTRING_LEN(global_http_prefix) + item->field_length);
132 memcpy( RSTRING_PTR(f)
133 , RSTRING_PTR(global_http_prefix)
134 , RSTRING_LEN(global_http_prefix)
136 int i;
137 for(i = 0; i < item->field_length; i++) {
138 char *ch = RSTRING_PTR(f) + RSTRING_LEN(global_http_prefix) + i;
139 *ch = item->field[i] == '-' ? '_' : ASCII_UPPER(item->field[i]);
141 return f;
143 switch(item->type) {
144 case MONGREL_CONTENT_LENGTH: return global_content_length;
145 case MONGREL_CONTENT_TYPE: return global_content_type;
146 case MONGREL_FRAGMENT: return global_fragment;
147 case MONGREL_HTTP_VERSION: return global_http_version;
148 case MONGREL_QUERY_STRING: return global_query_string;
149 case MONGREL_REQUEST_METHOD: return global_request_method;
150 case MONGREL_REQUEST_PATH: return global_request_path;
151 case MONGREL_REQUEST_URI: return global_request_uri;
153 fprintf(stderr, "Unknown environ type: %d", item->type);
154 assert(FALSE);
155 return Qnil;
159 VALUE env_value(struct ebb_env_item *item)
161 if(item->value_length > 0)
162 return rb_str_new(item->value, item->value_length);
163 else
164 return Qnil;
168 VALUE client_env(VALUE _, VALUE rb_client)
170 ebb_client *client;
171 VALUE field, value, env = rb_hash_new();
172 int i;
174 Data_Get_Struct(rb_client, ebb_client, client);
175 for(i=0; i < client->env_size; i++) {
176 field = env_field(&client->env[i]);
177 value = env_value(&client->env[i]);
178 rb_hash_aset(env, field, value);
181 if(client->server->port)
182 rb_hash_aset(env, global_server_port, rb_str_new2(client->server->port));
184 if(client->ip)
185 rb_hash_aset(env, global_http_client_ip, rb_str_new2(client->ip));
187 rb_hash_aset(env, global_path_info, rb_hash_aref(env, global_request_path));
188 return env;
192 VALUE client_read_input(VALUE _, VALUE client, VALUE size)
194 ebb_client *_client;
195 GString *_string;
196 VALUE string;
197 int _size = FIX2INT(size);
198 Data_Get_Struct(client, ebb_client, _client);
200 string = rb_str_buf_new( _size );
201 int nread = ebb_client_read(_client, RSTRING_PTR(string), _size);
202 #if RUBY_VERSION_CODE < 190
203 RSTRING(string)->len = nread;
204 #else
205 rb_str_set_len(string, nread);
206 #endif
208 if(nread < 0)
209 rb_raise(rb_eRuntimeError,"There was a problem reading from input (bad tmp file?)");
210 if(nread == 0)
211 return Qnil;
212 return string;
215 VALUE client_write_status(VALUE _, VALUE client, VALUE status, VALUE human_status)
217 ebb_client *_client;
218 Data_Get_Struct(client, ebb_client, _client);
219 ebb_client_write_status(_client, FIX2INT(status), StringValuePtr(human_status));
220 return Qnil;
223 VALUE client_write_header(VALUE _, VALUE client, VALUE field, VALUE value)
225 ebb_client *_client;
226 Data_Get_Struct(client, ebb_client, _client);
227 ebb_client_write_header(_client, StringValuePtr(field), StringValuePtr(value));
228 return Qnil;
231 VALUE client_write_body(VALUE _, VALUE client, VALUE string)
233 ebb_client *_client;
234 Data_Get_Struct(client, ebb_client, _client);
235 ebb_client_write_body(_client, RSTRING_PTR(string), RSTRING_LEN(string));
236 return Qnil;
240 VALUE client_release(VALUE _, VALUE rb_client)
242 ebb_client *client;
243 Data_Get_Struct(rb_client, ebb_client, client);
244 ebb_client_release(client);
245 return Qnil;
248 void Init_ebb_ext()
250 VALUE mEbb = rb_define_module("Ebb");
251 VALUE mFFI = rb_define_module_under(mEbb, "FFI");
253 rb_define_const(mEbb, "VERSION", rb_str_new2(EBB_VERSION));
255 /** Defines global strings in the init method. */
256 #define DEF_GLOBAL(N, val) global_##N = rb_obj_freeze(rb_str_new2(val)); rb_global_variable(&global_##N)
257 DEF_GLOBAL(content_length, "CONTENT_LENGTH");
258 DEF_GLOBAL(content_type, "CONTENT_TYPE");
259 DEF_GLOBAL(fragment, "FRAGMENT");
260 DEF_GLOBAL(path_info, "PATH_INFO");
261 DEF_GLOBAL(query_string, "QUERY_STRING");
262 DEF_GLOBAL(request_body, "REQUEST_BODY");
263 DEF_GLOBAL(request_method, "REQUEST_METHOD");
264 DEF_GLOBAL(request_path, "REQUEST_PATH");
265 DEF_GLOBAL(request_uri, "REQUEST_URI");
266 DEF_GLOBAL(server_port, "SERVER_PORT");
267 DEF_GLOBAL(http_client_ip, "HTTP_CLIENT_IP");
268 DEF_GLOBAL(http_prefix, "HTTP_");
269 DEF_GLOBAL(http_version, "HTTP_VERSION");
271 rb_define_singleton_method(mFFI, "server_process_connections", server_process_connections, 0);
272 rb_define_singleton_method(mFFI, "server_listen_on_fd", server_listen_on_fd, 1);
273 rb_define_singleton_method(mFFI, "server_listen_on_port", server_listen_on_port, 1);
274 rb_define_singleton_method(mFFI, "server_unlisten", server_unlisten, 0);
275 rb_define_singleton_method(mFFI, "server_open?", server_open, 0);
277 cClient = rb_define_class_under(mEbb, "Client", rb_cObject);
278 rb_define_singleton_method(mFFI, "client_read_input", client_read_input, 2);
279 rb_define_singleton_method(mFFI, "client_write_status", client_write_status, 3);
280 rb_define_singleton_method(mFFI, "client_write_header", client_write_header, 3);
281 rb_define_singleton_method(mFFI, "client_write_body", client_write_body, 2);
282 rb_define_singleton_method(mFFI, "client_env", client_env, 1);
283 rb_define_singleton_method(mFFI, "client_release", client_release, 1);
285 /* initialize ebb_server */
286 loop = ev_default_loop (0);
288 ev_idle_init (&idle_watcher, idle_cb);
289 attach_idle_watcher();
291 server = ebb_server_alloc();
292 VALUE waiting_clients = rb_ary_new();
293 rb_iv_set(mFFI, "@waiting_clients", waiting_clients);
294 ebb_server_init(server, loop, request_cb, (void*)waiting_clients);