igrigorik-em-http-request 0.1.7 → 0.1.8

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.
@@ -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