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
@@ -0,0 +1,14 @@
|
|
1
|
+
#ifndef RUBY_19
|
2
|
+
#ifndef STRING_PTR
|
3
|
+
#define STRING_PTR(v) (RSTRING(v)->ptr)
|
4
|
+
#endif
|
5
|
+
#ifndef RFLOAT_VALUE
|
6
|
+
#define RFLOAT_VALUE(v) (RFLOAT(v)->value)
|
7
|
+
#endif
|
8
|
+
#ifndef RARRAY_LEN
|
9
|
+
#define RARRAY_LEN(v) (RARRAY(v)->len)
|
10
|
+
#endif
|
11
|
+
#ifndef RARRAY_PTR
|
12
|
+
#define RARRAY_PTR(v) (RARRAY(v)->ptr)
|
13
|
+
#endif
|
14
|
+
#endif
|
@@ -0,0 +1,107 @@
|
|
1
|
+
#include "ext.h"
|
2
|
+
|
3
|
+
static VALUE t_allocate(VALUE klass);
|
4
|
+
|
5
|
+
static void t_free(Libevent_Signal *signal);
|
6
|
+
|
7
|
+
static VALUE t_initialize(VALUE self, VALUE base, VALUE name, VALUE handler);
|
8
|
+
|
9
|
+
static VALUE t_destroy(VALUE self);
|
10
|
+
|
11
|
+
static void t_handler(evutil_socket_t signal_number, short events, void *context);
|
12
|
+
|
13
|
+
void Init_libevent_signal() {
|
14
|
+
cLibevent_Signal = rb_define_class_under(mLibevent, "Signal", rb_cObject);
|
15
|
+
|
16
|
+
rb_define_alloc_func(cLibevent_Signal, t_allocate);
|
17
|
+
|
18
|
+
rb_define_method(cLibevent_Signal, "initialize", t_initialize, 3);
|
19
|
+
rb_define_method(cLibevent_Signal, "destroy", t_destroy, 0);
|
20
|
+
}
|
21
|
+
|
22
|
+
/*
|
23
|
+
* Allocate memory
|
24
|
+
*/
|
25
|
+
static VALUE t_allocate(VALUE klass) {
|
26
|
+
Libevent_Signal *signal;
|
27
|
+
|
28
|
+
signal = ALLOC(Libevent_Signal);
|
29
|
+
return Data_Wrap_Struct(klass, 0, t_free, signal);
|
30
|
+
}
|
31
|
+
|
32
|
+
/*
|
33
|
+
* Free memory
|
34
|
+
*/
|
35
|
+
static void t_free(Libevent_Signal *signal) {
|
36
|
+
if ( signal->ev_event ) {
|
37
|
+
event_free(signal->ev_event);
|
38
|
+
}
|
39
|
+
|
40
|
+
xfree(signal);
|
41
|
+
}
|
42
|
+
|
43
|
+
/*
|
44
|
+
* Create and add signal to specified event base with handler block
|
45
|
+
*
|
46
|
+
* @note method allocates memory for <b>struct event </b>
|
47
|
+
* that will be freed when object will be freed by ruby' GC
|
48
|
+
*
|
49
|
+
* @param [Base] base event base instance
|
50
|
+
* @param [String] name a name of signal
|
51
|
+
* @param [Object] handler object that perform signal handling. Any object that responds to :call method
|
52
|
+
*
|
53
|
+
*/
|
54
|
+
static VALUE t_initialize(VALUE self, VALUE base, VALUE name, VALUE handler) {
|
55
|
+
Libevent_Signal *le_signal;
|
56
|
+
Libevent_Base *le_base;
|
57
|
+
VALUE signal_list;
|
58
|
+
VALUE signal_number;
|
59
|
+
|
60
|
+
Data_Get_Struct(self, Libevent_Signal, le_signal);
|
61
|
+
Data_Get_Struct(base, Libevent_Base, le_base);
|
62
|
+
|
63
|
+
// check name
|
64
|
+
signal_list = rb_funcall( rb_const_get(rb_cObject, rb_intern("Signal")), rb_intern("list"), 0);
|
65
|
+
signal_number = rb_hash_aref(signal_list, name);
|
66
|
+
if ( signal_number == Qnil )
|
67
|
+
rb_raise(rb_eArgError, "unknown signal name given");
|
68
|
+
rb_iv_set(self, "@name", name);
|
69
|
+
|
70
|
+
// check handler
|
71
|
+
if ( !rb_respond_to(handler, rb_intern("call")))
|
72
|
+
rb_raise(rb_eArgError, "handler does not response to call method");
|
73
|
+
rb_iv_set(self, "@handler", handler);
|
74
|
+
|
75
|
+
// create signal event
|
76
|
+
le_signal->ev_event = evsignal_new(le_base->ev_base, FIX2INT(signal_number), t_handler, (void *)handler);
|
77
|
+
if ( !le_signal->ev_event )
|
78
|
+
rb_fatal("Could not create a signal event");
|
79
|
+
if ( event_add(le_signal->ev_event, NULL) < 0 )
|
80
|
+
rb_fatal("Could not add a signal event");
|
81
|
+
|
82
|
+
return self;
|
83
|
+
}
|
84
|
+
|
85
|
+
/*
|
86
|
+
* Delete signal from event base
|
87
|
+
* @return [true] on success
|
88
|
+
* @return [false] on failure
|
89
|
+
*/
|
90
|
+
static VALUE t_destroy(VALUE self) {
|
91
|
+
Libevent_Signal *le_signal;
|
92
|
+
int status;
|
93
|
+
|
94
|
+
Data_Get_Struct(self, Libevent_Signal, le_signal);
|
95
|
+
status = event_del(le_signal->ev_event);
|
96
|
+
|
97
|
+
return( status == -1 ? Qfalse : Qtrue);
|
98
|
+
}
|
99
|
+
|
100
|
+
/*
|
101
|
+
* C callback function that invokes call method on Ruby object.
|
102
|
+
*/
|
103
|
+
static void t_handler(evutil_socket_t signal_number, short events, void *context) {
|
104
|
+
VALUE handler = (VALUE)context;
|
105
|
+
|
106
|
+
rb_funcall(handler, rb_intern("call"), 0);
|
107
|
+
}
|
data/lib/libevent.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module Libevent
|
2
|
+
class Base
|
3
|
+
# Create new event base
|
4
|
+
def initialize
|
5
|
+
@signals = []
|
6
|
+
end
|
7
|
+
|
8
|
+
attr_reader :signals
|
9
|
+
|
10
|
+
# Create new signal with handler as block and add signal to event base
|
11
|
+
#
|
12
|
+
# @param [String] name of signal
|
13
|
+
def trap_signal(name, &block)
|
14
|
+
@signals << Signal.new(self, name, block)
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Libevent
|
2
|
+
class Builder
|
3
|
+
def initialize(options = {}, &block)
|
4
|
+
@base = Base.new
|
5
|
+
instance_eval(&block) if block_given?
|
6
|
+
end
|
7
|
+
|
8
|
+
attr_reader :base
|
9
|
+
|
10
|
+
# Create new Http instance, bind socket and options yield http object
|
11
|
+
# @param [String] host
|
12
|
+
# @param [Fixnum] port
|
13
|
+
# @return [Http] instance
|
14
|
+
def server(host, port, &block)
|
15
|
+
http = Http.new(@base)
|
16
|
+
http.bind_socket(host, port) or raise RuntimeError, "can't bind socket #{host}:#{port}"
|
17
|
+
yield(http) if block_given?
|
18
|
+
http
|
19
|
+
end
|
20
|
+
|
21
|
+
# Trap signal using event base
|
22
|
+
# @param [String] name a signal name
|
23
|
+
def signal(name, &block)
|
24
|
+
base.trap_signal(name, &block)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Start event base loop
|
28
|
+
def dispatch
|
29
|
+
base.dispatch
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Libevent
|
2
|
+
class Http
|
3
|
+
|
4
|
+
attr_reader :base
|
5
|
+
|
6
|
+
# Create virtual http server
|
7
|
+
# @param [String] domain a domain for virtual server
|
8
|
+
# @return [Http] http instance
|
9
|
+
def vhost(domain)
|
10
|
+
http = self.class.new(self.base)
|
11
|
+
add_virtual_host(domain, http)
|
12
|
+
yield(http) if block_given?
|
13
|
+
http
|
14
|
+
end
|
15
|
+
|
16
|
+
# Set request handler for current http instance
|
17
|
+
# @param block
|
18
|
+
def handler(&block)
|
19
|
+
set_request_handler(block)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require "libevent"
|
2
|
+
require "stringio"
|
3
|
+
|
4
|
+
module Rack
|
5
|
+
module Handler
|
6
|
+
class Libevent
|
7
|
+
|
8
|
+
def self.run(app, options)
|
9
|
+
server = new(app, options)
|
10
|
+
server.start
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.valid_options
|
14
|
+
{
|
15
|
+
"timeout=TIMEOUT" => "Set the timeout for an HTTP request"
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(app, options)
|
20
|
+
@app = app
|
21
|
+
|
22
|
+
options[:Host] or raise ArgumentError, "Host option required"
|
23
|
+
options[:Port] or raise ArgumentError, "Port option required"
|
24
|
+
|
25
|
+
@host = options[:Host]
|
26
|
+
@port = options[:Port].to_i
|
27
|
+
|
28
|
+
@base = ::Libevent::Base.new
|
29
|
+
@http = ::Libevent::Http.new(@base)
|
30
|
+
|
31
|
+
@http.set_timeout(options[:timeout].to_i) if options[:timeout]
|
32
|
+
end
|
33
|
+
|
34
|
+
def start
|
35
|
+
@http.bind_socket(@host, @port) or raise RuntimeError, "Can't bind to #{@host}:#{@port}"
|
36
|
+
@http.set_request_handler(self.method(:process))
|
37
|
+
|
38
|
+
@base.trap_signal("INT") { self.stop }
|
39
|
+
@base.trap_signal("TERM") { self.stop }
|
40
|
+
|
41
|
+
@base.dispatch
|
42
|
+
end
|
43
|
+
|
44
|
+
def stop
|
45
|
+
@base.exit_loop
|
46
|
+
end
|
47
|
+
|
48
|
+
protected
|
49
|
+
|
50
|
+
def process(request)
|
51
|
+
env = {}
|
52
|
+
|
53
|
+
env['REQUEST_METHOD'] = request.get_command
|
54
|
+
env['SCRIPT_NAME'] = ''
|
55
|
+
env['REQUEST_PATH'] = '/'
|
56
|
+
env['PATH_INFO'] = request.get_uri_path || '/'
|
57
|
+
env['QUERY_STRING'] = request.get_uri_query || ''
|
58
|
+
env['SERVER_NAME'] = request.get_host || @host
|
59
|
+
env['SERVER_PORT'] = @port.to_s
|
60
|
+
env['SERVER_SOFTWARE'] = "libevent/#{::Libevent::VERSION}"
|
61
|
+
env['SERVER_PROTOCOL'] = 'HTTP/1.1'
|
62
|
+
env['REMOTE_ADDR'] = request.get_remote_host
|
63
|
+
env['HTTP_VERSION'] = request.get_http_version
|
64
|
+
env['rack.version'] = [1, 1]
|
65
|
+
env['rack.url_scheme'] = request.get_uri_scheme || 'http'
|
66
|
+
env['rack.input'] = StringIO.new(request.get_body)
|
67
|
+
env['rack.errors'] = STDERR
|
68
|
+
env['rack.multithread'] = false
|
69
|
+
env['rack.multiprocess'] = false
|
70
|
+
env['rack.run_once'] = false
|
71
|
+
|
72
|
+
request.get_input_headers.each do |key, val|
|
73
|
+
env_key = ""
|
74
|
+
env_key << "HTTP_" unless key =~ /^content(_|-)(type|length)$/i
|
75
|
+
env_key << key
|
76
|
+
env_key.gsub!('-','_')
|
77
|
+
env_key.upcase!
|
78
|
+
env[env_key] = val
|
79
|
+
end
|
80
|
+
|
81
|
+
code, headers, body = @app.call(env)
|
82
|
+
|
83
|
+
begin
|
84
|
+
headers.each do |key, values|
|
85
|
+
values.split("\n").each { |value| request.add_output_header(key, value) }
|
86
|
+
end
|
87
|
+
request.send_reply_start(code, nil)
|
88
|
+
body.each { |chunk| request.send_reply_chunk(chunk) }
|
89
|
+
request.send_reply_end
|
90
|
+
ensure
|
91
|
+
body.close if body.respond_to?(:close)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
data/libevent.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "libevent/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "libevent"
|
7
|
+
s.version = Libevent::VERSION
|
8
|
+
s.authors = ["Andriy Yanko"]
|
9
|
+
s.email = ["andriy.yanko@gmail.com"]
|
10
|
+
s.homepage = "https://github.com/ayanko/libevent"
|
11
|
+
s.summary = %q{C extension for libevent}
|
12
|
+
s.description = %q{C extension for libevent}
|
13
|
+
|
14
|
+
s.rubyforge_project = "libevent"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.extensions = ["ext/libevent_ext/extconf.rb"]
|
22
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$:.unshift File.expand_path('../../lib', __FILE__)
|
4
|
+
|
5
|
+
require "libevent"
|
6
|
+
require "rack/handler/libevent"
|
7
|
+
|
8
|
+
app = lambda { |env|
|
9
|
+
body = []
|
10
|
+
body << "RACK_ENVIRONMENT:\n"
|
11
|
+
env.sort.each { |pair| body << "%-20s : %s\n" % pair }
|
12
|
+
body << "RACK_INPUT_START"
|
13
|
+
body << env['rack.input'].read
|
14
|
+
body << "RACK_INPUT_END\n"
|
15
|
+
|
16
|
+
headers = {}
|
17
|
+
headers['Content-Type'] = 'text/plain'
|
18
|
+
headers['Content-Length'] = body.inject(0) { |sum, chunk| sum += chunk.size }.to_s
|
19
|
+
headers['Set-Cookie'] = "one=first\nsecond=two"
|
20
|
+
|
21
|
+
[ 200, headers, body ]
|
22
|
+
}
|
23
|
+
|
24
|
+
Rack::Handler::Libevent.run(app, {
|
25
|
+
:Host => "0.0.0.0",
|
26
|
+
:Port => 3000,
|
27
|
+
:timeout => 60,
|
28
|
+
})
|
data/samples/simple.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require "libevent"
|
2
|
+
|
3
|
+
# create event base
|
4
|
+
base = Libevent::Base.new
|
5
|
+
|
6
|
+
# create http server instance
|
7
|
+
http = Libevent::Http.new(base)
|
8
|
+
|
9
|
+
# bind socket
|
10
|
+
http.bind_socket("0.0.0.0", 15015)
|
11
|
+
|
12
|
+
# set handler
|
13
|
+
http.handler do |request|
|
14
|
+
request.send_reply(200, {}, ["Hello World\n"])
|
15
|
+
end
|
16
|
+
|
17
|
+
# catch SIGINT
|
18
|
+
base.trap_signal("INT") { base.exit_loop }
|
19
|
+
|
20
|
+
# start libevent loop
|
21
|
+
base.dispatch
|
22
|
+
|
@@ -0,0 +1,63 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$:.unshift File.expand_path('../../lib', __FILE__)
|
4
|
+
|
5
|
+
require "libevent"
|
6
|
+
|
7
|
+
# curl -v -H 'Host: blog.local' http://localhost:3001/hello
|
8
|
+
# curl -v -H 'Host: blog.local' http://localhost:3001/api
|
9
|
+
# curl -v -H 'Host: wiki.local' http://localhost:3001
|
10
|
+
# curl -v -H 'Host: any.local' http://localhost:3001
|
11
|
+
|
12
|
+
Libevent::Builder.new do
|
13
|
+
|
14
|
+
server "0.0.0.0", 3000 do |http|
|
15
|
+
|
16
|
+
http.handler do |request|
|
17
|
+
case request.get_uri_path
|
18
|
+
when '/hello'
|
19
|
+
request.send_reply 200, { 'Content->Type' => 'text/plain'}, [ "Hello World" ]
|
20
|
+
when '/api'
|
21
|
+
request.send_reply 200, { 'Content->Type' => 'application/json'}, [ "{\"version\":\"1.0\"}" ]
|
22
|
+
else
|
23
|
+
request.send_error 404, "Nothing Found"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
http.vhost "blog.local" do |host|
|
28
|
+
host.handler do |request|
|
29
|
+
request.send_reply 200, {}, ["It's blog"]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
http.vhost "wiki.local" do |host|
|
34
|
+
host.handler do |request|
|
35
|
+
request.send_reply 200, {}, ["It's wiki"]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
http.vhost "*.local" do |host|
|
40
|
+
host.handler do |request|
|
41
|
+
request.send_error 404, "Please use blog.local or wiki.local"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
server "0.0.0.0", 3001 do |http|
|
48
|
+
http.handler do |request|
|
49
|
+
request.send_reply 200, { 'Content->Type' => 'text/plain'}, [ "Hello World 4000" ]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
signal("INT") do
|
54
|
+
base.exit_loop
|
55
|
+
end
|
56
|
+
|
57
|
+
signal("HUP") do
|
58
|
+
Kernel.puts "HUP received ..."
|
59
|
+
end
|
60
|
+
|
61
|
+
dispatch
|
62
|
+
|
63
|
+
end
|