em-http-request 0.2.0
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/.gitignore +4 -0
- data/LICENSE +58 -0
- data/README.rdoc +71 -0
- data/Rakefile +103 -0
- data/VERSION +1 -0
- data/examples/fetch.rb +30 -0
- data/examples/fibered-http.rb +39 -0
- data/ext/buffer/em_buffer.c +630 -0
- data/ext/buffer/extconf.rb +53 -0
- data/ext/http11_client/ext_help.h +14 -0
- data/ext/http11_client/extconf.rb +6 -0
- data/ext/http11_client/http11_client.c +302 -0
- data/ext/http11_client/http11_parser.c +418 -0
- data/ext/http11_client/http11_parser.h +48 -0
- data/ext/http11_client/http11_parser.rl +170 -0
- data/lib/em-http.rb +18 -0
- data/lib/em-http/client.rb +504 -0
- data/lib/em-http/core_ext/hash.rb +53 -0
- data/lib/em-http/decoders.rb +122 -0
- data/lib/em-http/multi.rb +51 -0
- data/lib/em-http/request.rb +81 -0
- data/test/helper.rb +5 -0
- data/test/stallion.rb +144 -0
- data/test/stub_server.rb +22 -0
- data/test/test_hash.rb +19 -0
- data/test/test_multi.rb +29 -0
- data/test/test_request.rb +393 -0
- metadata +108 -0
data/test/stub_server.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
class StubServer
|
2
|
+
module Server
|
3
|
+
def receive_data(data)
|
4
|
+
send_data @response
|
5
|
+
close_connection_after_writing
|
6
|
+
end
|
7
|
+
|
8
|
+
def response=(response)
|
9
|
+
@response = response
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(response, port=8081)
|
14
|
+
@sig = EventMachine::start_server("127.0.0.1", port, Server) { |s|
|
15
|
+
s.response = response
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def stop
|
20
|
+
EventMachine.stop_server @sig
|
21
|
+
end
|
22
|
+
end
|
data/test/test_hash.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'test/helper'
|
2
|
+
|
3
|
+
describe Hash do
|
4
|
+
|
5
|
+
describe ".to_params" do
|
6
|
+
it "should transform a basic hash into HTTP POST Params" do
|
7
|
+
{:a => "alpha", :b => "beta"}.to_params.should == "a=alpha&b=beta"
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should transform a more complex hash into HTTP POST Params" do
|
11
|
+
{:a => "a", :b => ["c", "d", "e"]}.to_params.should == "a=a&b[0]=c&b[1]=d&b[2]=e"
|
12
|
+
end
|
13
|
+
|
14
|
+
# Ruby 1.8 Hash is not sorted, so this test breaks randomly. Maybe once we're all on 1.9. ;-)
|
15
|
+
# it "should transform a very complex hash into HTTP POST Params" do
|
16
|
+
# {:a => "a", :b => [{:c => "c", :d => "d"}, {:e => "e", :f => "f"}]}.to_params.should == "a=a&b[0][d]=d&b[0][c]=c&b[1][f]=f&b[1][e]=e"
|
17
|
+
# end
|
18
|
+
end
|
19
|
+
end
|
data/test/test_multi.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'test/helper'
|
2
|
+
require 'test/stallion'
|
3
|
+
|
4
|
+
describe EventMachine::MultiRequest do
|
5
|
+
|
6
|
+
it "should submit multiple requests in parallel and return once all of them are complete" do
|
7
|
+
EventMachine.run {
|
8
|
+
|
9
|
+
# create an instance of multi-request handler, and the requests themselves
|
10
|
+
multi = EventMachine::MultiRequest.new
|
11
|
+
|
12
|
+
# add multiple requests to the multi-handler
|
13
|
+
multi.add(EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get(:query => {:q => 'test'}))
|
14
|
+
multi.add(EventMachine::HttpRequest.new('http://0.0.0.0/').get(:timeout => 1))
|
15
|
+
|
16
|
+
multi.callback {
|
17
|
+
# verify successfull request
|
18
|
+
multi.responses[:succeeded].size.should == 1
|
19
|
+
multi.responses[:succeeded].first.response.should match(/test/)
|
20
|
+
|
21
|
+
# verify invalid requests
|
22
|
+
multi.responses[:failed].size.should == 1
|
23
|
+
multi.responses[:failed].first.response_header.status.should == 0
|
24
|
+
|
25
|
+
EventMachine.stop
|
26
|
+
}
|
27
|
+
}
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,393 @@
|
|
1
|
+
require 'test/helper'
|
2
|
+
require 'test/stallion'
|
3
|
+
require 'test/stub_server'
|
4
|
+
|
5
|
+
describe EventMachine::HttpRequest do
|
6
|
+
|
7
|
+
def failed
|
8
|
+
EventMachine.stop
|
9
|
+
fail
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should fail GET on DNS timeout" do
|
13
|
+
EventMachine.run {
|
14
|
+
http = EventMachine::HttpRequest.new('http://127.1.1.1/').get :timeout => 1
|
15
|
+
http.callback { failed }
|
16
|
+
http.errback {
|
17
|
+
http.response_header.status.should == 0
|
18
|
+
EventMachine.stop
|
19
|
+
}
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should fail GET on invalid host" do
|
24
|
+
EventMachine.run {
|
25
|
+
http = EventMachine::HttpRequest.new('http://somethinglocal/').get :timeout => 1
|
26
|
+
http.callback { failed }
|
27
|
+
http.errback {
|
28
|
+
http.response_header.status.should == 0
|
29
|
+
http.errors.should match(/no connection/)
|
30
|
+
EventMachine.stop
|
31
|
+
}
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should fail GET on missing path" do
|
36
|
+
EventMachine.run {
|
37
|
+
lambda {
|
38
|
+
EventMachine::HttpRequest.new('http://www.google.com').get
|
39
|
+
}.should raise_error(ArgumentError)
|
40
|
+
|
41
|
+
EventMachine.stop
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should perform successfull GET" do
|
46
|
+
EventMachine.run {
|
47
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get
|
48
|
+
|
49
|
+
http.errback { failed }
|
50
|
+
http.callback {
|
51
|
+
http.response_header.status.should == 200
|
52
|
+
http.response.should match(/Hello/)
|
53
|
+
EventMachine.stop
|
54
|
+
}
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should perform successfull GET with a URI passed as argument" do
|
59
|
+
EventMachine.run {
|
60
|
+
uri = URI.parse('http://127.0.0.1:8080/')
|
61
|
+
http = EventMachine::HttpRequest.new(uri).get
|
62
|
+
|
63
|
+
http.errback { failed }
|
64
|
+
http.callback {
|
65
|
+
http.response_header.status.should == 200
|
66
|
+
http.response.should match(/Hello/)
|
67
|
+
EventMachine.stop
|
68
|
+
}
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should perform successfull HEAD with a URI passed as argument" do
|
73
|
+
EventMachine.run {
|
74
|
+
uri = URI.parse('http://127.0.0.1:8080/')
|
75
|
+
http = EventMachine::HttpRequest.new(uri).head
|
76
|
+
|
77
|
+
http.errback { failed }
|
78
|
+
http.callback {
|
79
|
+
http.response_header.status.should == 200
|
80
|
+
http.response.should == ""
|
81
|
+
EventMachine.stop
|
82
|
+
}
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should return 404 on invalid path" do
|
87
|
+
EventMachine.run {
|
88
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/fail').get
|
89
|
+
|
90
|
+
http.errback { failed }
|
91
|
+
http.callback {
|
92
|
+
http.response_header.status.should == 404
|
93
|
+
EventMachine.stop
|
94
|
+
}
|
95
|
+
}
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should build query parameters from Hash" do
|
99
|
+
EventMachine.run {
|
100
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get :query => {:q => 'test'}
|
101
|
+
|
102
|
+
http.errback { failed }
|
103
|
+
http.callback {
|
104
|
+
http.response_header.status.should == 200
|
105
|
+
http.response.should match(/test/)
|
106
|
+
EventMachine.stop
|
107
|
+
}
|
108
|
+
}
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should pass query parameters string" do
|
112
|
+
EventMachine.run {
|
113
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get :query => "q=test"
|
114
|
+
|
115
|
+
http.errback { failed }
|
116
|
+
http.callback {
|
117
|
+
http.response_header.status.should == 200
|
118
|
+
http.response.should match(/test/)
|
119
|
+
EventMachine.stop
|
120
|
+
}
|
121
|
+
}
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should encode an array of query parameters" do
|
125
|
+
EventMachine.run {
|
126
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/echo_query').get :query => {:hash => ['value1', 'value2']}
|
127
|
+
|
128
|
+
http.errback { failed }
|
129
|
+
http.callback {
|
130
|
+
http.response_header.status.should == 200
|
131
|
+
http.response.should match(/hash\[\]=value1&hash\[\]=value2/)
|
132
|
+
EventMachine.stop
|
133
|
+
}
|
134
|
+
}
|
135
|
+
end
|
136
|
+
|
137
|
+
it "should perform successfull POST" do
|
138
|
+
EventMachine.run {
|
139
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').post :body => "data"
|
140
|
+
|
141
|
+
http.errback { failed }
|
142
|
+
http.callback {
|
143
|
+
http.response_header.status.should == 200
|
144
|
+
http.response.should match(/data/)
|
145
|
+
EventMachine.stop
|
146
|
+
}
|
147
|
+
}
|
148
|
+
end
|
149
|
+
|
150
|
+
it "should perform successfull POST with Ruby Hash/Array as params" do
|
151
|
+
EventMachine.run {
|
152
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').post :body => {"key1" => 1, "key2" => [2,3]}
|
153
|
+
|
154
|
+
http.errback { failed }
|
155
|
+
http.callback {
|
156
|
+
http.response_header.status.should == 200
|
157
|
+
|
158
|
+
http.response.should match(/key1=1&key2\[0\]=2&key2\[1\]=3/)
|
159
|
+
EventMachine.stop
|
160
|
+
}
|
161
|
+
}
|
162
|
+
end
|
163
|
+
|
164
|
+
it "should perform successfull POST with Ruby Hash/Array as params and with the correct content length" do
|
165
|
+
EventMachine.run {
|
166
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/echo_content_length').post :body => {"key1" => "data1"}
|
167
|
+
|
168
|
+
http.errback { failed }
|
169
|
+
http.callback {
|
170
|
+
http.response_header.status.should == 200
|
171
|
+
|
172
|
+
http.response.to_i.should == 10
|
173
|
+
EventMachine.stop
|
174
|
+
}
|
175
|
+
}
|
176
|
+
end
|
177
|
+
|
178
|
+
it "should perform successfull GET with custom header" do
|
179
|
+
EventMachine.run {
|
180
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get :head => {'if-none-match' => 'evar!'}
|
181
|
+
|
182
|
+
http.errback { failed }
|
183
|
+
http.callback {
|
184
|
+
http.response_header.status.should == 304
|
185
|
+
EventMachine.stop
|
186
|
+
}
|
187
|
+
}
|
188
|
+
end
|
189
|
+
|
190
|
+
it "should perform a streaming GET" do
|
191
|
+
EventMachine.run {
|
192
|
+
|
193
|
+
# digg.com uses chunked encoding
|
194
|
+
http = EventMachine::HttpRequest.new('http://digg.com/').get
|
195
|
+
|
196
|
+
http.errback { failed }
|
197
|
+
http.callback {
|
198
|
+
http.response_header.status.should == 200
|
199
|
+
EventMachine.stop
|
200
|
+
}
|
201
|
+
}
|
202
|
+
end
|
203
|
+
|
204
|
+
it "should perform basic auth" do
|
205
|
+
EventMachine.run {
|
206
|
+
|
207
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get :head => {'authorization' => ['user', 'pass']}
|
208
|
+
|
209
|
+
http.errback { failed }
|
210
|
+
http.callback {
|
211
|
+
http.response_header.status.should == 200
|
212
|
+
EventMachine.stop
|
213
|
+
}
|
214
|
+
}
|
215
|
+
end
|
216
|
+
|
217
|
+
it "should work with keep-alive servers" do
|
218
|
+
EventMachine.run {
|
219
|
+
|
220
|
+
http = EventMachine::HttpRequest.new('http://mexicodiario.com/touch.public.json.php').get
|
221
|
+
|
222
|
+
http.errback { failed }
|
223
|
+
http.callback {
|
224
|
+
http.response_header.status.should == 200
|
225
|
+
EventMachine.stop
|
226
|
+
}
|
227
|
+
}
|
228
|
+
end
|
229
|
+
|
230
|
+
it "should detect deflate encoding" do
|
231
|
+
EventMachine.run {
|
232
|
+
|
233
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/deflate').get :head => {"accept-encoding" => "deflate"}
|
234
|
+
|
235
|
+
http.errback { failed }
|
236
|
+
http.callback {
|
237
|
+
http.response_header.status.should == 200
|
238
|
+
http.response_header["CONTENT_ENCODING"].should == "deflate"
|
239
|
+
http.response.should == "compressed"
|
240
|
+
|
241
|
+
EventMachine.stop
|
242
|
+
}
|
243
|
+
}
|
244
|
+
end
|
245
|
+
|
246
|
+
it "should detect gzip encoding" do
|
247
|
+
EventMachine.run {
|
248
|
+
|
249
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/gzip').get :head => {"accept-encoding" => "gzip, compressed"}
|
250
|
+
|
251
|
+
http.errback { failed }
|
252
|
+
http.callback {
|
253
|
+
http.response_header.status.should == 200
|
254
|
+
http.response_header["CONTENT_ENCODING"].should == "gzip"
|
255
|
+
http.response.should == "compressed"
|
256
|
+
|
257
|
+
EventMachine.stop
|
258
|
+
}
|
259
|
+
}
|
260
|
+
end
|
261
|
+
|
262
|
+
it "should timeout after 10 seconds" do
|
263
|
+
EventMachine.run {
|
264
|
+
t = Time.now.to_i
|
265
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/timeout').get :timeout => 1
|
266
|
+
|
267
|
+
http.errback {
|
268
|
+
(Time.now.to_i - t).should >= 2
|
269
|
+
EventMachine.stop
|
270
|
+
}
|
271
|
+
http.callback { failed }
|
272
|
+
}
|
273
|
+
end
|
274
|
+
|
275
|
+
it "should optionally pass the response body progressively" do
|
276
|
+
EventMachine.run {
|
277
|
+
body = ''
|
278
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get
|
279
|
+
|
280
|
+
http.errback { failed }
|
281
|
+
http.stream { |chunk| body += chunk }
|
282
|
+
|
283
|
+
http.callback {
|
284
|
+
http.response_header.status.should == 200
|
285
|
+
http.response.should == ''
|
286
|
+
body.should match(/Hello/)
|
287
|
+
EventMachine.stop
|
288
|
+
}
|
289
|
+
}
|
290
|
+
end
|
291
|
+
|
292
|
+
it "should optionally pass the deflate-encoded response body progressively" do
|
293
|
+
EventMachine.run {
|
294
|
+
body = ''
|
295
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/deflate').get :head => {"accept-encoding" => "deflate, compressed"}
|
296
|
+
|
297
|
+
http.errback { failed }
|
298
|
+
http.stream { |chunk| body += chunk }
|
299
|
+
|
300
|
+
http.callback {
|
301
|
+
http.response_header.status.should == 200
|
302
|
+
http.response_header["CONTENT_ENCODING"].should == "deflate"
|
303
|
+
http.response.should == ''
|
304
|
+
body.should == "compressed"
|
305
|
+
EventMachine.stop
|
306
|
+
}
|
307
|
+
}
|
308
|
+
end
|
309
|
+
|
310
|
+
it "should initiate SSL/TLS on HTTPS connections" do
|
311
|
+
EventMachine.run {
|
312
|
+
http = EventMachine::HttpRequest.new('https://mail.google.com:443/mail/').get
|
313
|
+
|
314
|
+
http.errback { failed }
|
315
|
+
http.callback {
|
316
|
+
http.response_header.status.should == 302
|
317
|
+
EventMachine.stop
|
318
|
+
}
|
319
|
+
}
|
320
|
+
end
|
321
|
+
|
322
|
+
it "should accept & return cookie header to user" do
|
323
|
+
EventMachine.run {
|
324
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/set_cookie').get
|
325
|
+
|
326
|
+
http.errback { failed }
|
327
|
+
http.callback {
|
328
|
+
http.response_header.status.should == 200
|
329
|
+
http.response_header.cookie.should == "id=1; expires=Tue, 09-Aug-2011 17:53:39 GMT; path=/;"
|
330
|
+
EventMachine.stop
|
331
|
+
}
|
332
|
+
}
|
333
|
+
end
|
334
|
+
|
335
|
+
it "should pass cookie header to server from string" do
|
336
|
+
EventMachine.run {
|
337
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/echo_cookie').get :head => {'cookie' => 'id=2;'}
|
338
|
+
|
339
|
+
http.errback { failed }
|
340
|
+
http.callback {
|
341
|
+
http.response.should == "id=2;"
|
342
|
+
EventMachine.stop
|
343
|
+
}
|
344
|
+
}
|
345
|
+
end
|
346
|
+
|
347
|
+
it "should pass cookie header to server from Hash" do
|
348
|
+
EventMachine.run {
|
349
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/echo_cookie').get :head => {'cookie' => {'id' => 2}}
|
350
|
+
|
351
|
+
http.errback { failed }
|
352
|
+
http.callback {
|
353
|
+
http.response.should == "id=2;"
|
354
|
+
EventMachine.stop
|
355
|
+
}
|
356
|
+
}
|
357
|
+
end
|
358
|
+
|
359
|
+
context "when talking to a stub HTTP/1.0 server" do
|
360
|
+
it "should get the body without Content-Length" do
|
361
|
+
EventMachine.run {
|
362
|
+
@s = StubServer.new("HTTP/1.0 200 OK\r\nConnection: close\r\n\r\nFoo")
|
363
|
+
|
364
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8081/').get
|
365
|
+
http.errback { failed }
|
366
|
+
http.callback {
|
367
|
+
http.response.should match(/Foo/)
|
368
|
+
http.response_header['CONTENT_LENGTH'].should_not == 0
|
369
|
+
|
370
|
+
@s.stop
|
371
|
+
EventMachine.stop
|
372
|
+
}
|
373
|
+
}
|
374
|
+
end
|
375
|
+
|
376
|
+
it "should work with \\n instead of \\r\\n" do
|
377
|
+
EventMachine.run {
|
378
|
+
@s = StubServer.new("HTTP/1.0 200 OK\nContent-Type: text/plain\nContent-Length: 3\nConnection: close\n\nFoo")
|
379
|
+
|
380
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8081/').get
|
381
|
+
http.errback { failed }
|
382
|
+
http.callback {
|
383
|
+
http.response_header.status.should == 200
|
384
|
+
http.response_header['CONTENT_TYPE'].should == 'text/plain'
|
385
|
+
http.response.should match(/Foo/)
|
386
|
+
|
387
|
+
@s.stop
|
388
|
+
EventMachine.stop
|
389
|
+
}
|
390
|
+
}
|
391
|
+
end
|
392
|
+
end
|
393
|
+
end
|