em-http-request 1.0.1 → 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
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
|
-
|