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.
@@ -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
- body = ''
65
- on_body = lambda { |chunk| body += chunk }
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
@@ -34,6 +34,10 @@ task :ragel do
34
34
  end
35
35
  end
36
36
 
37
+ task :spec do
38
+ sh 'spec test/test_*.rb'
39
+ end
40
+
37
41
  def make(makedir)
38
42
  Dir.chdir(makedir) { sh MAKE }
39
43
  end
@@ -1,6 +1,6 @@
1
1
  spec = Gem::Specification.new do |s|
2
2
  s.name = 'em-http-request'
3
- s.version = '0.1.7'
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/hash.rb",
37
+ "test/test_hash.rb",
38
38
  "test/helper.rb",
39
39
  "test/stallion.rb",
40
40
  "test/test_multi.rb",
@@ -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
- return path unless query
98
- if query.kind_of? String
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
- return path + "?" + query.map { |k, v| encode_param(k, v) }.join('&')
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 encode_cookies(cookies)
138
- cookies.inject('') { |result, (k, v)| result << encode_field('Cookie', encode_param(k, v)) }
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
- unbind
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 (on_response = @options[:on_response])
258
- on_response.call(data)
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
- (@state == :finished) ? succeed(self) : fail
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
- @state = :body
327
- @bytes_remaining = @response_header.content_length
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]) &&
@@ -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
@@ -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
- Stallion.run :Host => '127.0.0.1', :Port => 8080
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(2)
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
- it "should transform a very complex hash into HTTP POST Params" do
14
- {: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][e]=e&b[1][f]=f"
15
- 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
16
18
  end
17
19
  end
@@ -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://169.169.169.169/').get)
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
@@ -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 => 2
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 == 2
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
- on_body = lambda { |chunk| body += chunk }
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
- on_body = lambda { |chunk| body += chunk }
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
- end
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.7
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/hash.rb
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.2.0
83
+ rubygems_version: 1.3.5
83
84
  signing_key:
84
85
  specification_version: 2
85
86
  summary: EventMachine based HTTP Request interface