thin-fun_embed 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +4 -0
- data/LICENSE +23 -0
- data/README.rdoc +118 -0
- data/Rakefile +2 -0
- data/examples/config.ru +2 -0
- data/examples/rack_like.rb +28 -0
- data/examples/simple_200_ok.rb +16 -0
- data/examples/status_body.rb +16 -0
- data/lib/thin/fun_embed/constants.rb +18 -0
- data/lib/thin/fun_embed/version.rb +6 -0
- data/lib/thin/fun_embed.rb +179 -0
- data/thin-fun_embed.gemspec +21 -0
- metadata +74 -0
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
Copyright (c) 2012 Sokolov Yura 'funny-falcon'
|
2
|
+
Copyright (c) 2011 Nils Franzén
|
3
|
+
|
4
|
+
MIT License
|
5
|
+
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
7
|
+
a copy of this software and associated documentation files (the
|
8
|
+
"Software"), to deal in the Software without restriction, including
|
9
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
10
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
11
|
+
permit persons to whom the Software is furnished to do so, subject to
|
12
|
+
the following conditions:
|
13
|
+
|
14
|
+
The above copyright notice and this permission notice shall be
|
15
|
+
included in all copies or substantial portions of the Software.
|
16
|
+
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
19
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
20
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
21
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
22
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
23
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
= Thin::FunEmbed
|
2
|
+
|
3
|
+
This is minimalistic web server for embedding into EventMachine-enabled application
|
4
|
+
based on {idea of Nils Franzén}[http://www.franzens.org/2011/10/writing-minimalistic-web-server-using.html],
|
5
|
+
but it uses origin Thin::Request instead of custom wrapper to thin parser.
|
6
|
+
|
7
|
+
It is intentionally not full fledge rack server, but you could use +send_status_headers_body+ to return
|
8
|
+
rack application's response to client.
|
9
|
+
|
10
|
+
It is capable to serve keep-alive requests (but not pipelined).
|
11
|
+
|
12
|
+
It is faster than Thin itself cause it allows you to send very custom response, and even for Rack like
|
13
|
+
response it doesn't do many checks on your output. But it meens, that you responsible for you response :)
|
14
|
+
|
15
|
+
Rack hello world, tested with +ab -k -c 500 -n 50000 127.0.0.1:8080/+ on Core i3@1600Mhz
|
16
|
+
|
17
|
+
* with Thin::FunEmbed (see examples/rack_like.rb) ~ 14000 req/sec
|
18
|
+
* with `thin -p 8080 -e production start` ~ 6500 req/sec
|
19
|
+
|
20
|
+
(But consider, that this is for very dump response. Do not expect big speedup for heavy weight ones')
|
21
|
+
|
22
|
+
Also, it doesn't bother with keep-alive timeouts, limits and many other things that Thin do. But it is not
|
23
|
+
too hard too look for them in the Thin's sources. And, in any way, it is better to place this "server" behind
|
24
|
+
"something like" Nginx, which could use keep-alive connections to upstream since version 1.1 .
|
25
|
+
|
26
|
+
== Installation
|
27
|
+
|
28
|
+
Add this line to your application's Gemfile:
|
29
|
+
|
30
|
+
gem 'thin-fun_embed'
|
31
|
+
|
32
|
+
And then execute:
|
33
|
+
|
34
|
+
$ bundle
|
35
|
+
|
36
|
+
Or install it yourself as:
|
37
|
+
|
38
|
+
$ gem install thin-fun_embed
|
39
|
+
|
40
|
+
== Usage
|
41
|
+
|
42
|
+
You should subclass Thin::FunEmbed and override #handle_http_request method. Then you should start it with
|
43
|
+
EM.start_server as any other EM::Connection .
|
44
|
+
|
45
|
+
Abstract example:
|
46
|
+
require 'thin/fun_embed.rb'
|
47
|
+
class MyServer < Thin::FunEmbed
|
48
|
+
def handle_http_request(env)
|
49
|
+
if rack_like_response?
|
50
|
+
send_status_headers_body(status, headers, body)
|
51
|
+
elsif is_200_ok?
|
52
|
+
send_200_ok(body)
|
53
|
+
elsif is_status_body?
|
54
|
+
send_status_body(status, body)
|
55
|
+
elsif is_raw_string?
|
56
|
+
send_raw_string(full_http_response_string, try_to_keep_alive)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
host, port = '0.0.0.0', 8080
|
62
|
+
EM.run do
|
63
|
+
EM.start_server host, port, MyServer
|
64
|
+
end
|
65
|
+
|
66
|
+
200 ok example:
|
67
|
+
require 'thin/fun_embed.rb'
|
68
|
+
|
69
|
+
class Simple200ok < Thin::FunEmbed
|
70
|
+
def handle_http_request(env)
|
71
|
+
if rand(2) == 1
|
72
|
+
send_200_ok('{"hello":"world"}', 'application/javascript')
|
73
|
+
else
|
74
|
+
send_200_ok('hello world')
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
EM.run do
|
80
|
+
EM.start_server '0.0.0.0', 8080, Simple200ok
|
81
|
+
end
|
82
|
+
|
83
|
+
Rack like example with correct socket closing:
|
84
|
+
require 'thin/fun_embed.rb'
|
85
|
+
|
86
|
+
class RackLikeServer < Thin::FunEmbed
|
87
|
+
attr_accessor :app
|
88
|
+
def handle_http_request(env)
|
89
|
+
send_rack_response(*app.call(env))
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
app = proc{|env| [200, {'Content-Length'=>6}, ['hello', "\n"]]}
|
94
|
+
|
95
|
+
host, port = '0.0.0.0', 8080
|
96
|
+
all_conns = {}
|
97
|
+
trap(:INT) do
|
98
|
+
EM.schedule{
|
99
|
+
all_conns.each{|conn, _| conn.close_after_writting}
|
100
|
+
EM.next_tick{ EM.stop }
|
101
|
+
}
|
102
|
+
end
|
103
|
+
|
104
|
+
EM.run do
|
105
|
+
EM.start_server host, port, RackLikeServer do |conn|
|
106
|
+
conn.app = app
|
107
|
+
all_conns[conn] = true
|
108
|
+
conn.unbind_callback = all_conns.method(:delete)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
== Contributing
|
113
|
+
|
114
|
+
1. Fork it
|
115
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
116
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
117
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
118
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/examples/config.ru
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# :stopdoc:
|
2
|
+
require 'thin/fun_embed.rb'
|
3
|
+
|
4
|
+
class RackLikeServer < Thin::FunEmbed
|
5
|
+
attr_accessor :app
|
6
|
+
def handle_http_request(env)
|
7
|
+
send_rack_response(*app.call(env))
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
app = proc{|env| [200, {'Content-Length'=>6}, ['hello', "\n"]]}
|
12
|
+
|
13
|
+
host, port = '0.0.0.0', 8080
|
14
|
+
all_conns = {}
|
15
|
+
trap(:INT) do
|
16
|
+
EM.schedule{
|
17
|
+
all_conns.each{|conn, _| conn.close_after_writting}
|
18
|
+
EM.next_tick{ EM.stop }
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
EM.run do
|
23
|
+
EM.start_server host, port, RackLikeServer do |conn|
|
24
|
+
conn.app = app
|
25
|
+
all_conns[conn] = true
|
26
|
+
conn.unbind_callback = all_conns.method(:delete)
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# :stopdoc:
|
2
|
+
require 'thin/fun_embed.rb'
|
3
|
+
|
4
|
+
class Simple200ok < Thin::FunEmbed
|
5
|
+
def handle_http_request(env)
|
6
|
+
if false && rand(2) == 1
|
7
|
+
send_200_ok('{"hello":"world"}', 'application/javascript')
|
8
|
+
else
|
9
|
+
send_200_ok('hello world')
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
EM.run do
|
15
|
+
EM.start_server '0.0.0.0', 8080, Simple200ok
|
16
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# :stopdoc:
|
2
|
+
require 'thin/fun_embed.rb'
|
3
|
+
|
4
|
+
class StatusBody < Thin::FunEmbed
|
5
|
+
def handle_http_request(env)
|
6
|
+
if env[PATH_INFO] =~ /\.js$/
|
7
|
+
send_status_body(200, '{"hello":"world"}', 'application/javascript')
|
8
|
+
else
|
9
|
+
send_status_body(200, 'hello world')
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
EM.run do
|
15
|
+
EM.start_server '0.0.0.0', 8080, StatusBody
|
16
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Thin # :nodoc:
|
2
|
+
class FunEmbed
|
3
|
+
# Usefull constants for analysing Rack environmentc
|
4
|
+
module Constants
|
5
|
+
PATH_INFO = 'PATH_INFO'.freeze
|
6
|
+
REQUEST_METHOD = 'REQUEST_METHOD'.freeze
|
7
|
+
SCRIPT_NAME = 'SCRIPT_NAME'.freeze
|
8
|
+
QUERY_STRING = 'QUERY_STRING'.freeze
|
9
|
+
SERVER_NAME = 'SERVER_NAME'.freeze
|
10
|
+
SERVER_PORT = 'SERVER_PORT'.freeze
|
11
|
+
|
12
|
+
GET = 'GET'.freeze
|
13
|
+
POST = 'POST'.freeze
|
14
|
+
DELETE = 'DELETE'.freeze
|
15
|
+
PUT = 'PUT'.freeze
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
require "eventmachine"
|
2
|
+
require "thin_parser"
|
3
|
+
require "thin/request"
|
4
|
+
require "thin/statuses"
|
5
|
+
require "thin/fun_embed/version"
|
6
|
+
require "thin/fun_embed/constants"
|
7
|
+
|
8
|
+
module Thin # :nodoc:
|
9
|
+
SERVER = "FunEmbed #{FunEmbed::VERSION}".freeze
|
10
|
+
module VERSION # :nodoc:
|
11
|
+
RACK = [1,1].freeze
|
12
|
+
end
|
13
|
+
|
14
|
+
# minimalistic HTTP server for embedding into EventMachine'd applications
|
15
|
+
#
|
16
|
+
# Method #handle_http_request accept fully formed Rack environment and should
|
17
|
+
# call send http status+headers+body using one of #send_200_ok, #send_status_body,
|
18
|
+
# #send_raw_string and #send_rack_response methods or manually. If you send
|
19
|
+
# response manually, then you should call #consider_keep_alive method with your wish:
|
20
|
+
# whether you want to try keep-alive connection or not.
|
21
|
+
#
|
22
|
+
# require 'thin/fun_embed.rb'
|
23
|
+
#
|
24
|
+
# class Simple200ok < Thin::FunEmbed
|
25
|
+
# def handle_http_request(env)
|
26
|
+
# if rand(2) == 1
|
27
|
+
# send_200_ok('{"hello":"world"}', 'application/javascript')
|
28
|
+
# else
|
29
|
+
# send_200_ok('hello world')
|
30
|
+
# end
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# EM.run do
|
35
|
+
# EM.start_server '0.0.0.0', 8080, Simple200ok
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
class FunEmbed < EM::Connection
|
39
|
+
# :stopdoc:
|
40
|
+
include Constants
|
41
|
+
FULL_KEEP_ALIVE = "Connection: keep-alive\r\n".freeze
|
42
|
+
RN = "\r\n".freeze
|
43
|
+
RNRN = "\r\n\r\n".freeze
|
44
|
+
NL = "\n".freeze
|
45
|
+
CONTENT_LENGTH = "Content-Length".freeze
|
46
|
+
CONTENT_LENGTH_DT = "\r\nContent-Length: ".freeze
|
47
|
+
CONNECTION = 'Connection'.freeze
|
48
|
+
KEEP_ALIVE = 'keep-alive'.freeze
|
49
|
+
CLOSE = 'close'.freeze
|
50
|
+
TEXT_PLAIN = 'text/plain'.freeze
|
51
|
+
HTTP_STATUS_CODES = Thin::HTTP_STATUS_CODES
|
52
|
+
# :startdoc:
|
53
|
+
|
54
|
+
# callback, which called from +#unbind+.
|
55
|
+
# Could be used for monitoring connections.
|
56
|
+
attr_accessor :unbind_callback
|
57
|
+
|
58
|
+
# signals if client may accept keep-alive connection
|
59
|
+
attr :keep_alive
|
60
|
+
|
61
|
+
def post_init
|
62
|
+
@keep_alive = nil
|
63
|
+
@parser = Thin::Request.new
|
64
|
+
end
|
65
|
+
|
66
|
+
def receive_data(data)
|
67
|
+
if @parser.parse(data)
|
68
|
+
@keep_alive = @parser.persistent?
|
69
|
+
handle_http_request(@parser.env)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# main method for override
|
74
|
+
# you ought to put your application logic here
|
75
|
+
def handle_http_request(env)
|
76
|
+
send_200_ok "you should override #handle_http_request"
|
77
|
+
end
|
78
|
+
|
79
|
+
# send simple '200 OK' response with a body
|
80
|
+
def send_200_ok(body = '', type = TEXT_PLAIN, try_keep_alive = true)
|
81
|
+
send_data("HTTP/1.1 200 OK\r\nContent-Type: #{type}\r\n"\
|
82
|
+
"Content-Length: #{body.bytesize}\r\n"\
|
83
|
+
"#{keep_alive ? FULL_KEEP_ALIVE : nil}\r\n")
|
84
|
+
send_data(body)
|
85
|
+
consider_keep_alive(try_keep_alive)
|
86
|
+
end
|
87
|
+
|
88
|
+
# send simple response with status, body and type
|
89
|
+
def send_status_body(status, body = '', type = TEXT_PLAIN, try_keep_alive = true)
|
90
|
+
status = status.to_i
|
91
|
+
if status < 200 || status == 204 || status == 304
|
92
|
+
send_data("HTTP/1.1 #{status} #{HTTP_STATUS_CODES[status]}\r\n"\
|
93
|
+
"#{keep_alive ? FULL_KEEP_ALIVE : nil}\r\n")
|
94
|
+
else
|
95
|
+
send_data("HTTP/1.1 #{status} #{HTTP_STATUS_CODES[status]}\r\n"\
|
96
|
+
"Content-Type: #{type}\r\n"\
|
97
|
+
"Content-Length: #{body.bytesize}\r\n"\
|
98
|
+
"#{keep_alive ? FULL_KEEP_ALIVE : nil}\r\n")
|
99
|
+
send_data(body)
|
100
|
+
end
|
101
|
+
consider_keep_alive(try_keep_alive)
|
102
|
+
end
|
103
|
+
|
104
|
+
# send fully formatted HTTP response
|
105
|
+
def send_raw_string(string, try_keep_alive = true)
|
106
|
+
send_data(string)
|
107
|
+
try_keep_alive &&= @keep_alive &&
|
108
|
+
(cl = string.index(CONTENT_LENGTH_DT)) &&
|
109
|
+
(kai = string.index(FULL_KEEP_ALIVE)) &&
|
110
|
+
kai < (rn = string.index(RNRN)) &&
|
111
|
+
cl < rn
|
112
|
+
consider_keep_alive(try_keep_alive)
|
113
|
+
end
|
114
|
+
|
115
|
+
# send Rack like response (status, headers, body)
|
116
|
+
#
|
117
|
+
# This method tries to implement as much of Rack as it progmatically needed, but not more.
|
118
|
+
# Test it before use
|
119
|
+
def send_rack_response(status, headers, body, try_keep_alive = true)
|
120
|
+
status = status.to_i
|
121
|
+
out = "HTTP/1.1 #{status} #{HTTP_STATUS_CODES[status]}\r\n"
|
122
|
+
|
123
|
+
if String === headers
|
124
|
+
try_keep_alive &&= headers.index(CONTENT_LENGTH_DT) && headers.index(FULL_KEEP_ALIVE)
|
125
|
+
out << headers
|
126
|
+
else
|
127
|
+
content_length = connection = nil
|
128
|
+
headers.each do |k, v|
|
129
|
+
if k == CONTENT_LENGTH
|
130
|
+
content_length = v
|
131
|
+
elsif k == CONNECTION
|
132
|
+
connection = v
|
133
|
+
end
|
134
|
+
if String === v
|
135
|
+
unless v.index(NL)
|
136
|
+
out << "#{k}: #{v}\r\n"
|
137
|
+
else
|
138
|
+
v.each_line{|l| out << "#{k}: #{l}\r\n"}
|
139
|
+
end
|
140
|
+
elsif v.respond_to?(:each)
|
141
|
+
v.each{|l| out << "#{k}: #{l}\r\n"}
|
142
|
+
else
|
143
|
+
unless (v=v.to_s).index(NL)
|
144
|
+
out << "#{k}: #{v}\r\n"
|
145
|
+
else
|
146
|
+
v.each_line{|l| out << "#{k}: #{l}\r\n"}
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
try_keep_alive &&= @keep_alive && content_length && (!connection || connection == KEEP_ALIVE)
|
152
|
+
out << FULL_KEEP_ALIVE if try_keep_alive && !connection
|
153
|
+
end
|
154
|
+
out << RN
|
155
|
+
send_data out
|
156
|
+
|
157
|
+
if String === body
|
158
|
+
send_data(body)
|
159
|
+
else
|
160
|
+
body.each{|s| send_data(s)}
|
161
|
+
end
|
162
|
+
|
163
|
+
consider_keep_alive(try_keep_alive)
|
164
|
+
end
|
165
|
+
|
166
|
+
# call this when you fully send response
|
167
|
+
def consider_keep_alive(try_keep_alive = true)
|
168
|
+
if @keep_alive && try_keep_alive
|
169
|
+
post_init
|
170
|
+
else
|
171
|
+
close_connection_after_writing
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def unbind # :nodoc:
|
176
|
+
@unbind_callback && @unbind_callback.call(self)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/thin/fun_embed/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Sokolov Yura 'funny-falcon'"]
|
6
|
+
gem.email = ["funny.falcon@gmail.com"]
|
7
|
+
gem.description = %q{trim of Thin web server for embedding into eventmachined application}
|
8
|
+
gem.summary = "Subclass of EM::Connection which uses thin internals to do http request handling.\n"\
|
9
|
+
"It is intentionally not Rack server, but could be used to build some."
|
10
|
+
|
11
|
+
gem.homepage = "https://github.com/funny-falcon/thin-fun_embed"
|
12
|
+
|
13
|
+
gem.files = Dir["examples/*"] + Dir["lib/**/*.rb"] +
|
14
|
+
%w{Gemfile LICENSE Rakefile README.rdoc thin-fun_embed.gemspec}
|
15
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
16
|
+
gem.name = "thin-fun_embed"
|
17
|
+
gem.require_paths = ["lib"]
|
18
|
+
gem.version = Thin::FunEmbed::VERSION
|
19
|
+
|
20
|
+
gem.add_dependency 'thin', ['>= 1.4']
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: thin-fun_embed
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Sokolov Yura 'funny-falcon'
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-08-22 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: thin
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.4'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.4'
|
30
|
+
description: trim of Thin web server for embedding into eventmachined application
|
31
|
+
email:
|
32
|
+
- funny.falcon@gmail.com
|
33
|
+
executables: []
|
34
|
+
extensions: []
|
35
|
+
extra_rdoc_files: []
|
36
|
+
files:
|
37
|
+
- examples/simple_200_ok.rb
|
38
|
+
- examples/rack_like.rb
|
39
|
+
- examples/status_body.rb
|
40
|
+
- examples/config.ru
|
41
|
+
- lib/thin/fun_embed/constants.rb
|
42
|
+
- lib/thin/fun_embed/version.rb
|
43
|
+
- lib/thin/fun_embed.rb
|
44
|
+
- Gemfile
|
45
|
+
- LICENSE
|
46
|
+
- Rakefile
|
47
|
+
- README.rdoc
|
48
|
+
- thin-fun_embed.gemspec
|
49
|
+
homepage: https://github.com/funny-falcon/thin-fun_embed
|
50
|
+
licenses: []
|
51
|
+
post_install_message:
|
52
|
+
rdoc_options: []
|
53
|
+
require_paths:
|
54
|
+
- lib
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ! '>='
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ! '>='
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
requirements: []
|
68
|
+
rubyforge_project:
|
69
|
+
rubygems_version: 1.8.24
|
70
|
+
signing_key:
|
71
|
+
specification_version: 3
|
72
|
+
summary: Subclass of EM::Connection which uses thin internals to do http request handling.
|
73
|
+
It is intentionally not Rack server, but could be used to build some.
|
74
|
+
test_files: []
|