libevent 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/Gemfile +5 -0
- data/README.md +146 -0
- data/Rakefile +4 -0
- data/ext/libevent_ext/base.c +94 -0
- data/ext/libevent_ext/ext.c +10 -0
- data/ext/libevent_ext/ext.h +40 -0
- data/ext/libevent_ext/extconf.rb +7 -0
- data/ext/libevent_ext/http.c +184 -0
- data/ext/libevent_ext/http_request.c +466 -0
- data/ext/libevent_ext/ruby18_compat.h +14 -0
- data/ext/libevent_ext/signal.c +107 -0
- data/lib/libevent.rb +9 -0
- data/lib/libevent/base.rb +18 -0
- data/lib/libevent/builder.rb +32 -0
- data/lib/libevent/http.rb +23 -0
- data/lib/libevent/http_request.rb +4 -0
- data/lib/libevent/signal.rb +6 -0
- data/lib/libevent/version.rb +3 -0
- data/lib/rack/handler/libevent.rb +97 -0
- data/libevent.gemspec +22 -0
- data/samples/rack_request_inspect +28 -0
- data/samples/simple.rb +22 -0
- data/samples/virtual_hosts +63 -0
- metadata +70 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
## Libevent
|
2
|
+
|
3
|
+
C extension to [libevent](http://libevent.org/) library.
|
4
|
+
|
5
|
+
## Description
|
6
|
+
|
7
|
+
The nice feature of libevent is it already contains build in HTTP server (evhttp).
|
8
|
+
|
9
|
+
Currently libevent extension implements mostly http server.
|
10
|
+
|
11
|
+
## Dependencies
|
12
|
+
|
13
|
+
* libevent v2
|
14
|
+
|
15
|
+
## Documentation
|
16
|
+
|
17
|
+
Please read [libevent rubydoc](http://rubydoc.info/github/ayanko/libevent/frames)
|
18
|
+
|
19
|
+
## Installation
|
20
|
+
|
21
|
+
gem install libevent
|
22
|
+
|
23
|
+
## Using Libevent HTTP server
|
24
|
+
|
25
|
+
Check `samples` directory
|
26
|
+
|
27
|
+
### From scratch
|
28
|
+
|
29
|
+
Simple server
|
30
|
+
|
31
|
+
require "libevent"
|
32
|
+
|
33
|
+
# create event base
|
34
|
+
base = Libevent::Base.new
|
35
|
+
|
36
|
+
# create http server instance
|
37
|
+
http = Libevent::Http.new(base)
|
38
|
+
|
39
|
+
# bind socket
|
40
|
+
http.bind_socket("0.0.0.0", 15015)
|
41
|
+
|
42
|
+
# set handler
|
43
|
+
http.handler do |request|
|
44
|
+
request.send_reply(200, {}, ["Hello World\n"])
|
45
|
+
end
|
46
|
+
|
47
|
+
# catch SIGINT
|
48
|
+
base.trap_signal("INT") { base.exit_loop }
|
49
|
+
|
50
|
+
# start libevent loop
|
51
|
+
base.dispatch
|
52
|
+
|
53
|
+
Check with curl
|
54
|
+
|
55
|
+
$ curl -v http://localhost:15015
|
56
|
+
> GET / HTTP/1.1
|
57
|
+
> User-Agent: curl/7.22.0 (x86_64-unknown-linux-gnu) libcurl/7.22.0 OpenSSL/1.0.0e zlib/1.2.5 libssh2/1.3.0
|
58
|
+
> Host: localhost:15015
|
59
|
+
> Accept: */*
|
60
|
+
>
|
61
|
+
< HTTP/1.1 200 OK
|
62
|
+
< Transfer-Encoding: chunked
|
63
|
+
< Date: Fri, 18 Nov 2011 19:09:04 GMT
|
64
|
+
< Content-Type: text/html; charset=ISO-8859-1
|
65
|
+
<
|
66
|
+
Hello World
|
67
|
+
|
68
|
+
### Server with virtual hosts
|
69
|
+
|
70
|
+
require "libevent"
|
71
|
+
|
72
|
+
Libevent::Builder.new do
|
73
|
+
|
74
|
+
server "0.0.0.0", 3000 do |http|
|
75
|
+
|
76
|
+
http.handler do |request|
|
77
|
+
case request.get_uri_path
|
78
|
+
when '/hello'
|
79
|
+
request.send_reply 200, { 'Content->Type' => 'text/plain'}, [ "Hello World" ]
|
80
|
+
when '/api'
|
81
|
+
request.send_reply 200, { 'Content->Type' => 'application/json'}, [ "{\"version\":\"1.0\"}" ]
|
82
|
+
else
|
83
|
+
request.send_error 404, "Nothing Found"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
http.vhost "blog.local" do |host|
|
88
|
+
host.handler do |request|
|
89
|
+
request.send_reply 200, {}, ["It's blog"]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
http.vhost "wiki.local" do |host|
|
94
|
+
host.handler do |request|
|
95
|
+
request.send_reply 200, {}, ["It's wiki"]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
http.vhost "*.local" do |host|
|
100
|
+
host.handler do |request|
|
101
|
+
request.send_error 404, "Please use blog.local or wiki.local"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
server "0.0.0.0", 3001 do |http|
|
108
|
+
http.handler do |request|
|
109
|
+
request.send_reply 200, { 'Content->Type' => 'text/plain'}, [ "Hello World 3001" ]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
signal("INT") do
|
114
|
+
base.exit_loop
|
115
|
+
end
|
116
|
+
|
117
|
+
signal("HUP") do
|
118
|
+
Kernel.puts "HUP received ..."
|
119
|
+
end
|
120
|
+
|
121
|
+
dispatch
|
122
|
+
|
123
|
+
end
|
124
|
+
|
125
|
+
### Serve Rails application
|
126
|
+
|
127
|
+
Add to `Gemfile`
|
128
|
+
|
129
|
+
gem "libevent", :require => false
|
130
|
+
|
131
|
+
Update gems
|
132
|
+
|
133
|
+
$ bundle install
|
134
|
+
|
135
|
+
Run application
|
136
|
+
|
137
|
+
$ script/rails s Libevent
|
138
|
+
|
139
|
+
Or via rackup
|
140
|
+
|
141
|
+
$ bundle exec rackup -s Libevent -p 3000
|
142
|
+
|
143
|
+
### Serve Rack application
|
144
|
+
|
145
|
+
Check rack handler `rack/handler/libevent.rb`
|
146
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
#include "ext.h"
|
2
|
+
|
3
|
+
static VALUE t_allocate(VALUE klass);
|
4
|
+
|
5
|
+
static void t_free(Libevent_Base *base);
|
6
|
+
|
7
|
+
static VALUE t_dispatch(VALUE self);
|
8
|
+
|
9
|
+
static VALUE t_exit_loop(VALUE self);
|
10
|
+
|
11
|
+
static VALUE t_break_loop(VALUE self);
|
12
|
+
|
13
|
+
void Init_libevent_base() {
|
14
|
+
cLibevent_Base = rb_define_class_under(mLibevent, "Base", rb_cObject);
|
15
|
+
|
16
|
+
rb_define_alloc_func(cLibevent_Base, t_allocate);
|
17
|
+
|
18
|
+
rb_define_method(cLibevent_Base, "dispatch", t_dispatch, 0);
|
19
|
+
rb_define_method(cLibevent_Base, "exit_loop", t_exit_loop, 0);
|
20
|
+
rb_define_method(cLibevent_Base, "break_loop", t_break_loop, 0);
|
21
|
+
}
|
22
|
+
|
23
|
+
/*
|
24
|
+
* Allocate memmory
|
25
|
+
*/
|
26
|
+
static VALUE t_allocate(VALUE klass) {
|
27
|
+
Libevent_Base *base;
|
28
|
+
|
29
|
+
base = ALLOC(Libevent_Base);
|
30
|
+
base->ev_base = event_base_new();
|
31
|
+
|
32
|
+
if ( !base->ev_base ) {
|
33
|
+
rb_fatal("Couldn't get an event base");
|
34
|
+
}
|
35
|
+
|
36
|
+
return Data_Wrap_Struct(klass, 0, t_free, base);
|
37
|
+
}
|
38
|
+
|
39
|
+
/*
|
40
|
+
* Free memmory
|
41
|
+
*/
|
42
|
+
static void t_free(Libevent_Base *base) {
|
43
|
+
event_base_free(base->ev_base);
|
44
|
+
}
|
45
|
+
|
46
|
+
/*
|
47
|
+
* Event dispatching loop.
|
48
|
+
*
|
49
|
+
* This loop will run the event base until either there are no more added events,
|
50
|
+
* or until something calls Libevent::Base#break_loop or Base#exit_loop.
|
51
|
+
* @see #break_loop
|
52
|
+
* @see #exit_loop
|
53
|
+
*/
|
54
|
+
static VALUE t_dispatch(VALUE self) {
|
55
|
+
Libevent_Base *base;
|
56
|
+
int status;
|
57
|
+
|
58
|
+
Data_Get_Struct(self, Libevent_Base, base);
|
59
|
+
status = event_base_dispatch(base->ev_base);
|
60
|
+
|
61
|
+
return INT2FIX(status);
|
62
|
+
}
|
63
|
+
|
64
|
+
/*
|
65
|
+
* Exit the event loop after the specified time
|
66
|
+
*
|
67
|
+
* @todo specified time is not implemented
|
68
|
+
*/
|
69
|
+
static VALUE t_exit_loop(VALUE self) {
|
70
|
+
Libevent_Base *base;
|
71
|
+
int status;
|
72
|
+
|
73
|
+
Data_Get_Struct(self, Libevent_Base, base);
|
74
|
+
status = event_base_loopexit(base->ev_base, NULL);
|
75
|
+
|
76
|
+
return (status == -1 ? Qfalse : Qtrue);
|
77
|
+
}
|
78
|
+
/*
|
79
|
+
* Abort the active event_base loop immediately.
|
80
|
+
*
|
81
|
+
* It will abort the loop after the next event is completed;
|
82
|
+
* event_base_loopbreak() is typically invoked from this event's callback.
|
83
|
+
* This behavior is analogous to the "break;" statement.
|
84
|
+
* Subsequent invocations of event_loop() will proceed normally.
|
85
|
+
*/
|
86
|
+
static VALUE t_break_loop(VALUE self) {
|
87
|
+
Libevent_Base *base;
|
88
|
+
int status;
|
89
|
+
|
90
|
+
Data_Get_Struct(self, Libevent_Base, base);
|
91
|
+
status = event_base_loopbreak(base->ev_base);
|
92
|
+
|
93
|
+
return (status == -1 ? Qfalse : Qtrue);
|
94
|
+
}
|
@@ -0,0 +1,40 @@
|
|
1
|
+
#ifndef LIBEVENT_EXT_H
|
2
|
+
#define LIBEVENT_EXT_H
|
3
|
+
|
4
|
+
#include "ruby.h"
|
5
|
+
#include "ruby18_compat.h"
|
6
|
+
|
7
|
+
#include <event.h>
|
8
|
+
#include <evhttp.h>
|
9
|
+
|
10
|
+
VALUE mLibevent;
|
11
|
+
VALUE cLibevent_Base;
|
12
|
+
VALUE cLibevent_Signal;
|
13
|
+
VALUE cLibevent_Http;
|
14
|
+
VALUE cLibevent_HttpRequest;
|
15
|
+
|
16
|
+
typedef struct Libevent_Base {
|
17
|
+
struct event_base *ev_base;
|
18
|
+
} Libevent_Base;
|
19
|
+
|
20
|
+
typedef struct Libevent_Signal {
|
21
|
+
struct event *ev_event;
|
22
|
+
} Libevent_Signal;
|
23
|
+
|
24
|
+
typedef struct Libevent_Http {
|
25
|
+
struct event_base *ev_base;
|
26
|
+
struct evhttp *ev_http;
|
27
|
+
struct evhttp *ev_http_parent;
|
28
|
+
} Libevent_Http;
|
29
|
+
|
30
|
+
typedef struct Libevent_HttpRequest {
|
31
|
+
struct evhttp_request *ev_request;
|
32
|
+
struct evbuffer *ev_buffer;
|
33
|
+
} Libevent_HttpRequest;
|
34
|
+
|
35
|
+
void Init_libevent_base();
|
36
|
+
void Init_libevent_signal();
|
37
|
+
void Init_libevent_http();
|
38
|
+
void Init_libevent_http_request();
|
39
|
+
|
40
|
+
#endif
|
@@ -0,0 +1,184 @@
|
|
1
|
+
#include "ext.h"
|
2
|
+
|
3
|
+
static VALUE t_allocate(VALUE klass);
|
4
|
+
|
5
|
+
static void t_free(Libevent_Http *http);
|
6
|
+
|
7
|
+
static VALUE t_initialize(VALUE self, VALUE object);
|
8
|
+
|
9
|
+
static VALUE t_bind_socket(VALUE self, VALUE address, VALUE port);
|
10
|
+
|
11
|
+
static VALUE t_set_request_handler(VALUE self, VALUE handler);
|
12
|
+
|
13
|
+
static VALUE t_set_timeout(VALUE self, VALUE timeout);
|
14
|
+
|
15
|
+
static void t_request_handler(struct evhttp_request *ev_request, void *context);
|
16
|
+
|
17
|
+
static VALUE t_add_virtual_host(VALUE self, VALUE domain, VALUE vhttp);
|
18
|
+
|
19
|
+
void Init_libevent_http() {
|
20
|
+
cLibevent_Http = rb_define_class_under(mLibevent, "Http", rb_cObject);
|
21
|
+
|
22
|
+
rb_define_alloc_func(cLibevent_Http, t_allocate);
|
23
|
+
|
24
|
+
rb_define_method(cLibevent_Http, "initialize", t_initialize, 1);
|
25
|
+
rb_define_method(cLibevent_Http, "bind_socket", t_bind_socket, 2);
|
26
|
+
rb_define_method(cLibevent_Http, "set_request_handler", t_set_request_handler, 1);
|
27
|
+
rb_define_method(cLibevent_Http, "set_timeout", t_set_timeout, 1);
|
28
|
+
rb_define_method(cLibevent_Http, "add_virtual_host", t_add_virtual_host, 2);
|
29
|
+
}
|
30
|
+
|
31
|
+
/*
|
32
|
+
* Allocate memory
|
33
|
+
*/
|
34
|
+
static VALUE t_allocate(VALUE klass) {
|
35
|
+
Libevent_Http *http = ALLOC(Libevent_Http);
|
36
|
+
|
37
|
+
http->ev_base = NULL;
|
38
|
+
http->ev_http = NULL;
|
39
|
+
http->ev_http_parent = NULL;
|
40
|
+
|
41
|
+
return Data_Wrap_Struct(klass, 0, t_free, http);
|
42
|
+
}
|
43
|
+
|
44
|
+
/*
|
45
|
+
* Free memory
|
46
|
+
*/
|
47
|
+
static void t_free(Libevent_Http *http) {
|
48
|
+
if ( http->ev_http ) {
|
49
|
+
// main http frees all associated vhosts
|
50
|
+
if ( http->ev_http_parent == NULL )
|
51
|
+
evhttp_free(http->ev_http);
|
52
|
+
}
|
53
|
+
|
54
|
+
xfree(http);
|
55
|
+
}
|
56
|
+
|
57
|
+
/*
|
58
|
+
* Initialize http instance and allocate evhttp structure
|
59
|
+
*
|
60
|
+
* @param [Base] object
|
61
|
+
* @yield [self]
|
62
|
+
*/
|
63
|
+
static VALUE t_initialize(VALUE self, VALUE object) {
|
64
|
+
Libevent_Http *http;
|
65
|
+
Libevent_Base *base;
|
66
|
+
|
67
|
+
Data_Get_Struct(self, Libevent_Http, http);
|
68
|
+
Data_Get_Struct(object, Libevent_Base, base);
|
69
|
+
|
70
|
+
http->ev_base = base->ev_base;
|
71
|
+
http->ev_http = evhttp_new(http->ev_base);
|
72
|
+
|
73
|
+
if (!http->ev_http) {
|
74
|
+
rb_fatal("Couldn't create evhttp");
|
75
|
+
}
|
76
|
+
|
77
|
+
rb_iv_set(self, "@base", object);
|
78
|
+
|
79
|
+
if (rb_block_given_p())
|
80
|
+
rb_yield(self);
|
81
|
+
|
82
|
+
return self;
|
83
|
+
}
|
84
|
+
|
85
|
+
/*
|
86
|
+
* Binds an HTTP instance on the specified address and port.
|
87
|
+
* Can be called multiple times to bind the same http server to multiple different ports.
|
88
|
+
* @param [String] address IP address
|
89
|
+
* @param [Fixnum] port port to bind
|
90
|
+
* @return [true] on success
|
91
|
+
* @return [false] on failure
|
92
|
+
*/
|
93
|
+
static VALUE t_bind_socket(VALUE self, VALUE address, VALUE port) {
|
94
|
+
Libevent_Http *http;
|
95
|
+
int status;
|
96
|
+
|
97
|
+
Data_Get_Struct(self, Libevent_Http, http);
|
98
|
+
Check_Type(address, T_STRING);
|
99
|
+
Check_Type(port, T_FIXNUM);
|
100
|
+
status = evhttp_bind_socket(http->ev_http, RSTRING_PTR(address), FIX2INT(port));
|
101
|
+
|
102
|
+
return ( status == -1 ? Qfalse : Qtrue );
|
103
|
+
}
|
104
|
+
|
105
|
+
/*
|
106
|
+
* Set a callback for all requests that are not caught by specific callbacks.
|
107
|
+
* @note
|
108
|
+
* handler should response to :call method.
|
109
|
+
*
|
110
|
+
* Libevent::HttpRequest instance will be passed to handler as first argument
|
111
|
+
*
|
112
|
+
* @param [Object] handler object that response to :call
|
113
|
+
* @return [nil]
|
114
|
+
*/
|
115
|
+
static VALUE t_set_request_handler(VALUE self, VALUE handler) {
|
116
|
+
Libevent_Http *http;
|
117
|
+
|
118
|
+
Data_Get_Struct(self, Libevent_Http, http);
|
119
|
+
|
120
|
+
if ( !rb_respond_to(handler, rb_intern("call")))
|
121
|
+
rb_raise(rb_eArgError, "handler does not response to call method");
|
122
|
+
|
123
|
+
rb_iv_set(self, "@request_handler", handler);
|
124
|
+
evhttp_set_gencb(http->ev_http, t_request_handler, (void *)handler);
|
125
|
+
|
126
|
+
return Qnil;
|
127
|
+
}
|
128
|
+
|
129
|
+
/*
|
130
|
+
* C callback function that create HttpRequest instance and call Ruby handler object with it.
|
131
|
+
*/
|
132
|
+
static void t_request_handler(struct evhttp_request *ev_request, void* context) {
|
133
|
+
Libevent_HttpRequest *le_http_request;
|
134
|
+
VALUE http_request;
|
135
|
+
VALUE handler = (VALUE)context;
|
136
|
+
|
137
|
+
http_request = rb_obj_alloc(cLibevent_HttpRequest);
|
138
|
+
Data_Get_Struct(http_request, Libevent_HttpRequest, le_http_request);
|
139
|
+
le_http_request->ev_request = ev_request;
|
140
|
+
rb_obj_call_init(http_request, 0, 0);
|
141
|
+
|
142
|
+
rb_funcall(handler, rb_intern("call"), 1, http_request);
|
143
|
+
}
|
144
|
+
|
145
|
+
/*
|
146
|
+
* Set the timeout for an HTTP request.
|
147
|
+
* @param [Fixnum] timeout he timeout, in seconds
|
148
|
+
* return nil
|
149
|
+
*/
|
150
|
+
static VALUE t_set_timeout(VALUE self, VALUE timeout) {
|
151
|
+
Libevent_Http *http;
|
152
|
+
|
153
|
+
Data_Get_Struct(self, Libevent_Http, http);
|
154
|
+
evhttp_set_timeout(http->ev_http, NUM2INT(timeout));
|
155
|
+
|
156
|
+
return Qnil;
|
157
|
+
}
|
158
|
+
|
159
|
+
/*
|
160
|
+
* Adds a virtual host to the http server.
|
161
|
+
* It is possible to have hierarchical vhosts.
|
162
|
+
* @param [String] domain the glob pattern against which the hostname is matched. The match is case insensitive and follows otherwise regular shell matching.
|
163
|
+
* @param [Http] vhttp the virtual host to add the regular http server
|
164
|
+
* @return [true false]
|
165
|
+
* @example
|
166
|
+
* A vhost with the pattern *.example.com may have other vhosts with patterns foo.example.com and bar.example.com associated with it.
|
167
|
+
* @note
|
168
|
+
* A virtual host is a newly initialized evhttp object that has request handler
|
169
|
+
* It most not have any listing sockets associated with it.
|
170
|
+
*/
|
171
|
+
static VALUE t_add_virtual_host(VALUE self, VALUE domain, VALUE vhttp) {
|
172
|
+
Libevent_Http *le_http;
|
173
|
+
Libevent_Http *le_vhttp;
|
174
|
+
int status;
|
175
|
+
|
176
|
+
Data_Get_Struct(self, Libevent_Http, le_http);
|
177
|
+
Data_Get_Struct(vhttp, Libevent_Http, le_vhttp);
|
178
|
+
Check_Type(domain, T_STRING);
|
179
|
+
le_vhttp->ev_http_parent = le_http->ev_http;
|
180
|
+
status = evhttp_add_virtual_host(le_http->ev_http, RSTRING_PTR(domain), le_vhttp->ev_http);
|
181
|
+
|
182
|
+
return ( status == -1 ? Qfalse : Qtrue );
|
183
|
+
}
|
184
|
+
|