igrigorik-em-http-request 0.1.7 → 0.1.8
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +5 -3
- data/Rakefile +4 -0
- data/em-http-request.gemspec +2 -2
- data/lib/em-http/client.rb +67 -16
- data/lib/em-http/request.rb +3 -2
- data/test/stallion.rb +16 -2
- data/test/{hash.rb → test_hash.rb} +6 -4
- data/test/test_multi.rb +1 -6
- data/test/test_request.rb +63 -11
- metadata +4 -3
data/README.rdoc
CHANGED
@@ -6,6 +6,9 @@ EventMachine based HTTP Request interface. Supports streaming response processin
|
|
6
6
|
- Basic-Auth support
|
7
7
|
- Custom timeouts
|
8
8
|
|
9
|
+
Screencast / Demo of using EM-HTTP-Request:
|
10
|
+
- http://everburning.com/news/eventmachine-screencast-em-http-request/
|
11
|
+
|
9
12
|
== Simple client example
|
10
13
|
|
11
14
|
EventMachine.run {
|
@@ -61,9 +64,8 @@ EventMachine based HTTP Request interface. Supports streaming response processin
|
|
61
64
|
|
62
65
|
== Streaming body processing
|
63
66
|
EventMachine.run {
|
64
|
-
|
65
|
-
|
66
|
-
http = EventMachine::HttpRequest.new('http://www.website.com/').get :on_response => on_body
|
67
|
+
http = EventMachine::HttpRequest.new('http://www.website.com/').get
|
68
|
+
http.stream { |chunk| print chunk }
|
67
69
|
|
68
70
|
# ...
|
69
71
|
}
|
data/Rakefile
CHANGED
data/em-http-request.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
spec = Gem::Specification.new do |s|
|
2
2
|
s.name = 'em-http-request'
|
3
|
-
s.version = '0.1.
|
3
|
+
s.version = '0.1.8'
|
4
4
|
s.date = '2009-03-20'
|
5
5
|
s.summary = 'EventMachine based HTTP Request interface'
|
6
6
|
s.description = s.summary
|
@@ -34,7 +34,7 @@ spec = Gem::Specification.new do |s|
|
|
34
34
|
"lib/em-http/decoders.rb",
|
35
35
|
"lib/em-http/multi.rb",
|
36
36
|
"lib/em-http/request.rb",
|
37
|
-
"test/
|
37
|
+
"test/test_hash.rb",
|
38
38
|
"test/helper.rb",
|
39
39
|
"test/stallion.rb",
|
40
40
|
"test/test_multi.rb",
|
data/lib/em-http/client.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
# -*- coding: undecided -*-
|
2
1
|
# #--
|
3
2
|
# Copyright (C)2008 Ilya Grigorik
|
4
3
|
#
|
@@ -21,6 +20,16 @@ module EventMachine
|
|
21
20
|
|
22
21
|
# The status code (as a string!)
|
23
22
|
attr_accessor :http_status
|
23
|
+
|
24
|
+
# E-Tag
|
25
|
+
def etag
|
26
|
+
self["ETag"]
|
27
|
+
end
|
28
|
+
|
29
|
+
def last_modified
|
30
|
+
time = self["Last-Modified"]
|
31
|
+
Time.parse(time) if time
|
32
|
+
end
|
24
33
|
|
25
34
|
# HTTP response status as an integer
|
26
35
|
def status
|
@@ -32,6 +41,11 @@ module EventMachine
|
|
32
41
|
Integer(self[HttpClient::CONTENT_LENGTH]) rescue nil
|
33
42
|
end
|
34
43
|
|
44
|
+
# Cookie header from the server
|
45
|
+
def cookie
|
46
|
+
self[HttpClient::SET_COOKIE]
|
47
|
+
end
|
48
|
+
|
35
49
|
# Is the transfer encoding chunked?
|
36
50
|
def chunked_encoding?
|
37
51
|
/chunked/i === self[HttpClient::TRANSFER_ENCODING]
|
@@ -94,12 +108,16 @@ module EventMachine
|
|
94
108
|
end
|
95
109
|
|
96
110
|
def encode_query(path, query)
|
97
|
-
|
98
|
-
|
99
|
-
return "#{path}?#{query}"
|
111
|
+
encoded_query = if query.kind_of?(Hash)
|
112
|
+
query.map { |k, v| encode_param(k, v) }.join('&')
|
100
113
|
else
|
101
|
-
|
114
|
+
query.to_s
|
115
|
+
end
|
116
|
+
if !@uri.query.to_s.empty?
|
117
|
+
encoded_query = [encoded_query, @uri.query].reject {|part| part.empty?}.join("&")
|
102
118
|
end
|
119
|
+
return path if encoded_query.to_s.empty?
|
120
|
+
"#{path}?#{encoded_query}"
|
103
121
|
end
|
104
122
|
|
105
123
|
# URL encodes query parameters:
|
@@ -134,8 +152,12 @@ module EventMachine
|
|
134
152
|
end
|
135
153
|
end
|
136
154
|
|
137
|
-
def
|
138
|
-
|
155
|
+
def encode_cookie(cookie)
|
156
|
+
if cookie.is_a? Hash
|
157
|
+
cookie.inject('') { |result, (k, v)| result << encode_param(k, v) + ";" }
|
158
|
+
else
|
159
|
+
cookie
|
160
|
+
end
|
139
161
|
end
|
140
162
|
end
|
141
163
|
|
@@ -167,6 +189,7 @@ module EventMachine
|
|
167
189
|
@inflate = []
|
168
190
|
@errors = ''
|
169
191
|
@content_decoder = nil
|
192
|
+
@stream = nil
|
170
193
|
end
|
171
194
|
|
172
195
|
# start HTTP request once we establish connection to host
|
@@ -177,7 +200,7 @@ module EventMachine
|
|
177
200
|
send_request_header
|
178
201
|
send_request_body
|
179
202
|
end
|
180
|
-
|
203
|
+
|
181
204
|
# request is done, invoke the callback
|
182
205
|
def on_request_complete
|
183
206
|
begin
|
@@ -187,11 +210,19 @@ module EventMachine
|
|
187
210
|
end
|
188
211
|
unbind
|
189
212
|
end
|
190
|
-
|
213
|
+
|
191
214
|
# request failed, invoke errback
|
192
|
-
def on_error(msg)
|
215
|
+
def on_error(msg, dns_error = false)
|
193
216
|
@errors = msg
|
194
|
-
|
217
|
+
|
218
|
+
# no connection signature on DNS failures
|
219
|
+
# fail the connection directly
|
220
|
+
dns_error == true ? fail : unbind
|
221
|
+
end
|
222
|
+
|
223
|
+
# assign a stream processing block
|
224
|
+
def stream(&blk)
|
225
|
+
@stream = blk
|
195
226
|
end
|
196
227
|
|
197
228
|
def normalize_body
|
@@ -221,6 +252,11 @@ module EventMachine
|
|
221
252
|
@inflate = head['accept-encoding'].split(',').map {|t| t.strip}
|
222
253
|
end
|
223
254
|
|
255
|
+
# Set the cookie header if provided
|
256
|
+
if cookie = head.delete('cookie')
|
257
|
+
head['cookie'] = encode_cookie(cookie)
|
258
|
+
end
|
259
|
+
|
224
260
|
# Build the request
|
225
261
|
request_header = encode_request(@method, @uri.path, query)
|
226
262
|
request_header << encode_headers(head)
|
@@ -254,15 +290,19 @@ module EventMachine
|
|
254
290
|
end
|
255
291
|
|
256
292
|
def on_decoded_body_data(data)
|
257
|
-
if
|
258
|
-
|
293
|
+
if @stream
|
294
|
+
@stream.call(data)
|
259
295
|
else
|
260
296
|
@response << data
|
261
297
|
end
|
262
298
|
end
|
263
299
|
|
264
300
|
def unbind
|
265
|
-
|
301
|
+
if @state == :finished
|
302
|
+
succeed(self)
|
303
|
+
else
|
304
|
+
fail(self)
|
305
|
+
end
|
266
306
|
close_connection
|
267
307
|
end
|
268
308
|
|
@@ -320,11 +360,22 @@ module EventMachine
|
|
320
360
|
return false
|
321
361
|
end
|
322
362
|
|
363
|
+
# shortcircuit on HEAD requests
|
364
|
+
if @method == "HEAD"
|
365
|
+
@state = :finished
|
366
|
+
on_request_complete
|
367
|
+
end
|
368
|
+
|
323
369
|
if @response_header.chunked_encoding?
|
324
370
|
@state = :chunk_header
|
325
371
|
else
|
326
|
-
@
|
327
|
-
|
372
|
+
if @response_header.content_length > 0
|
373
|
+
@state = :body
|
374
|
+
@bytes_remaining = @response_header.content_length
|
375
|
+
else
|
376
|
+
@state = :finished
|
377
|
+
on_request_complete
|
378
|
+
end
|
328
379
|
end
|
329
380
|
|
330
381
|
if @inflate.include?(response_header[CONTENT_ENCODING]) &&
|
data/lib/em-http/request.rb
CHANGED
@@ -47,13 +47,14 @@ module EventMachine
|
|
47
47
|
#
|
48
48
|
|
49
49
|
def get options = {}; send_request(:get, options); end
|
50
|
+
def head options = {}; send_request(:head, options); end
|
50
51
|
def post options = {}; send_request(:post, options); end
|
51
52
|
|
52
53
|
protected
|
53
54
|
|
54
55
|
def send_request(method, options)
|
55
56
|
raise ArgumentError, "invalid request path" unless /^\// === @uri.path
|
56
|
-
|
57
|
+
|
57
58
|
method = method.to_s.upcase
|
58
59
|
begin
|
59
60
|
EventMachine.connect(@uri.host, @uri.port, EventMachine::HttpClient) { |c|
|
@@ -65,7 +66,7 @@ module EventMachine
|
|
65
66
|
rescue RuntimeError => e
|
66
67
|
raise e unless e.message == "no connection"
|
67
68
|
conn = EventMachine::HttpClient.new("")
|
68
|
-
conn.on_error("no connection")
|
69
|
+
conn.on_error("no connection", true)
|
69
70
|
conn
|
70
71
|
end
|
71
72
|
end
|
data/test/stallion.rb
CHANGED
@@ -83,9 +83,19 @@ Stallion.saddle :spec do |stable|
|
|
83
83
|
elsif stable.request.path_info == '/echo_content_length'
|
84
84
|
stable.response.write stable.request.content_length
|
85
85
|
|
86
|
+
elsif stable.request.head?
|
87
|
+
stable.response.status = 200
|
88
|
+
|
86
89
|
elsif stable.request.post?
|
87
90
|
stable.response.write stable.request.body.read
|
88
91
|
|
92
|
+
elsif stable.request.path_info == '/set_cookie'
|
93
|
+
stable.response["Set-Cookie"] = "id=1; expires=Tue, 09-Aug-2011 17:53:39 GMT; path=/;"
|
94
|
+
stable.response.write "cookie set"
|
95
|
+
|
96
|
+
elsif stable.request.path_info == '/echo_cookie'
|
97
|
+
stable.response.write stable.request.env["HTTP_COOKIE"]
|
98
|
+
|
89
99
|
elsif stable.request.path_info == '/timeout'
|
90
100
|
sleep(10)
|
91
101
|
stable.response.write 'timeout'
|
@@ -124,7 +134,11 @@ Stallion.saddle :spec do |stable|
|
|
124
134
|
end
|
125
135
|
|
126
136
|
Thread.new do
|
127
|
-
|
137
|
+
begin
|
138
|
+
Stallion.run :Host => '127.0.0.1', :Port => 8080
|
139
|
+
rescue Exception => e
|
140
|
+
print e
|
141
|
+
end
|
128
142
|
end
|
129
143
|
|
130
|
-
sleep(
|
144
|
+
sleep(1)
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'test/helper'
|
2
2
|
|
3
3
|
describe Hash do
|
4
|
+
|
4
5
|
describe ".to_params" do
|
5
6
|
it "should transform a basic hash into HTTP POST Params" do
|
6
7
|
{:a => "alpha", :b => "beta"}.to_params.should == "a=alpha&b=beta"
|
@@ -9,9 +10,10 @@ describe Hash do
|
|
9
10
|
it "should transform a more complex hash into HTTP POST Params" do
|
10
11
|
{:a => "a", :b => ["c", "d", "e"]}.to_params.should == "a=a&b[0]=c&b[1]=d&b[2]=e"
|
11
12
|
end
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
16
18
|
end
|
17
19
|
end
|
data/test/test_multi.rb
CHANGED
@@ -3,11 +3,6 @@ require 'test/stallion'
|
|
3
3
|
|
4
4
|
describe EventMachine::MultiRequest do
|
5
5
|
|
6
|
-
def failed
|
7
|
-
EventMachine.stop
|
8
|
-
fail
|
9
|
-
end
|
10
|
-
|
11
6
|
it "should submit multiple requests in parallel and return once all of them are complete" do
|
12
7
|
EventMachine.run {
|
13
8
|
|
@@ -16,7 +11,7 @@ describe EventMachine::MultiRequest do
|
|
16
11
|
|
17
12
|
# add multiple requests to the multi-handler
|
18
13
|
multi.add(EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get(:query => {:q => 'test'}))
|
19
|
-
multi.add(EventMachine::HttpRequest.new('http://
|
14
|
+
multi.add(EventMachine::HttpRequest.new('http://0.0.0.0/').get(:timeout => 1))
|
20
15
|
|
21
16
|
multi.callback {
|
22
17
|
# verify successfull request
|
data/test/test_request.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
require 'test/helper'
|
2
2
|
require 'test/stallion'
|
3
|
-
|
3
|
+
|
4
4
|
describe EventMachine::HttpRequest do
|
5
5
|
|
6
6
|
def failed
|
7
7
|
EventMachine.stop
|
8
8
|
fail
|
9
9
|
end
|
10
|
-
|
10
|
+
|
11
11
|
it "should fail GET on DNS timeout" do
|
12
12
|
EventMachine.run {
|
13
13
|
http = EventMachine::HttpRequest.new('http://127.1.1.1/').get
|
@@ -68,12 +68,26 @@ describe EventMachine::HttpRequest do
|
|
68
68
|
}
|
69
69
|
end
|
70
70
|
|
71
|
+
it "should perform successfull HEAD with a URI passed as argument" do
|
72
|
+
EventMachine.run {
|
73
|
+
uri = URI.parse('http://127.0.0.1:8080/')
|
74
|
+
http = EventMachine::HttpRequest.new(uri).head
|
75
|
+
|
76
|
+
http.errback { failed }
|
77
|
+
http.callback {
|
78
|
+
http.response_header.status.should == 200
|
79
|
+
http.response.should == ""
|
80
|
+
EventMachine.stop
|
81
|
+
}
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
71
85
|
it "should return 404 on invalid path" do
|
72
86
|
EventMachine.run {
|
73
87
|
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/fail').get
|
74
88
|
|
75
89
|
http.errback { failed }
|
76
|
-
http.callback {
|
90
|
+
http.callback {
|
77
91
|
http.response_header.status.should == 404
|
78
92
|
EventMachine.stop
|
79
93
|
}
|
@@ -247,10 +261,10 @@ describe EventMachine::HttpRequest do
|
|
247
261
|
it "should timeout after 10 seconds" do
|
248
262
|
EventMachine.run {
|
249
263
|
t = Time.now.to_i
|
250
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/timeout').get :timeout =>
|
264
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/timeout').get :timeout => 1
|
251
265
|
|
252
266
|
http.errback {
|
253
|
-
(Time.now.to_i - t).should
|
267
|
+
(Time.now.to_i - t).should >= 2
|
254
268
|
EventMachine.stop
|
255
269
|
}
|
256
270
|
http.callback { failed }
|
@@ -260,10 +274,11 @@ describe EventMachine::HttpRequest do
|
|
260
274
|
it "should optionally pass the response body progressively" do
|
261
275
|
EventMachine.run {
|
262
276
|
body = ''
|
263
|
-
|
264
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get :on_response => on_body
|
277
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get
|
265
278
|
|
266
279
|
http.errback { failed }
|
280
|
+
http.stream { |chunk| body += chunk }
|
281
|
+
|
267
282
|
http.callback {
|
268
283
|
http.response_header.status.should == 200
|
269
284
|
http.response.should == ''
|
@@ -276,11 +291,11 @@ describe EventMachine::HttpRequest do
|
|
276
291
|
it "should optionally pass the deflate-encoded response body progressively" do
|
277
292
|
EventMachine.run {
|
278
293
|
body = ''
|
279
|
-
|
280
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/deflate').get :head => {"accept-encoding" => "deflate, compressed"},
|
281
|
-
:on_response => on_body
|
294
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/deflate').get :head => {"accept-encoding" => "deflate, compressed"}
|
282
295
|
|
283
296
|
http.errback { failed }
|
297
|
+
http.stream { |chunk| body += chunk }
|
298
|
+
|
284
299
|
http.callback {
|
285
300
|
http.response_header.status.should == 200
|
286
301
|
http.response_header["CONTENT_ENCODING"].should == "deflate"
|
@@ -303,4 +318,41 @@ describe EventMachine::HttpRequest do
|
|
303
318
|
}
|
304
319
|
end
|
305
320
|
|
306
|
-
|
321
|
+
it "should accept & return cookie header to user" do
|
322
|
+
EventMachine.run {
|
323
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/set_cookie').get
|
324
|
+
|
325
|
+
http.errback { failed }
|
326
|
+
http.callback {
|
327
|
+
http.response_header.status.should == 200
|
328
|
+
http.response_header.cookie.should == "id=1; expires=Tue, 09-Aug-2011 17:53:39 GMT; path=/;"
|
329
|
+
EventMachine.stop
|
330
|
+
}
|
331
|
+
}
|
332
|
+
end
|
333
|
+
|
334
|
+
it "should pass cookie header to server from string" do
|
335
|
+
EventMachine.run {
|
336
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/echo_cookie').get :head => {'cookie' => 'id=2;'}
|
337
|
+
|
338
|
+
http.errback { failed }
|
339
|
+
http.callback {
|
340
|
+
http.response.should == "id=2;"
|
341
|
+
EventMachine.stop
|
342
|
+
}
|
343
|
+
}
|
344
|
+
end
|
345
|
+
|
346
|
+
it "should pass cookie header to server from Hash" do
|
347
|
+
EventMachine.run {
|
348
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/echo_cookie').get :head => {'cookie' => {'id' => 2}}
|
349
|
+
|
350
|
+
http.errback { failed }
|
351
|
+
http.callback {
|
352
|
+
http.response.should == "id=2;"
|
353
|
+
EventMachine.stop
|
354
|
+
}
|
355
|
+
}
|
356
|
+
end
|
357
|
+
|
358
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: igrigorik-em-http-request
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ilya Grigorik
|
@@ -52,13 +52,14 @@ files:
|
|
52
52
|
- lib/em-http/decoders.rb
|
53
53
|
- lib/em-http/multi.rb
|
54
54
|
- lib/em-http/request.rb
|
55
|
-
- test/
|
55
|
+
- test/test_hash.rb
|
56
56
|
- test/helper.rb
|
57
57
|
- test/stallion.rb
|
58
58
|
- test/test_multi.rb
|
59
59
|
- test/test_request.rb
|
60
60
|
has_rdoc: true
|
61
61
|
homepage: http://github.com/igrigorik/em-http-request
|
62
|
+
licenses:
|
62
63
|
post_install_message:
|
63
64
|
rdoc_options: []
|
64
65
|
|
@@ -79,7 +80,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
79
80
|
requirements: []
|
80
81
|
|
81
82
|
rubyforge_project: em-http-request
|
82
|
-
rubygems_version: 1.
|
83
|
+
rubygems_version: 1.3.5
|
83
84
|
signing_key:
|
84
85
|
specification_version: 2
|
85
86
|
summary: EventMachine based HTTP Request interface
|