Skip to content
Browse files

Initial commit

  • Loading branch information...
0 parents commit 14f5d8ed5ea148efe96d50919a5b74e03315c6d0 @ayanko committed
6 .gitignore
@@ -0,0 +1,6 @@
+*.gem
+.bundle
+Gemfile.lock
+pkg/*
+lib/*.so
+tmp/*
5 Gemfile
@@ -0,0 +1,5 @@
+source "http://rubygems.org"
+
+gemspec
+
+gem "rake-compiler"
3 README.md
@@ -0,0 +1,3 @@
+## Libevent
+
+C extension to [libevent](http://libevent.org/) library.
4 Rakefile
@@ -0,0 +1,4 @@
+require "bundler/gem_tasks"
+require 'rake/extensiontask'
+
+Rake::ExtensionTask.new("libevent_ext")
94 ext/libevent_ext/base.c
@@ -0,0 +1,94 @@
+#include "ext.h"
+
+static VALUE t_allocate(VALUE klass);
+
+static void t_free(Libevent_Base *base);
+
+static VALUE t_dispatch(VALUE self);
+
+static VALUE t_exit_loop(VALUE self);
+
+static VALUE t_break_loop(VALUE self);
+
+void Init_libevent_base() {
+ cLibevent_Base = rb_define_class_under(mLibevent, "Base", rb_cObject);
+
+ rb_define_alloc_func(cLibevent_Base, t_allocate);
+
+ rb_define_method(cLibevent_Base, "dispatch", t_dispatch, 0);
+ rb_define_method(cLibevent_Base, "exit_loop", t_exit_loop, 0);
+ rb_define_method(cLibevent_Base, "break_loop", t_break_loop, 0);
+}
+
+/*
+ * Allocate memmory
+ */
+static VALUE t_allocate(VALUE klass) {
+ Libevent_Base *base;
+
+ base = ALLOC(Libevent_Base);
+ base->ev_base = event_base_new();
+
+ if ( !base->ev_base ) {
+ rb_fatal("Couldn't get an event base");
+ }
+
+ return Data_Wrap_Struct(klass, 0, t_free, base);
+}
+
+/*
+ * Free memmory
+ */
+static void t_free(Libevent_Base *base) {
+ event_base_free(base->ev_base);
+}
+
+/*
+ * Event dispatching loop.
+ *
+ * This loop will run the event base until either there are no more added events,
+ * or until something calls Libevent::Base#break_loop or Base#exit_loop.
+ * @see #break_loop
+ * @see #exit_loop
+*/
+static VALUE t_dispatch(VALUE self) {
+ Libevent_Base *base;
+ int status;
+
+ Data_Get_Struct(self, Libevent_Base, base);
+ status = event_base_dispatch(base->ev_base);
+
+ return INT2FIX(status);
+}
+
+/*
+ * Exit the event loop after the specified time
+ *
+ * @todo specified time is not implemented
+ */
+static VALUE t_exit_loop(VALUE self) {
+ Libevent_Base *base;
+ int status;
+
+ Data_Get_Struct(self, Libevent_Base, base);
+ status = event_base_loopexit(base->ev_base, NULL);
+
+ return (status == -1 ? Qfalse : Qtrue);
+}
+/*
+ * Abort the active event_base loop immediately.
+ *
+ * It will abort the loop after the next event is completed;
+ * event_base_loopbreak() is typically invoked from this event's callback.
+ * This behavior is analogous to the "break;" statement.
+ * Subsequent invocations of event_loop() will proceed normally.
+ */
+static VALUE t_break_loop(VALUE self) {
+ Libevent_Base *base;
+ int status;
+
+ Data_Get_Struct(self, Libevent_Base, base);
+ status = event_base_loopbreak(base->ev_base);
+
+ return (status == -1 ? Qfalse : Qtrue);
+}
10 ext/libevent_ext/ext.c
@@ -0,0 +1,10 @@
+#include "ext.h"
+
+void Init_libevent_ext() {
+ mLibevent = rb_define_module("Libevent");
+
+ Init_libevent_base();
+ Init_libevent_signal();
+ Init_libevent_http();
+ Init_libevent_http_request();
+}
40 ext/libevent_ext/ext.h
@@ -0,0 +1,40 @@
+#ifndef LIBEVENT_EXT_H
+#define LIBEVENT_EXT_H
+
+#include "ruby.h"
+#include "ruby18_compat.h"
+
+#include <event.h>
+#include <evhttp.h>
+
+VALUE mLibevent;
+VALUE cLibevent_Base;
+VALUE cLibevent_Signal;
+VALUE cLibevent_Http;
+VALUE cLibevent_HttpRequest;
+
+typedef struct Libevent_Base {
+ struct event_base *ev_base;
+} Libevent_Base;
+
+typedef struct Libevent_Signal {
+ struct event *ev_event;
+} Libevent_Signal;
+
+typedef struct Libevent_Http {
+ struct event_base *ev_base;
+ struct evhttp *ev_http;
+ struct evhttp *ev_http_parent;
+} Libevent_Http;
+
+typedef struct Libevent_HttpRequest {
+ struct evhttp_request *ev_request;
+ struct evbuffer *ev_buffer;
+} Libevent_HttpRequest;
+
+void Init_libevent_base();
+void Init_libevent_signal();
+void Init_libevent_http();
+void Init_libevent_http_request();
+
+#endif
7 ext/libevent_ext/extconf.rb
@@ -0,0 +1,7 @@
+require "mkmf"
+
+$CFLAGS << ' -Wall '
+
+$LDFLAGS << ' ' << `pkg-config --libs libevent`
+
+create_makefile('libevent_ext')
184 ext/libevent_ext/http.c
@@ -0,0 +1,184 @@
+#include "ext.h"
+
+static VALUE t_allocate(VALUE klass);
+
+static void t_free(Libevent_Http *http);
+
+static VALUE t_initialize(VALUE self, VALUE object);
+
+static VALUE t_bind_socket(VALUE self, VALUE address, VALUE port);
+
+static VALUE t_set_request_handler(VALUE self, VALUE handler);
+
+static VALUE t_set_timeout(VALUE self, VALUE timeout);
+
+static void t_request_handler(struct evhttp_request *ev_request, void *context);
+
+static VALUE t_add_virtual_host(VALUE self, VALUE domain, VALUE vhttp);
+
+void Init_libevent_http() {
+ cLibevent_Http = rb_define_class_under(mLibevent, "Http", rb_cObject);
+
+ rb_define_alloc_func(cLibevent_Http, t_allocate);
+
+ rb_define_method(cLibevent_Http, "initialize", t_initialize, 1);
+ rb_define_method(cLibevent_Http, "bind_socket", t_bind_socket, 2);
+ rb_define_method(cLibevent_Http, "set_request_handler", t_set_request_handler, 1);
+ rb_define_method(cLibevent_Http, "set_timeout", t_set_timeout, 1);
+ rb_define_method(cLibevent_Http, "add_virtual_host", t_add_virtual_host, 2);
+}
+
+/*
+ * Allocate memory
+ */
+static VALUE t_allocate(VALUE klass) {
+ Libevent_Http *http = ALLOC(Libevent_Http);
+
+ http->ev_base = NULL;
+ http->ev_http = NULL;
+ http->ev_http_parent = NULL;
+
+ return Data_Wrap_Struct(klass, 0, t_free, http);
+}
+
+/*
+ * Free memory
+ */
+static void t_free(Libevent_Http *http) {
+ if ( http->ev_http ) {
+ // main http frees all associated vhosts
+ if ( http->ev_http_parent == NULL )
+ evhttp_free(http->ev_http);
+ }
+
+ xfree(http);
+}
+
+/*
+ * Initialize http instance and allocate evhttp structure
+ *
+ * @param [Base] object
+ * @yield [self]
+ */
+static VALUE t_initialize(VALUE self, VALUE object) {
+ Libevent_Http *http;
+ Libevent_Base *base;
+
+ Data_Get_Struct(self, Libevent_Http, http);
+ Data_Get_Struct(object, Libevent_Base, base);
+
+ http->ev_base = base->ev_base;
+ http->ev_http = evhttp_new(http->ev_base);
+
+ if (!http->ev_http) {
+ rb_fatal("Couldn't create evhttp");
+ }
+
+ rb_iv_set(self, "@base", object);
+
+ if (rb_block_given_p())
+ rb_yield(self);
+
+ return self;
+}
+
+/*
+ * Binds an HTTP instance on the specified address and port.
+ * Can be called multiple times to bind the same http server to multiple different ports.
+ * @param [String] address IP address
+ * @param [Fixnum] port port to bind
+ * @return [true] on success
+ * @return [false] on failure
+ */
+static VALUE t_bind_socket(VALUE self, VALUE address, VALUE port) {
+ Libevent_Http *http;
+ int status;
+
+ Data_Get_Struct(self, Libevent_Http, http);
+ Check_Type(address, T_STRING);
+ Check_Type(port, T_FIXNUM);
+ status = evhttp_bind_socket(http->ev_http, RSTRING_PTR(address), FIX2INT(port));
+
+ return ( status == -1 ? Qfalse : Qtrue );
+}
+
+/*
+ * Set a callback for all requests that are not caught by specific callbacks.
+ * @note
+ * handler should response to :call method.
+ *
+ * Libevent::HttpRequest instance will be passed to handler as first argument
+ *
+ * @param [Object] handler object that response to :call
+ * @return [nil]
+ */
+static VALUE t_set_request_handler(VALUE self, VALUE handler) {
+ Libevent_Http *http;
+
+ Data_Get_Struct(self, Libevent_Http, http);
+
+ if ( !rb_respond_to(handler, rb_intern("call")))
+ rb_raise(rb_eArgError, "handler does not response to call method");
+
+ rb_iv_set(self, "@request_handler", handler);
+ evhttp_set_gencb(http->ev_http, t_request_handler, (void *)handler);
+
+ return Qnil;
+}
+
+/*
+ * C callback function that create HttpRequest instance and call Ruby handler object with it.
+ */
+static void t_request_handler(struct evhttp_request *ev_request, void* context) {
+ Libevent_HttpRequest *le_http_request;
+ VALUE http_request;
+ VALUE handler = (VALUE)context;
+
+ http_request = rb_obj_alloc(cLibevent_HttpRequest);
+ Data_Get_Struct(http_request, Libevent_HttpRequest, le_http_request);
+ le_http_request->ev_request = ev_request;
+ rb_obj_call_init(http_request, 0, 0);
+
+ rb_funcall(handler, rb_intern("call"), 1, http_request);
+}
+
+/*
+ * Set the timeout for an HTTP request.
+ * @param [Fixnum] timeout he timeout, in seconds
+ * return nil
+ */
+static VALUE t_set_timeout(VALUE self, VALUE timeout) {
+ Libevent_Http *http;
+
+ Data_Get_Struct(self, Libevent_Http, http);
+ evhttp_set_timeout(http->ev_http, NUM2INT(timeout));
+
+ return Qnil;
+}
+
+/*
+ * Adds a virtual host to the http server.
+ * It is possible to have hierarchical vhosts.
+ * @param [String] domain the glob pattern against which the hostname is matched. The match is case insensitive and follows otherwise regular shell matching.
+ * @param [Http] vhttp the virtual host to add the regular http server
+ * @return [true false]
+ * @example
+ * A vhost with the pattern *.example.com may have other vhosts with patterns foo.example.com and bar.example.com associated with it.
+ * @note
+ * A virtual host is a newly initialized evhttp object that has request handler
+ * It most not have any listing sockets associated with it.
+ */
+static VALUE t_add_virtual_host(VALUE self, VALUE domain, VALUE vhttp) {
+ Libevent_Http *le_http;
+ Libevent_Http *le_vhttp;
+ int status;
+
+ Data_Get_Struct(self, Libevent_Http, le_http);
+ Data_Get_Struct(vhttp, Libevent_Http, le_vhttp);
+ Check_Type(domain, T_STRING);
+ le_vhttp->ev_http_parent = le_http->ev_http;
+ status = evhttp_add_virtual_host(le_http->ev_http, RSTRING_PTR(domain), le_vhttp->ev_http);
+
+ return ( status == -1 ? Qfalse : Qtrue );
+}
+
466 ext/libevent_ext/http_request.c
@@ -0,0 +1,466 @@
+#include "ext.h"
+
+static VALUE t_allocate(VALUE klass);
+
+static void t_free(Libevent_HttpRequest *http_request);
+
+static VALUE t_initialize(VALUE self);
+
+static VALUE t_get_remote_host(VALUE self);
+
+static VALUE t_get_remote_port(VALUE self);
+
+static VALUE t_get_http_version(VALUE self);
+
+static VALUE t_get_command(VALUE self);
+
+static VALUE t_get_uri(VALUE self);
+
+static VALUE t_get_uri_scheme(VALUE self);
+
+static VALUE t_get_uri_path(VALUE self);
+
+static VALUE t_get_uri_query(VALUE self);
+
+static VALUE t_get_host(VALUE self);
+
+static VALUE t_get_input_headers(VALUE self);
+
+static VALUE t_get_body(VALUE self);
+
+static VALUE t_send_reply(VALUE self, VALUE code, VALUE headers, VALUE body);
+
+static VALUE t_send_error(VALUE self, VALUE code, VALUE reason);
+
+static VALUE t_add_output_header(VALUE self, VALUE key, VALUE value);
+
+static VALUE t_set_output_headers(VALUE self, VALUE headers);
+
+static VALUE t_clear_output_headers(VALUE self);
+
+static VALUE t_send_chunk(VALUE chunk, VALUE self);
+
+static VALUE t_send_reply_start(VALUE self, VALUE code, VALUE reason);
+
+static VALUE t_send_reply_chunk(VALUE self, VALUE chunk);
+
+static VALUE t_send_reply_end(VALUE self);
+
+void Init_libevent_http_request() {
+ cLibevent_HttpRequest = rb_define_class_under(mLibevent, "HttpRequest", rb_cObject);
+
+ rb_define_alloc_func(cLibevent_HttpRequest, t_allocate);
+
+ rb_define_method(cLibevent_HttpRequest, "initialize", t_initialize, 0);
+ rb_define_method(cLibevent_HttpRequest, "get_remote_host", t_get_remote_host, 0);
+ rb_define_method(cLibevent_HttpRequest, "get_remote_port", t_get_remote_port, 0);
+ rb_define_method(cLibevent_HttpRequest, "get_http_version", t_get_http_version, 0);
+ rb_define_method(cLibevent_HttpRequest, "get_command", t_get_command, 0);
+ rb_define_method(cLibevent_HttpRequest, "get_uri", t_get_uri, 0);
+ rb_define_method(cLibevent_HttpRequest, "get_uri_scheme", t_get_uri_scheme, 0);
+ rb_define_method(cLibevent_HttpRequest, "get_uri_path", t_get_uri_path, 0);
+ rb_define_method(cLibevent_HttpRequest, "get_uri_query", t_get_uri_query, 0);
+ rb_define_method(cLibevent_HttpRequest, "get_host", t_get_host, 0);
+ rb_define_method(cLibevent_HttpRequest, "get_input_headers", t_get_input_headers, 0);
+ rb_define_method(cLibevent_HttpRequest, "get_body", t_get_body, 0);
+ rb_define_method(cLibevent_HttpRequest, "add_output_header", t_add_output_header, 2);
+ rb_define_method(cLibevent_HttpRequest, "set_output_headers", t_set_output_headers, 1);
+ rb_define_method(cLibevent_HttpRequest, "clear_output_headers", t_clear_output_headers, 0);
+ rb_define_method(cLibevent_HttpRequest, "send_reply", t_send_reply, 3);
+ rb_define_method(cLibevent_HttpRequest, "send_error", t_send_error, 2);
+ rb_define_method(cLibevent_HttpRequest, "send_reply_start", t_send_reply_start, 2);
+ rb_define_method(cLibevent_HttpRequest, "send_reply_chunk", t_send_reply_chunk, 1);
+ rb_define_method(cLibevent_HttpRequest, "send_reply_end", t_send_reply_end, 0);
+}
+
+/*
+ * Allocate memory
+ */
+static VALUE t_allocate(VALUE klass) {
+ Libevent_HttpRequest *http_request = ALLOC(Libevent_HttpRequest);
+
+ http_request->ev_request = NULL;
+ http_request->ev_buffer = evbuffer_new();
+
+ return Data_Wrap_Struct(klass, 0, t_free, http_request);
+}
+
+/*
+ * Free memory
+ */
+static void t_free(Libevent_HttpRequest *http_request) {
+ if ( http_request->ev_buffer != NULL ) {
+ evbuffer_free(http_request->ev_buffer);
+ }
+
+ xfree(http_request);
+}
+
+/*
+ * Initialize HttpRequest object
+ * @raise [ArgumentError] if object created withot evhttp_request c data
+ */
+static VALUE t_initialize(VALUE self) {
+ Libevent_HttpRequest *http_request;
+
+ Data_Get_Struct(self, Libevent_HttpRequest, http_request);
+ if ( !http_request->ev_request )
+ rb_raise(rb_eArgError, "http_request C data is not given");
+
+ return self;
+}
+
+/*
+ * Add output header
+ * @param [String] key a header key
+ * @param [String] value a header value
+ * @return [true false]
+ */
+static VALUE t_add_output_header(VALUE self, VALUE key, VALUE value) {
+ Libevent_HttpRequest *http_request;
+ struct evkeyvalq *ev_headers;
+ int status;
+
+ Data_Get_Struct(self, Libevent_HttpRequest, http_request);
+ ev_headers = evhttp_request_get_output_headers(http_request->ev_request);
+ status = evhttp_add_header(ev_headers, RSTRING_PTR(key), RSTRING_PTR(value));
+
+ return ( status == -1 ? Qfalse : Qtrue);
+}
+
+/*
+ * Set request output headers
+ * @param [Hash Array] headers
+ * @return [nil]
+ */
+static VALUE t_set_output_headers(VALUE self, VALUE headers) {
+ Libevent_HttpRequest *http_request;
+ VALUE pairs;
+ VALUE pair;
+ VALUE key;
+ VALUE val;
+ struct evkeyvalq *ev_headers;
+ int i;
+
+ Data_Get_Struct(self, Libevent_HttpRequest, http_request);
+
+ ev_headers = evhttp_request_get_output_headers(http_request->ev_request);
+
+ pairs = rb_funcall(headers, rb_intern("to_a"), 0);
+
+ for ( i=0 ; i < RARRAY_LEN(pairs); i++ ) {
+ pair = rb_ary_entry(pairs, i);
+ key = rb_ary_entry(pair, 0);
+ val = rb_ary_entry(pair, 1);
+ evhttp_add_header(ev_headers, RSTRING_PTR(key), RSTRING_PTR(val));
+ }
+
+ return Qnil;
+}
+
+/*
+ * Removes all output headers.
+ * @return [nil]
+ */
+static VALUE t_clear_output_headers(VALUE self) {
+ Libevent_HttpRequest *http_request;
+ struct evkeyvalq *ev_headers;
+
+ Data_Get_Struct(self, Libevent_HttpRequest, http_request);
+ ev_headers = evhttp_request_get_output_headers(http_request->ev_request);
+ evhttp_clear_headers(ev_headers);
+
+ return Qnil;
+}
+
+/*
+ * Get request input headers
+ * @return [Hash]
+ */
+static VALUE t_get_input_headers(VALUE self) {
+ Libevent_HttpRequest *http_request;
+ struct evkeyvalq *ev_headers;
+ struct evkeyval *ev_header;
+ VALUE headers;
+
+ Data_Get_Struct(self, Libevent_HttpRequest, http_request);
+
+ headers = rb_hash_new();
+
+ ev_headers = evhttp_request_get_input_headers(http_request->ev_request);
+
+ for ( ev_header = ev_headers->tqh_first; ev_header; ev_header = ev_header->next.tqe_next ) {
+ rb_hash_aset(headers, rb_str_new2(ev_header->key), rb_str_new2(ev_header->value));
+ }
+
+ return headers;
+}
+
+/*
+ * Get the remote address of associated connection
+ * @return [String] IP address
+ */
+static VALUE t_get_remote_host(VALUE self) {
+ Libevent_HttpRequest *http_request;
+
+ Data_Get_Struct(self, Libevent_HttpRequest, http_request);
+
+ return rb_str_new2(http_request->ev_request->remote_host);
+}
+
+/*
+ * Get the remote port of associated connection
+ * @return [Fixnum] port
+ */
+static VALUE t_get_remote_port(VALUE self) {
+ Libevent_HttpRequest *http_request;
+
+ Data_Get_Struct(self, Libevent_HttpRequest, http_request);
+
+ return INT2FIX(http_request->ev_request->remote_port);
+}
+
+/*
+ * Get request HTTP version
+ * @return [String] http version
+ */
+static VALUE t_get_http_version(VALUE self) {
+ Libevent_HttpRequest *http_request;
+ char http_version[3];
+
+ Data_Get_Struct(self, Libevent_HttpRequest, http_request);
+ sprintf(http_version, "HTTP/%d.%d", http_request->ev_request->major, http_request->ev_request->minor);
+
+ return rb_str_new2(http_version);
+}
+
+/*
+ * Get request command (i.e method)
+ *
+ * @return [String] http method for known command
+ * @return [nil] if method is not supported
+ * @example
+ * GET
+ */
+static VALUE t_get_command(VALUE self) {
+ Libevent_HttpRequest *http_request;
+ VALUE command;
+
+ Data_Get_Struct(self, Libevent_HttpRequest, http_request);
+
+ switch ( evhttp_request_get_command(http_request->ev_request) ) {
+ case EVHTTP_REQ_GET : command = rb_str_new2("GET"); break;
+ case EVHTTP_REQ_POST : command = rb_str_new2("POST"); break;
+ case EVHTTP_REQ_HEAD : command = rb_str_new2("HEAD"); break;
+ case EVHTTP_REQ_PUT : command = rb_str_new2("PUT"); break;
+ case EVHTTP_REQ_DELETE : command = rb_str_new2("DELETE"); break;
+ case EVHTTP_REQ_OPTIONS : command = rb_str_new2("OPTIONS"); break;
+ case EVHTTP_REQ_TRACE : command = rb_str_new2("TRACE"); break;
+ case EVHTTP_REQ_CONNECT : command = rb_str_new2("CONNECT"); break;
+ case EVHTTP_REQ_PATCH : command = rb_str_new2("PATCH"); break;
+ default: command = Qnil; break;
+ }
+
+ return command;
+}
+
+/*
+ * Returns the host associated with the request. If a client sends an absolute
+ * URI, the host part of that is preferred. Otherwise, the input headers are
+ * searched for a Host: header. NULL is returned if no absolute URI or Host:
+ * header is provided.
+ * @return [String] host
+*/
+static VALUE t_get_host(VALUE self) {
+ Libevent_HttpRequest *http_request;
+
+ Data_Get_Struct(self, Libevent_HttpRequest, http_request);
+
+ return rb_str_new2(evhttp_request_get_host(http_request->ev_request));
+}
+
+/*
+ * Get request URI
+ * @return [String] uri string
+ */
+static VALUE t_get_uri(VALUE self) {
+ Libevent_HttpRequest *http_request;
+
+ Data_Get_Struct(self, Libevent_HttpRequest, http_request);
+
+ return rb_str_new2(evhttp_request_get_uri(http_request->ev_request));
+}
+
+/*
+ * Read body from request input buffer.
+ * @return [String] body
+ */
+static VALUE t_get_body(VALUE self) {
+ Libevent_HttpRequest *http_request;
+ struct evbuffer *ev_buffer;
+ int length;
+ VALUE body;
+
+ Data_Get_Struct(self, Libevent_HttpRequest, http_request);
+
+ ev_buffer = evhttp_request_get_input_buffer(http_request->ev_request);
+
+ length = evbuffer_get_length(ev_buffer);
+ body = rb_str_new(0, length);
+ evbuffer_copyout(ev_buffer, RSTRING_PTR(body), length);
+
+ return body;
+}
+
+/*
+ * Send error to client
+ *
+ * @param [Fixnum] code HTTP code
+ * @param [String] reason (optional) short error descriptor
+ * @return [nil]
+ */
+static VALUE t_send_error(VALUE self, VALUE code, VALUE reason) {
+ Libevent_HttpRequest *http_request;
+
+ Data_Get_Struct(self, Libevent_HttpRequest, http_request);
+
+ evhttp_send_error(http_request->ev_request, FIX2INT(code), reason == Qnil ? NULL : RSTRING_PTR(reason));
+
+ return Qnil;
+}
+
+/*
+ * Send reply to client
+ * @param [Fixnum] code HTTP code
+ * @param [Hash] headers hash of http output headers
+ * @param [Object] body object that response to each method that returns strings
+ * @return [nil]
+ */
+static VALUE t_send_reply(VALUE self, VALUE code, VALUE headers, VALUE body) {
+ Libevent_HttpRequest *http_request;
+
+ Data_Get_Struct(self, Libevent_HttpRequest, http_request);
+ Check_Type(code, T_FIXNUM);
+ Check_Type(headers, T_HASH);
+
+ t_set_output_headers(self, headers);
+
+ evhttp_send_reply_start(http_request->ev_request, FIX2INT(code), NULL);
+ rb_iterate(rb_each, body, t_send_chunk, self);
+ evhttp_send_reply_end(http_request->ev_request);
+
+ return Qnil;
+}
+
+/*
+ * #send_reply iteration method to send chunk of data to client
+ * @param [String] chunk
+ * @param [Object] self HttpRequest instance
+ * @return [nil]
+ */
+static VALUE t_send_chunk(VALUE chunk, VALUE self) {
+ Libevent_HttpRequest *http_request;
+
+ Data_Get_Struct(self, Libevent_HttpRequest, http_request);
+
+ evbuffer_add(http_request->ev_buffer, RSTRING_PTR(chunk), RSTRING_LEN(chunk));
+ evhttp_send_reply_chunk(http_request->ev_request, http_request->ev_buffer);
+
+ return Qnil;
+}
+
+/*
+ * Start reply to client
+ * @param [Fixnum] code HTTP code
+ * @param [String] reason (optional) response descriptor
+ * @return [nil]
+ */
+static VALUE t_send_reply_start(VALUE self, VALUE code, VALUE reason) {
+ Libevent_HttpRequest *http_request;
+
+ Data_Get_Struct(self, Libevent_HttpRequest, http_request);
+ Check_Type(code, T_FIXNUM);
+
+ evhttp_send_reply_start(http_request->ev_request, FIX2INT(code), reason == Qnil ? NULL : RSTRING_PTR(reason));
+
+ return Qnil;
+}
+
+/*
+ * Send chunk of data to client
+ * @param [String] chunk string
+ * @return [nil]
+ */
+static VALUE t_send_reply_chunk(VALUE self, VALUE chunk) {
+ Libevent_HttpRequest *http_request;
+
+ Data_Get_Struct(self, Libevent_HttpRequest, http_request);
+
+ evbuffer_add(http_request->ev_buffer, RSTRING_PTR(chunk), RSTRING_LEN(chunk));
+ evhttp_send_reply_chunk(http_request->ev_request, http_request->ev_buffer);
+
+ return Qnil;
+}
+
+/*
+ * Stop reply to client
+ * @return [nil]
+ */
+static VALUE t_send_reply_end(VALUE self) {
+ Libevent_HttpRequest *http_request;
+
+ Data_Get_Struct(self, Libevent_HttpRequest, http_request);
+ evhttp_send_reply_end(http_request->ev_request);
+
+ return Qnil;
+}
+
+/*
+ * Get request URI scheme
+ * @return [String] http or https
+ * @return [nil]
+ */
+static VALUE t_get_uri_scheme(VALUE self) {
+ Libevent_HttpRequest *http_request;
+ const struct evhttp_uri *ev_uri;
+ const char* scheme;
+
+ Data_Get_Struct(self, Libevent_HttpRequest, http_request);
+
+ ev_uri = evhttp_request_get_evhttp_uri(http_request->ev_request);
+ scheme = evhttp_uri_get_scheme(ev_uri);
+
+ return(scheme ? rb_str_new2(scheme) : Qnil);
+}
+
+/*
+ * Get request URI path
+ * @return [String]
+ * @return [nil]
+ */
+static VALUE t_get_uri_path(VALUE self) {
+ Libevent_HttpRequest *http_request;
+ const char *path;
+
+ Data_Get_Struct(self, Libevent_HttpRequest, http_request);
+
+ path = evhttp_uri_get_path(http_request->ev_request->uri_elems);
+
+ return(path ? rb_str_new2(path) : Qnil);
+}
+
+/*
+ * Get request URI query
+ * @return [String]
+ * @return [nil]
+ */
+static VALUE t_get_uri_query(VALUE self) {
+ Libevent_HttpRequest *http_request;
+ const char *query;
+
+ Data_Get_Struct(self, Libevent_HttpRequest, http_request);
+
+ query = evhttp_uri_get_query(http_request->ev_request->uri_elems);
+
+ return(query ? rb_str_new2(query) : Qnil);
+}
14 ext/libevent_ext/ruby18_compat.h
@@ -0,0 +1,14 @@
+#ifndef RUBY_19
+#ifndef STRING_PTR
+#define STRING_PTR(v) (RSTRING(v)->ptr)
+#endif
+#ifndef RFLOAT_VALUE
+#define RFLOAT_VALUE(v) (RFLOAT(v)->value)
+#endif
+#ifndef RARRAY_LEN
+#define RARRAY_LEN(v) (RARRAY(v)->len)
+#endif
+#ifndef RARRAY_PTR
+#define RARRAY_PTR(v) (RARRAY(v)->ptr)
+#endif
+#endif
107 ext/libevent_ext/signal.c
@@ -0,0 +1,107 @@
+#include "ext.h"
+
+static VALUE t_allocate(VALUE klass);
+
+static void t_free(Libevent_Signal *signal);
+
+static VALUE t_initialize(VALUE self, VALUE base, VALUE name, VALUE handler);
+
+static VALUE t_destroy(VALUE self);
+
+static void t_handler(evutil_socket_t signal_number, short events, void *context);
+
+void Init_libevent_signal() {
+ cLibevent_Signal = rb_define_class_under(mLibevent, "Signal", rb_cObject);
+
+ rb_define_alloc_func(cLibevent_Signal, t_allocate);
+
+ rb_define_method(cLibevent_Signal, "initialize", t_initialize, 3);
+ rb_define_method(cLibevent_Signal, "destroy", t_destroy, 0);
+}
+
+/*
+ * Allocate memory
+ */
+static VALUE t_allocate(VALUE klass) {
+ Libevent_Signal *signal;
+
+ signal = ALLOC(Libevent_Signal);
+ return Data_Wrap_Struct(klass, 0, t_free, signal);
+}
+
+/*
+ * Free memory
+ */
+static void t_free(Libevent_Signal *signal) {
+ if ( signal->ev_event ) {
+ event_free(signal->ev_event);
+ }
+
+ xfree(signal);
+}
+
+/*
+ * Create and add signal to specified event base with handler block
+ *
+ * @note method allocates memory for <b>struct event </b>
+ * that will be freed when object will be freed by ruby' GC
+ *
+ * @param [Base] base event base instance
+ * @param [String] name a name of signal
+ * @param [Object] handler object that perform signal handling. Any object that responds to :call method
+ *
+ */
+static VALUE t_initialize(VALUE self, VALUE base, VALUE name, VALUE handler) {
+ Libevent_Signal *le_signal;
+ Libevent_Base *le_base;
+ VALUE signal_list;
+ VALUE signal_number;
+
+ Data_Get_Struct(self, Libevent_Signal, le_signal);
+ Data_Get_Struct(base, Libevent_Base, le_base);
+
+ // check name
+ signal_list = rb_funcall( rb_const_get(rb_cObject, rb_intern("Signal")), rb_intern("list"), 0);
+ signal_number = rb_hash_aref(signal_list, name);
+ if ( signal_number == Qnil )
+ rb_raise(rb_eArgError, "unknown signal name given");
+ rb_iv_set(self, "@name", name);
+
+ // check handler
+ if ( !rb_respond_to(handler, rb_intern("call")))
+ rb_raise(rb_eArgError, "handler does not response to call method");
+ rb_iv_set(self, "@handler", handler);
+
+ // create signal event
+ le_signal->ev_event = evsignal_new(le_base->ev_base, FIX2INT(signal_number), t_handler, (void *)handler);
+ if ( !le_signal->ev_event )
+ rb_fatal("Could not create a signal event");
+ if ( event_add(le_signal->ev_event, NULL) < 0 )
+ rb_fatal("Could not add a signal event");
+
+ return self;
+}
+
+/*
+ * Delete signal from event base
+ * @return [true] on success
+ * @return [false] on failure
+ */
+static VALUE t_destroy(VALUE self) {
+ Libevent_Signal *le_signal;
+ int status;
+
+ Data_Get_Struct(self, Libevent_Signal, le_signal);
+ status = event_del(le_signal->ev_event);
+
+ return( status == -1 ? Qfalse : Qtrue);
+}
+
+/*
+ * C callback function that invokes call method on Ruby object.
+ */
+static void t_handler(evutil_socket_t signal_number, short events, void *context) {
+ VALUE handler = (VALUE)context;
+
+ rb_funcall(handler, rb_intern("call"), 0);
+}
9 lib/libevent.rb
@@ -0,0 +1,9 @@
+require "libevent/version"
+
+require "libevent_ext"
+
+require "libevent/base"
+require "libevent/signal"
+require "libevent/http"
+require "libevent/http_request"
+require "libevent/builder"
18 lib/libevent/base.rb
@@ -0,0 +1,18 @@
+module Libevent
+ class Base
+ # Create new event base
+ def initialize
+ @signals = []
+ end
+
+ attr_reader :signals
+
+ # Create new signal with handler as block and add signal to event base
+ #
+ # @param [String] name of signal
+ def trap_signal(name, &block)
+ @signals << Signal.new(self, name, block)
+ end
+
+ end
+end
32 lib/libevent/builder.rb
@@ -0,0 +1,32 @@
+module Libevent
+ class Builder
+ def initialize(options = {}, &block)
+ @base = Base.new
+ instance_eval(&block) if block_given?
+ end
+
+ attr_reader :base
+
+ # Create new Http instance, bind socket and options yield http object
+ # @param [String] host
+ # @param [Fixnum] port
+ # @return [Http] instance
+ def server(host, port, &block)
+ http = Http.new(@base)
+ http.bind_socket(host, port) or raise RuntimeError, "can't bind socket #{host}:#{port}"
+ yield(http) if block_given?
+ http
+ end
+
+ # Trap signal using event base
+ # @param [String] name a signal name
+ def signal(name, &block)
+ base.trap_signal(name, &block)
+ end
+
+ # Start event base loop
+ def dispatch
+ base.dispatch
+ end
+ end
+end
23 lib/libevent/http.rb
@@ -0,0 +1,23 @@
+module Libevent
+ class Http
+
+ attr_reader :base
+
+ # Create virtual http server
+ # @param [String] domain a domain for virtual server
+ # @return [Http] http instance
+ def vhost(domain)
+ http = self.class.new(self.base)
+ add_virtual_host(domain, http)
+ yield(http) if block_given?
+ http
+ end
+
+ # Set request handler for current http instance
+ # @param block
+ def handler(&block)
+ set_request_handler(block)
+ end
+
+ end
+end
4 lib/libevent/http_request.rb
@@ -0,0 +1,4 @@
+module Libevent
+ class HttpRequest
+ end
+end
6 lib/libevent/signal.rb
@@ -0,0 +1,6 @@
+module Libevent
+ class Signal
+ attr_reader :name
+ end
+end
+
3 lib/libevent/version.rb
@@ -0,0 +1,3 @@
+module Libevent
+ VERSION = "0.0.1"
+end
94 lib/rack/handler/libevent.rb
@@ -0,0 +1,94 @@
+require "libevent"
+require "stringio"
+
+module Rack
+ module Handler
+ class Libevent
+
+ def self.run(app, options)
+ server = new(app, options)
+ server.start
+ end
+
+ def self.valid_options
+ {
+ "timeout=TIMEOUT" => "Set the timeout for an HTTP request"
+ }
+ end
+
+ def initialize(app, options)
+ @app = app
+
+ @host = options[:Host] or raise ArgumentError, "Host option required"
+ @port = options[:Port] or raise ArgumentError, "Port option required"
+
+ @base = ::Libevent::Base.new
+ @http = ::Libevent::Http.new(@base)
+
+ @http.set_timeout(options[:timeout].to_i) if options[:timeout]
+ end
+
+ def start
+ @http.bind_socket(@host, @port) or raise RuntimeError, "Can't bind to #{@host}:#{@port}"
+ @http.set_request_handler(self.method(:process))
+
+ @base.trap_signal("INT") { self.stop }
+ @base.trap_signal("TERM") { self.stop }
+
+ @base.dispatch
+ end
+
+ def stop
+ @base.exit_loop
+ end
+
+ protected
+
+ def process(request)
+ env = {}
+
+ env['REQUEST_METHOD'] = request.get_command
+ env['SCRIPT_NAME'] = ''
+ env['REQUEST_PATH'] = '/'
+ env['PATH_INFO'] = request.get_uri_path || '/'
+ env['QUERY_STRING'] = request.get_uri_query || ''
+ env['SERVER_NAME'] = request.get_host || @host
+ env['SERVER_PORT'] = @port.to_s
+ env['SERVER_SOFTWARE'] = "libevent/#{::Libevent::VERSION}"
+ env['SERVER_PROTOCOL'] = 'HTTP/1.1'
+ env['REMOTE_ADDR'] = request.get_remote_host
+ env['HTTP_VERSION'] = request.get_http_version
+ env['rack.version'] = [1, 1]
+ env['rack.url_scheme'] = request.get_uri_scheme || 'http'
+ env['rack.input'] = StringIO.new(request.get_body)
+ env['rack.errors'] = STDERR
+ env['rack.multithread'] = false
+ env['rack.multiprocess'] = false
+ env['rack.run_once'] = false
+
+ request.get_input_headers.each do |key, val|
+ env_key = ""
+ env_key << "HTTP_" unless key =~ /^content(_|-)(type|length)$/i
+ env_key << key
+ env_key.gsub!('-','_')
+ env_key.upcase!
+ env[env_key] = val
+ end
+
+ code, headers, body = @app.call(env)
+
+ begin
+ headers.each do |key, values|
+ values.split("\n").each { |value| request.add_output_header(key, value) }
+ end
+ request.send_reply_start(code, nil)
+ body.each { |chunk| request.send_reply_chunk(chunk) }
+ request.send_reply_end
+ ensure
+ body.close if body.respond_to?(:close)
+ end
+ end
+ end
+
+ end
+end
22 libevent.gemspec
@@ -0,0 +1,22 @@
+# -*- encoding: utf-8 -*-
+$:.push File.expand_path("../lib", __FILE__)
+require "libevent/version"
+
+Gem::Specification.new do |s|
+ s.name = "libevent"
+ s.version = Libevent::VERSION
+ s.authors = ["Andriy Yanko"]
+ s.email = ["andriy.yanko@gmail.com"]
+ s.homepage = "https://github.com/ayanko/libevent"
+ s.summary = %q{C extension for libevent}
+ s.description = %q{C extension for libevent}
+
+ s.rubyforge_project = "libevent"
+
+ s.files = `git ls-files`.split("\n")
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
+ s.require_paths = ["lib"]
+
+ s.extensions = ["ext/libevent_ext/extconf.rb"]
+end
28 samples/rack_request_inspect
@@ -0,0 +1,28 @@
+#!/usr/bin/env ruby
+
+$:.unshift File.expand_path('../../lib', __FILE__)
+
+require "libevent"
+require "rack/handler/libevent"
+
+app = lambda { |env|
+ body = []
+ body << "RACK_ENVIRONMENT:\n"
+ env.sort.each { |pair| body << "%-20s : %s\n" % pair }
+ body << "RACK_INPUT_START"
+ body << env['rack.input'].read
+ body << "RACK_INPUT_END\n"
+
+ headers = {}
+ headers['Content-Type'] = 'text/plain'
+ headers['Content-Length'] = body.inject(0) { |sum, chunk| sum += chunk.size }.to_s
+ headers['Set-Cookie'] = "one=first\nsecond=two"
+
+ [ 200, headers, body ]
+}
+
+Rack::Handler::Libevent.run(app, {
+ :Host => "0.0.0.0",
+ :Port => 3000,
+ :timeout => 60,
+})
63 samples/virtual_hosts
@@ -0,0 +1,63 @@
+#!/usr/bin/env ruby
+
+$:.unshift File.expand_path('../../lib', __FILE__)
+
+require "libevent"
+
+# curl -v -H 'Host: blog.local' http://localhost:3001/hello
+# curl -v -H 'Host: blog.local' http://localhost:3001/api
+# curl -v -H 'Host: wiki.local' http://localhost:3001
+# curl -v -H 'Host: any.local' http://localhost:3001
+
+Libevent::Builder.new do
+
+ server "0.0.0.0", 3000 do |http|
+
+ http.handler do |request|
+ case request.get_uri_path
+ when '/hello'
+ request.send_reply 200, { 'Content->Type' => 'text/plain'}, [ "Hello World" ]
+ when '/api'
+ request.send_reply 200, { 'Content->Type' => 'application/json'}, [ "{\"version\":\"1.0\"}" ]
+ else
+ request.send_error 404, "Nothing Found"
+ end
+ end
+
+ http.vhost "blog.local" do |host|
+ host.handler do |request|
+ request.send_reply 200, {}, ["It's blog"]
+ end
+ end
+
+ http.vhost "wiki.local" do |host|
+ host.handler do |request|
+ request.send_reply 200, {}, ["It's wiki"]
+ end
+ end
+
+ http.vhost "*.local" do |host|
+ host.handler do |request|
+ request.send_error 404, "Please use blog.local or wiki.local"
+ end
+ end
+
+ end
+
+ server "0.0.0.0", 3001 do |http|
+ http.handler do |request|
+ request.send_reply 200, { 'Content->Type' => 'text/plain'}, [ "Hello World 4000" ]
+ end
+ end
+
+ signal("INT") do
+ base.exit_loop
+ end
+
+ signal("HUP") do
+ Kernel.puts "HUP received ..."
+ end
+
+ dispatch
+
+end

0 comments on commit 14f5d8e

Please sign in to comment.
Something went wrong with that request. Please try again.