em-http-request 1.0.1 → 1.0.2
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.
Potentially problematic release.
This version of em-http-request might be problematic. Click here for more details.
- data/examples/fibered-http.rb +51 -51
- data/lib/em-http/client.rb +309 -307
- data/lib/em-http/http_client_options.rb +3 -9
- data/lib/em-http/http_connection.rb +205 -199
- data/lib/em-http/version.rb +1 -1
- data/spec/client_fiber_spec.rb +10 -8
- data/spec/client_spec.rb +763 -734
- data/spec/http_proxy_spec.rb +22 -1
- data/spec/redirect_spec.rb +4 -4
- data/spec/stallion.rb +270 -273
- metadata +22 -22
@@ -12,7 +12,6 @@ class HttpClientOptions
|
|
12
12
|
|
13
13
|
@method = method.to_s.upcase
|
14
14
|
@headers = options[:head] || {}
|
15
|
-
@proxy = options[:proxy] || {}
|
16
15
|
@query = options[:query]
|
17
16
|
@path = options[:path]
|
18
17
|
|
@@ -45,13 +44,8 @@ class HttpClientOptions
|
|
45
44
|
@uri.port ||= 80
|
46
45
|
end
|
47
46
|
|
48
|
-
|
49
|
-
|
50
|
-
@port = @proxy[:port]
|
51
|
-
else
|
52
|
-
@host = @uri.host
|
53
|
-
@port = @uri.port
|
54
|
-
end
|
47
|
+
@host = @uri.host
|
48
|
+
@port = @uri.port
|
55
49
|
|
56
50
|
end
|
57
|
-
end
|
51
|
+
end
|
@@ -1,199 +1,205 @@
|
|
1
|
-
module EventMachine
|
2
|
-
|
3
|
-
module HTTPMethods
|
4
|
-
def get options = {}, &blk; setup_request(:get, options, &blk); end
|
5
|
-
def head options = {}, &blk; setup_request(:head, options, &blk); end
|
6
|
-
def delete options = {}, &blk; setup_request(:delete,options, &blk); end
|
7
|
-
def put options = {}, &blk; setup_request(:put, options, &blk); end
|
8
|
-
def post options = {}, &blk; setup_request(:post, options, &blk); end
|
9
|
-
end
|
10
|
-
|
11
|
-
class HttpStubConnection < Connection
|
12
|
-
include Deferrable
|
13
|
-
attr_reader :parent
|
14
|
-
|
15
|
-
def parent=(p)
|
16
|
-
@parent = p
|
17
|
-
@parent.conn = self
|
18
|
-
end
|
19
|
-
|
20
|
-
def receive_data(data)
|
21
|
-
@parent.receive_data data
|
22
|
-
end
|
23
|
-
|
24
|
-
def connection_completed
|
25
|
-
@parent.connection_completed
|
26
|
-
end
|
27
|
-
|
28
|
-
def unbind(reason=nil)
|
29
|
-
@parent.unbind(reason)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
class HttpConnection
|
34
|
-
include HTTPMethods
|
35
|
-
include Socksify
|
36
|
-
|
37
|
-
attr_reader :deferred
|
38
|
-
attr_accessor :error, :connopts, :uri, :conn
|
39
|
-
|
40
|
-
def initialize
|
41
|
-
@deferred = true
|
42
|
-
@middleware = []
|
43
|
-
end
|
44
|
-
|
45
|
-
def conn=(c)
|
46
|
-
@conn = c
|
47
|
-
@deferred = false
|
48
|
-
end
|
49
|
-
|
50
|
-
def activate_connection(client)
|
51
|
-
begin
|
52
|
-
EventMachine.bind_connect(@connopts.bind, @connopts.bind_port, @connopts.host, @connopts.port, HttpStubConnection) do |conn|
|
53
|
-
post_init
|
54
|
-
|
55
|
-
@deferred = false
|
56
|
-
@conn = conn
|
57
|
-
|
58
|
-
conn.parent = self
|
59
|
-
conn.pending_connect_timeout = @connopts.connect_timeout
|
60
|
-
conn.comm_inactivity_timeout = @connopts.inactivity_timeout
|
61
|
-
end
|
62
|
-
|
63
|
-
finalize_request(client)
|
64
|
-
rescue EventMachine::ConnectionError => e
|
65
|
-
#
|
66
|
-
# Currently, this can only fire on initial connection setup
|
67
|
-
# since #connect is a synchronous method. Hence, rescue the exception,
|
68
|
-
# and return a failed deferred which fail any client request at next
|
69
|
-
# tick. We fail at next tick to keep a consistent API when the newly
|
70
|
-
# created HttpClient is failed. This approach has the advantage to
|
71
|
-
# remove a state check of @deferred_status after creating a new
|
72
|
-
# HttpRequest. The drawback is that users may setup a callback which we
|
73
|
-
# know won't be used.
|
74
|
-
#
|
75
|
-
# Once there is async-DNS, then we'll iterate over the outstanding
|
76
|
-
# client requests and fail them in order.
|
77
|
-
#
|
78
|
-
# Net outcome: failed connection will invoke the same ConnectionError
|
79
|
-
# message on the connection deferred, and on the client deferred.
|
80
|
-
#
|
81
|
-
EM.next_tick{client.close(e.message)}
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
def setup_request(method, options = {}, c = nil)
|
86
|
-
c ||= HttpClient.new(self, HttpClientOptions.new(@uri, options, method))
|
87
|
-
@deferred ? activate_connection(c) : finalize_request(c)
|
88
|
-
c
|
89
|
-
end
|
90
|
-
|
91
|
-
def finalize_request(c)
|
92
|
-
@conn.callback { c.connection_completed }
|
93
|
-
|
94
|
-
middleware.each do |m|
|
95
|
-
c.callback &m.method(:response) if m.respond_to?(:response)
|
96
|
-
end
|
97
|
-
|
98
|
-
@clients.push c
|
99
|
-
end
|
100
|
-
|
101
|
-
def middleware
|
102
|
-
[HttpRequest.middleware, @middleware].flatten
|
103
|
-
end
|
104
|
-
|
105
|
-
def post_init
|
106
|
-
@clients = []
|
107
|
-
@pending = []
|
108
|
-
|
109
|
-
@p = Http::Parser.new
|
110
|
-
@p.header_value_type = :mixed
|
111
|
-
@p.on_headers_complete = proc do |h|
|
112
|
-
client.parse_response_header(h, @p.http_version, @p.status_code)
|
113
|
-
:reset if client.req.no_body?
|
114
|
-
end
|
115
|
-
|
116
|
-
@p.on_body = proc do |b|
|
117
|
-
client.on_body_data(b)
|
118
|
-
end
|
119
|
-
|
120
|
-
@p.on_message_complete = proc do
|
121
|
-
if not client.continue?
|
122
|
-
c = @clients.shift
|
123
|
-
c.state = :finished
|
124
|
-
c.on_request_complete
|
125
|
-
end
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
def use(klass, *args, &block)
|
130
|
-
@middleware << klass.new(*args, &block)
|
131
|
-
end
|
132
|
-
|
133
|
-
def peer
|
134
|
-
Socket.unpack_sockaddr_in(@peer)[1] rescue nil
|
135
|
-
end
|
136
|
-
|
137
|
-
def receive_data(data)
|
138
|
-
begin
|
139
|
-
@p << data
|
140
|
-
rescue HTTP::Parser::Error => e
|
141
|
-
c = @clients.shift
|
142
|
-
c.nil? ? unbind(e.message) : c.on_error(e.message)
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
|
-
def connection_completed
|
147
|
-
@peer = @conn.get_peername
|
148
|
-
|
149
|
-
if @connopts.proxy && @connopts.proxy[:type] == :socks5
|
150
|
-
socksify(client.req.uri.host, client.req.uri.port, *@connopts.proxy[:authorization]) { start }
|
151
|
-
else
|
152
|
-
start
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
def start
|
157
|
-
@conn.start_tls(@connopts.tls) if client && client.req.ssl?
|
158
|
-
@conn.succeed
|
159
|
-
end
|
160
|
-
|
161
|
-
def redirect(client)
|
162
|
-
@pending.push client
|
163
|
-
end
|
164
|
-
|
165
|
-
def unbind(reason)
|
166
|
-
@clients.map { |c| c.unbind(reason) }
|
167
|
-
|
168
|
-
if r = @pending.shift
|
169
|
-
@clients.push r
|
170
|
-
|
171
|
-
r.reset!
|
172
|
-
@p.reset!
|
173
|
-
|
174
|
-
begin
|
175
|
-
@conn.set_deferred_status :unknown
|
176
|
-
|
177
|
-
@
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
1
|
+
module EventMachine
|
2
|
+
|
3
|
+
module HTTPMethods
|
4
|
+
def get options = {}, &blk; setup_request(:get, options, &blk); end
|
5
|
+
def head options = {}, &blk; setup_request(:head, options, &blk); end
|
6
|
+
def delete options = {}, &blk; setup_request(:delete,options, &blk); end
|
7
|
+
def put options = {}, &blk; setup_request(:put, options, &blk); end
|
8
|
+
def post options = {}, &blk; setup_request(:post, options, &blk); end
|
9
|
+
end
|
10
|
+
|
11
|
+
class HttpStubConnection < Connection
|
12
|
+
include Deferrable
|
13
|
+
attr_reader :parent
|
14
|
+
|
15
|
+
def parent=(p)
|
16
|
+
@parent = p
|
17
|
+
@parent.conn = self
|
18
|
+
end
|
19
|
+
|
20
|
+
def receive_data(data)
|
21
|
+
@parent.receive_data data
|
22
|
+
end
|
23
|
+
|
24
|
+
def connection_completed
|
25
|
+
@parent.connection_completed
|
26
|
+
end
|
27
|
+
|
28
|
+
def unbind(reason=nil)
|
29
|
+
@parent.unbind(reason)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class HttpConnection
|
34
|
+
include HTTPMethods
|
35
|
+
include Socksify
|
36
|
+
|
37
|
+
attr_reader :deferred
|
38
|
+
attr_accessor :error, :connopts, :uri, :conn
|
39
|
+
|
40
|
+
def initialize
|
41
|
+
@deferred = true
|
42
|
+
@middleware = []
|
43
|
+
end
|
44
|
+
|
45
|
+
def conn=(c)
|
46
|
+
@conn = c
|
47
|
+
@deferred = false
|
48
|
+
end
|
49
|
+
|
50
|
+
def activate_connection(client)
|
51
|
+
begin
|
52
|
+
EventMachine.bind_connect(@connopts.bind, @connopts.bind_port, @connopts.host, @connopts.port, HttpStubConnection) do |conn|
|
53
|
+
post_init
|
54
|
+
|
55
|
+
@deferred = false
|
56
|
+
@conn = conn
|
57
|
+
|
58
|
+
conn.parent = self
|
59
|
+
conn.pending_connect_timeout = @connopts.connect_timeout
|
60
|
+
conn.comm_inactivity_timeout = @connopts.inactivity_timeout
|
61
|
+
end
|
62
|
+
|
63
|
+
finalize_request(client)
|
64
|
+
rescue EventMachine::ConnectionError => e
|
65
|
+
#
|
66
|
+
# Currently, this can only fire on initial connection setup
|
67
|
+
# since #connect is a synchronous method. Hence, rescue the exception,
|
68
|
+
# and return a failed deferred which fail any client request at next
|
69
|
+
# tick. We fail at next tick to keep a consistent API when the newly
|
70
|
+
# created HttpClient is failed. This approach has the advantage to
|
71
|
+
# remove a state check of @deferred_status after creating a new
|
72
|
+
# HttpRequest. The drawback is that users may setup a callback which we
|
73
|
+
# know won't be used.
|
74
|
+
#
|
75
|
+
# Once there is async-DNS, then we'll iterate over the outstanding
|
76
|
+
# client requests and fail them in order.
|
77
|
+
#
|
78
|
+
# Net outcome: failed connection will invoke the same ConnectionError
|
79
|
+
# message on the connection deferred, and on the client deferred.
|
80
|
+
#
|
81
|
+
EM.next_tick{client.close(e.message)}
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def setup_request(method, options = {}, c = nil)
|
86
|
+
c ||= HttpClient.new(self, HttpClientOptions.new(@uri, options, method))
|
87
|
+
@deferred ? activate_connection(c) : finalize_request(c)
|
88
|
+
c
|
89
|
+
end
|
90
|
+
|
91
|
+
def finalize_request(c)
|
92
|
+
@conn.callback { c.connection_completed }
|
93
|
+
|
94
|
+
middleware.each do |m|
|
95
|
+
c.callback &m.method(:response) if m.respond_to?(:response)
|
96
|
+
end
|
97
|
+
|
98
|
+
@clients.push c
|
99
|
+
end
|
100
|
+
|
101
|
+
def middleware
|
102
|
+
[HttpRequest.middleware, @middleware].flatten
|
103
|
+
end
|
104
|
+
|
105
|
+
def post_init
|
106
|
+
@clients = []
|
107
|
+
@pending = []
|
108
|
+
|
109
|
+
@p = Http::Parser.new
|
110
|
+
@p.header_value_type = :mixed
|
111
|
+
@p.on_headers_complete = proc do |h|
|
112
|
+
client.parse_response_header(h, @p.http_version, @p.status_code)
|
113
|
+
:reset if client.req.no_body?
|
114
|
+
end
|
115
|
+
|
116
|
+
@p.on_body = proc do |b|
|
117
|
+
client.on_body_data(b)
|
118
|
+
end
|
119
|
+
|
120
|
+
@p.on_message_complete = proc do
|
121
|
+
if not client.continue?
|
122
|
+
c = @clients.shift
|
123
|
+
c.state = :finished
|
124
|
+
c.on_request_complete
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def use(klass, *args, &block)
|
130
|
+
@middleware << klass.new(*args, &block)
|
131
|
+
end
|
132
|
+
|
133
|
+
def peer
|
134
|
+
Socket.unpack_sockaddr_in(@peer)[1] rescue nil
|
135
|
+
end
|
136
|
+
|
137
|
+
def receive_data(data)
|
138
|
+
begin
|
139
|
+
@p << data
|
140
|
+
rescue HTTP::Parser::Error => e
|
141
|
+
c = @clients.shift
|
142
|
+
c.nil? ? unbind(e.message) : c.on_error(e.message)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def connection_completed
|
147
|
+
@peer = @conn.get_peername
|
148
|
+
|
149
|
+
if @connopts.proxy && @connopts.proxy[:type] == :socks5
|
150
|
+
socksify(client.req.uri.host, client.req.uri.port, *@connopts.proxy[:authorization]) { start }
|
151
|
+
else
|
152
|
+
start
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def start
|
157
|
+
@conn.start_tls(@connopts.tls) if client && client.req.ssl?
|
158
|
+
@conn.succeed
|
159
|
+
end
|
160
|
+
|
161
|
+
def redirect(client)
|
162
|
+
@pending.push client
|
163
|
+
end
|
164
|
+
|
165
|
+
def unbind(reason)
|
166
|
+
@clients.map { |c| c.unbind(reason) }
|
167
|
+
|
168
|
+
if r = @pending.shift
|
169
|
+
@clients.push r
|
170
|
+
|
171
|
+
r.reset!
|
172
|
+
@p.reset!
|
173
|
+
|
174
|
+
begin
|
175
|
+
@conn.set_deferred_status :unknown
|
176
|
+
|
177
|
+
if @connopts.proxy
|
178
|
+
@conn.reconnect(@connopts.host, @connopts.port)
|
179
|
+
else
|
180
|
+
@conn.reconnect(r.req.host, r.req.port)
|
181
|
+
end
|
182
|
+
|
183
|
+
@conn.callback { r.connection_completed }
|
184
|
+
rescue EventMachine::ConnectionError => e
|
185
|
+
@clients.pop.close(e.message)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
alias :close :unbind
|
190
|
+
|
191
|
+
def send_data(data)
|
192
|
+
@conn.send_data data
|
193
|
+
end
|
194
|
+
|
195
|
+
def stream_file_data(filename, args = {})
|
196
|
+
@conn.stream_file_data filename, args
|
197
|
+
end
|
198
|
+
|
199
|
+
private
|
200
|
+
|
201
|
+
def client
|
202
|
+
@clients.first
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
data/lib/em-http/version.rb
CHANGED
data/spec/client_fiber_spec.rb
CHANGED
@@ -3,19 +3,21 @@ require 'fiber'
|
|
3
3
|
|
4
4
|
describe EventMachine::HttpRequest do
|
5
5
|
context "with fibers" do
|
6
|
-
|
6
|
+
|
7
|
+
it "should be transparent to connection errors" do
|
7
8
|
EventMachine.run do
|
8
9
|
Fiber.new do
|
9
10
|
f = Fiber.current
|
10
|
-
|
11
|
-
http.
|
12
|
-
http.
|
13
|
-
|
14
|
-
|
11
|
+
fired = false
|
12
|
+
http = EventMachine::HttpRequest.new('http://non-existing.domain/', :connection_timeout => 0.1).get
|
13
|
+
http.callback { failed(http) }
|
14
|
+
http.errback { f.resume :errback }
|
15
|
+
|
16
|
+
Fiber.yield.should == :errback
|
17
|
+
EM.stop
|
15
18
|
end.resume
|
16
19
|
end
|
17
20
|
end
|
21
|
+
|
18
22
|
end
|
19
23
|
end
|
20
|
-
|
21
|
-
|