libevent 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|