em-http-request 0.2.5 → 0.2.6

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/README.rdoc CHANGED
@@ -3,8 +3,10 @@
3
3
  EventMachine based HTTP Request interface. Supports streaming response processing, uses Ragel HTTP parser.
4
4
  - Simple interface for single & parallel requests via deferred callbacks
5
5
  - Automatic gzip & deflate decoding
6
- - Basic-Auth support
7
- - Custom timeouts
6
+ - Basic-Auth & OAuth support
7
+ - Custom timeouts
8
+ - Proxy support (with SSL Tunneling)
9
+ - Bi-directional communication with web-socket services
8
10
 
9
11
  Screencast / Demo of using EM-HTTP-Request:
10
12
  - http://everburning.com/news/eventmachine-screencast-em-http-request/
@@ -35,6 +37,7 @@ Screencast / Demo of using EM-HTTP-Request:
35
37
  }
36
38
 
37
39
  == Multi request example
40
+ Fire and wait for multiple requess to complete via the MultiRequest interface.
38
41
 
39
42
  EventMachine.run {
40
43
  multi = EventMachine::MultiRequest.new
@@ -52,6 +55,7 @@ Screencast / Demo of using EM-HTTP-Request:
52
55
  }
53
56
 
54
57
  == Basic-Auth example
58
+ Full basic author support. For OAuth, check examples/oauth-tweet.rb file.
55
59
 
56
60
  EventMachine.run {
57
61
  http = EventMachine::HttpRequest.new('http://www.website.com/').get :head => {'authorization' => ['user', 'pass']}
@@ -63,17 +67,6 @@ Screencast / Demo of using EM-HTTP-Request:
63
67
  }
64
68
  }
65
69
 
66
- == OAuth example
67
-
68
- EventMachine.run {
69
- http = EventMachine::HttpRequest.new('http://www.website.com/').get :head => {'authorization' => 'OAuth oauth_nonce=...'}
70
- http.errback { failed }
71
- http.callback {
72
- p http.response_header
73
- EventMachine.stop
74
- }
75
- }
76
-
77
70
 
78
71
  == POST example
79
72
  EventMachine.run {
@@ -84,6 +77,9 @@ Screencast / Demo of using EM-HTTP-Request:
84
77
  }
85
78
 
86
79
  == Streaming body processing
80
+ Allows you to consume an HTTP stream of content in real-time. Each time a new piece of conent is pushed
81
+ to the client, it is passed to the stream callback for you to operate on.
82
+
87
83
  EventMachine.run {
88
84
  http = EventMachine::HttpRequest.new('http://www.website.com/').get
89
85
  http.stream { |chunk| print chunk }
@@ -92,10 +88,33 @@ Screencast / Demo of using EM-HTTP-Request:
92
88
  }
93
89
 
94
90
  == Proxy example
91
+ Full transparent proxy support with support for SSL tunneling.
92
+
95
93
  EventMachine.run {
96
- http1 = EventMachine::HttpRequest.new('http://www.website.com/').get :proxy => {
94
+ http = EventMachine::HttpRequest.new('http://www.website.com/').get :proxy => {
97
95
  :host => 'www.myproxy.com',
98
96
  :port => 8080,
99
97
  :authorization => ['username', 'password'] # authorization is optional
98
+ }
99
+
100
+ == WebSocket example
101
+ Bi-directional communication with WebSockets: simply pass in a ws:// resource and the client will
102
+ negotiate the connection upgrade for you. On successfull handshake the callback is invoked, and
103
+ any incoming messages will be passed to the stream callback. The client can also send data to the
104
+ server at will by calling the "send" method!
105
+ - http://www.igvita.com/2009/12/22/ruby-websockets-tcp-for-the-browser/
106
+
107
+ EventMachine.run {
108
+ http = EventMachine::HttpRequest.new("ws://yourservice.com/websocket").get :timeout => 0
109
+
110
+ http.errback { puts "oops" }
111
+ http.callback {
112
+ puts "WebSocket connected!"
113
+ http.send("Hello client")
100
114
  }
101
115
 
116
+ http.stream { |msg|
117
+ puts "Recieved: #{msg}"
118
+ http.send "Pong: #{msg}"
119
+ }
120
+ }
data/Rakefile CHANGED
@@ -36,7 +36,6 @@ end
36
36
 
37
37
  task :spec do
38
38
  sh 'spec test/test_*.rb'
39
- sh 'spec test/mock/test_*.rb'
40
39
  end
41
40
 
42
41
  def make(makedir)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.5
1
+ 0.2.6
@@ -0,0 +1,50 @@
1
+ # Courtesy of Darcy Laycock:
2
+ # http://gist.github.com/265261
3
+ #
4
+
5
+ require 'rubygems'
6
+
7
+ require 'em-http'
8
+ require 'oauth'
9
+
10
+ # At a minimum, require 'oauth/request_proxy/em_http_request'
11
+ # for this example, we'll use Net::HTTP like support.
12
+ require 'oauth/client/em_http'
13
+
14
+ # You need two things: an oauth consumer and an access token.
15
+ # You need to generate an access token, I suggest looking elsewhere how to do that or wait for a full tutorial.
16
+ # For a consumer key / consumer secret, signup for an app at:
17
+ # http://twitter.com/apps/new
18
+
19
+ # Edit in your details.
20
+ CONSUMER_KEY = ""
21
+ CONSUMER_SECRET = ""
22
+ ACCESS_TOKEN = ""
23
+ ACCESS_TOKEN_SECRET = ""
24
+
25
+ def twitter_oauth_consumer
26
+ @twitter_oauth_consumer ||= OAuth::Consumer.new(CONSUMER_KEY, CONSUMER_SECRET, :site => "http://twitter.com")
27
+ end
28
+
29
+ def twitter_oauth_access_token
30
+ @twitter_oauth_access_token ||= OAuth::AccessToken.new(twitter_oauth_consumer, ACCESS_TOKEN, ACCESS_TOKEN_SECRET)
31
+ end
32
+
33
+ EM.run do
34
+
35
+ request = EventMachine::HttpRequest.new('http://twitter.com/statuses/update.json')
36
+ http = request.post(:body => {'status' => 'Hello Twitter from em-http-request with OAuth'}, :head => {"Content-Type" => "application/x-www-form-urlencoded"}) do |client|
37
+ twitter_oauth_consumer.sign!(client, twitter_oauth_access_token)
38
+ end
39
+
40
+ http.callback do
41
+ puts "Response: #{http.response} (Code: #{http.response_header.status})"
42
+ EM.stop_event_loop
43
+ end
44
+
45
+ http.errback do
46
+ puts "Failed to post"
47
+ EM.stop_event_loop
48
+ end
49
+
50
+ end
@@ -27,6 +27,7 @@ void client_http_field(void *data, const char *field, size_t flen, const char *v
27
27
  VALUE req = (VALUE)data;
28
28
  VALUE v = Qnil;
29
29
  VALUE f = Qnil;
30
+ VALUE el = Qnil;
30
31
 
31
32
  v = rb_str_new(value, vlen);
32
33
  f = rb_str_new(field, flen);
@@ -41,7 +42,18 @@ void client_http_field(void *data, const char *field, size_t flen, const char *v
41
42
  }
42
43
  }
43
44
 
44
- rb_hash_aset(req, f, v);
45
+ el = rb_hash_lookup(req, f);
46
+ switch(TYPE(el)) {
47
+ case T_ARRAY:
48
+ rb_ary_push(el, v);
49
+ break;
50
+ case T_STRING:
51
+ rb_hash_aset(req, f, rb_ary_new3(2, el, v));
52
+ break;
53
+ default:
54
+ rb_hash_aset(req, f, v);
55
+ break;
56
+ }
45
57
  }
46
58
 
47
59
  void client_reason_phrase(void *data, const char *at, size_t length)
@@ -212,7 +212,7 @@ module EventMachine
212
212
  # if connecting via proxy, then state will be :proxy_connected,
213
213
  # indicating successful tunnel. from here, initiate normal http
214
214
  # exchange
215
- else
215
+ else
216
216
  @state = :response_header
217
217
 
218
218
  ssl = @options[:tls] || @options[:ssl] || {}
@@ -246,14 +246,43 @@ module EventMachine
246
246
  @stream = blk
247
247
  end
248
248
 
249
+ # raw data push from the client (WebSocket) should
250
+ # only be invoked after handshake, otherwise it will
251
+ # inject data into the header exchange
252
+ #
253
+ # frames need to start with 0x00-0x7f byte and end with
254
+ # an 0xFF byte. Per spec, we can also set the first
255
+ # byte to a value betweent 0x80 and 0xFF, followed by
256
+ # a leading length indicator
257
+ def send(data)
258
+ if @state == :websocket
259
+ send_data("\x00#{data}\xff")
260
+ end
261
+ end
262
+
249
263
  def normalize_body
250
- if @options[:body].is_a? Hash
251
- @options[:body].to_params
252
- else
253
- @options[:body]
264
+ @normalized_body ||= begin
265
+ if @options[:body].is_a? Hash
266
+ @options[:body].to_params
267
+ else
268
+ @options[:body]
269
+ end
270
+ end
271
+ end
272
+
273
+ def normalize_uri
274
+ @normalized_uri ||= begin
275
+ uri = @uri.dup
276
+ encoded_query = encode_query(@uri.path, @options[:query], @uri.query)
277
+ path, query = encoded_query.split("?", 2)
278
+ uri.query = query unless encoded_query.empty?
279
+ uri.path = path
280
+ uri
254
281
  end
255
282
  end
256
-
283
+
284
+ def websocket?; @uri.scheme == 'ws'; end
285
+
257
286
  def send_request_header
258
287
  query = @options[:query]
259
288
  head = @options[:head] ? munge_header_keys(@options[:head]) : {}
@@ -267,7 +296,12 @@ module EventMachine
267
296
  head = proxy[:head] ? munge_header_keys(proxy[:head]) : {}
268
297
  head['proxy-authorization'] = proxy[:authorization] if proxy[:authorization]
269
298
  request_header = HTTP_REQUEST_HEADER % ['CONNECT', "#{@uri.host}:#{@uri.port}"]
270
-
299
+
300
+ elsif websocket?
301
+ head['upgrade'] = 'WebSocket'
302
+ head['connection'] = 'Upgrade'
303
+ head['origin'] = @options[:origin] || @uri.host
304
+
271
305
  else
272
306
  # Set the Content-Length if body is given
273
307
  head['content-length'] = body.length if body
@@ -283,7 +317,7 @@ module EventMachine
283
317
  end
284
318
  end
285
319
 
286
- # Set the Host header if it hasn't been specified already
320
+ # Set the Host header if it hasn't been specified already
287
321
  head['host'] ||= encode_host
288
322
 
289
323
  # Set the User-Agent if it hasn't been specified
@@ -357,6 +391,8 @@ module EventMachine
357
391
  process_response_footer
358
392
  when :body
359
393
  process_body
394
+ when :websocket
395
+ process_websocket
360
396
  when :finished, :invalid
361
397
  break
362
398
  else raise RuntimeError, "invalid state: #{@state}"
@@ -435,7 +471,15 @@ module EventMachine
435
471
  on_request_complete
436
472
  end
437
473
 
438
- if @response_header.chunked_encoding?
474
+ if websocket?
475
+ if @response_header.status == 101
476
+ @state = :websocket
477
+ succeed
478
+ else
479
+ fail "websocket handshake failed"
480
+ end
481
+
482
+ elsif @response_header.chunked_encoding?
439
483
  @state = :chunk_header
440
484
  elsif @response_header.content_length
441
485
  @state = :body
@@ -554,7 +598,23 @@ module EventMachine
554
598
  false
555
599
  end
556
600
 
557
-
601
+ def process_websocket
602
+ return false if @data.empty?
603
+
604
+ # slice the message out of the buffer and pass in
605
+ # for processing, and buffer data otherwise
606
+ buffer = @data.read
607
+ while msg = buffer.slice!(/\000([^\377]*)\377/)
608
+ msg.gsub!(/^\x00|\xff$/, '')
609
+ @stream.call(msg)
610
+ end
611
+
612
+ # store remainder if message boundary has not yet
613
+ # been recieved
614
+ @data << buffer if not buffer.empty?
615
+
616
+ false
617
+ end
558
618
  end
559
619
 
560
620
  end
data/lib/em-http/mock.rb CHANGED
@@ -1,11 +1,12 @@
1
1
  module EventMachine
2
- class HttpRequest
2
+ class MockHttpRequest < EventMachine::HttpRequest
3
3
 
4
4
  include HttpEncoding
5
5
 
6
6
  class FakeHttpClient < EventMachine::HttpClient
7
7
 
8
- def setup(response)
8
+ def setup(response, uri)
9
+ @uri = uri
9
10
  receive_data(response)
10
11
  succeed(self)
11
12
  end
@@ -56,12 +57,12 @@ module EventMachine
56
57
  alias_method :real_send_request, :send_request
57
58
 
58
59
  protected
59
- def send_request
60
+ def send_request(&blk)
60
61
  query = "#{@uri.scheme}://#{@uri.host}:#{@uri.port}#{encode_query(@uri.path, @options[:query], @uri.query)}"
61
62
  if s = @@registry[query] and fake = s[@method]
62
63
  @@registry_count[query][@method] += 1
63
64
  client = FakeHttpClient.new(nil)
64
- client.setup(fake)
65
+ client.setup(fake, @uri)
65
66
  client
66
67
  elsif @@pass_through_requests
67
68
  real_send_request
@@ -47,15 +47,15 @@ module EventMachine
47
47
  # OK then)
48
48
  #
49
49
 
50
- def get options = {}; setup_request(:get, options); end
51
- def head options = {}; setup_request(:head, options); end
52
- def delete options = {}; setup_request(:delete, options); end
53
- def put options = {}; setup_request(:put, options); end
54
- def post options = {}; setup_request(:post, options); end
50
+ def get options = {}, &blk; setup_request(:get, options, &blk); end
51
+ def head options = {}, &blk; setup_request(:head, options, &blk); end
52
+ def delete options = {}, &blk; setup_request(:delete, options, &blk); end
53
+ def put options = {}, &blk; setup_request(:put, options, &blk); end
54
+ def post options = {}, &blk; setup_request(:post, options, &blk); end
55
55
 
56
56
  protected
57
57
 
58
- def setup_request(method, options)
58
+ def setup_request(method, options, &blk)
59
59
  raise ArgumentError, "invalid request path" unless /^\// === @uri.path
60
60
  @options = options
61
61
 
@@ -76,10 +76,10 @@ module EventMachine
76
76
  @port_to_connect ||= 80
77
77
 
78
78
  @method = method.to_s.upcase
79
- send_request
79
+ send_request(&blk)
80
80
  end
81
81
 
82
- def send_request
82
+ def send_request(&blk)
83
83
  begin
84
84
  EventMachine.connect(@host_to_connect, @port_to_connect, EventMachine::HttpClient) { |c|
85
85
  c.uri = @uri
@@ -87,6 +87,7 @@ module EventMachine
87
87
  c.options = @options
88
88
  c.comm_inactivity_timeout = @options[:timeout]
89
89
  c.pending_connect_timeout = @options[:timeout]
90
+ blk.call(c) unless blk.nil?
90
91
  }
91
92
  rescue EventMachine::ConnectionError => e
92
93
  conn = EventMachine::HttpClient.new("")
File without changes
data/spec/hash_spec.rb ADDED
@@ -0,0 +1,24 @@
1
+ require 'spec/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.split("&").should include "a=alpha"
8
+ {:a => "alpha", :b => "beta"}.to_params.split("&").should include "b=beta"
9
+ end
10
+
11
+ it "should transform a more complex hash into HTTP POST Params" do
12
+ {:a => "a", :b => ["c", "d", "e"]}.to_params.split("&").should include "a=a"
13
+ {:a => "a", :b => ["c", "d", "e"]}.to_params.split("&").should include "b[0]=c"
14
+ {:a => "a", :b => ["c", "d", "e"]}.to_params.split("&").should include "b[1]=d"
15
+ {:a => "a", :b => ["c", "d", "e"]}.to_params.split("&").should include "b[2]=e"
16
+ end
17
+
18
+ it "should transform a very complex hash into HTTP POST Params" do
19
+ params = {:a => "a", :b => [{:c => "c", :d => "d"}, {:e => "e", :f => "f"}]}.to_params.split("&")
20
+ params.should include "a=a"
21
+ params.should include "b[0][d]=d"
22
+ end
23
+ end
24
+ end
data/spec/helper.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+ require 'pp'
4
+
5
+ require 'lib/em-http'
6
+ require 'lib/em-http/mock'
7
+ require 'em-websocket'
@@ -1,32 +1,25 @@
1
- require 'rubygems'
2
- require 'spec'
3
- require 'pp'
4
-
5
- require 'lib/em-http'
6
- require 'lib/em-http/mock'
1
+ require 'spec/helper'
7
2
 
8
3
  describe 'em-http mock' do
9
4
 
10
5
  before(:each) do
11
- EM::HttpRequest.reset_registry!
12
- EM::HttpRequest.reset_counts!
6
+ EventMachine::MockHttpRequest.reset_registry!
7
+ EventMachine::MockHttpRequest.reset_counts!
13
8
  end
14
9
 
15
10
  it "should serve a fake http request from a file" do
16
- EM::HttpRequest.register_file('http://www.google.ca:80/', :get, File.join(File.dirname(__FILE__), 'fixtures', 'google.ca'))
17
- EM.run do
18
- http = EM::HttpRequest.new('http://www.google.ca/').get
19
- http.callback do
11
+ EventMachine::MockHttpRequest.register_file('http://www.google.ca:80/', :get, File.join(File.dirname(__FILE__), 'fixtures', 'google.ca'))
12
+ EM.run {
13
+
14
+ http = EventMachine::MockHttpRequest.new('http://www.google.ca/').get
15
+ http.errback { fail }
16
+ http.callback {
20
17
  http.response.should == File.read(File.join(File.dirname(__FILE__), 'fixtures', 'google.ca')).split("\n\n", 2).last
21
- EM.stop
22
- end
23
- http.errback do
24
- fail
25
- EM.stop
26
- end
27
- end
18
+ EventMachine.stop
19
+ }
20
+ }
28
21
 
29
- EM::HttpRequest.count('http://www.google.ca:80/', :get).should == 1
22
+ EventMachine::MockHttpRequest.count('http://www.google.ca:80/', :get).should == 1
30
23
  end
31
24
 
32
25
  it "should serve a fake http request from a string" do
@@ -52,32 +45,28 @@ window._gjp && _gjp()</script><style>td{line-height:.8em;}.gac_m td{line-height:
52
45
  ;google.y.first.push(function(){google.ac.b=true;google.ac.i(document.f,document.f.q,'','')});google.xjs&&google.j&&google.j.xi&&google.j.xi()</script></div><script>(function(){
53
46
  function a(){google.timers.load.t.ol=(new Date).getTime();google.report&&google.timers.load.t.xjs&&google.report(google.timers.load,google.kCSI)}if(window.addEventListener)window.addEventListener("load",a,false);else if(window.attachEvent)window.attachEvent("onload",a);google.timers.load.t.prt=(new Date).getTime();
54
47
  })();
55
- HEREDOC
56
- EM::HttpRequest.register('http://www.google.ca:80/', :get, data)
57
- EM.run do
58
- http = EM::HttpRequest.new('http://www.google.ca/').get
59
- http.callback do
48
+ HEREDOC
49
+ EventMachine::MockHttpRequest.register('http://www.google.ca:80/', :get, data)
50
+ EventMachine.run {
51
+
52
+ http = EventMachine::MockHttpRequest.new('http://www.google.ca/').get
53
+ http.errback { fail }
54
+ http.callback {
60
55
  http.response.should == data.split("\n\n", 2).last
61
- EM.stop
62
- end
63
- http.errback do
64
- fail
65
- EM.stop
66
- end
67
- end
56
+ EventMachine.stop
57
+ }
58
+ }
68
59
 
69
- EM::HttpRequest.count('http://www.google.ca:80/', :get).should == 1
60
+ EventMachine::MockHttpRequest.count('http://www.google.ca:80/', :get).should == 1
70
61
  end
71
62
 
72
63
  it "should raise an exception if pass-thru is disabled" do
73
- EM::HttpRequest.pass_through_requests = false
74
- EM.run do
64
+ EventMachine::MockHttpRequest.pass_through_requests = false
65
+ EventMachine.run {
75
66
  proc {
76
- http = EM::HttpRequest.new('http://www.google.ca/').get
67
+ http = EventMachine::MockHttpRequest.new('http://www.google.ca/').get
77
68
  }.should raise_error
78
- EM.stop
79
- end
80
-
69
+ EventMachine.stop
70
+ }
81
71
  end
82
-
83
72
  end
@@ -1,5 +1,5 @@
1
- require 'test/helper'
2
- require 'test/stallion'
1
+ require 'spec/helper'
2
+ require 'spec/stallion'
3
3
 
4
4
  describe EventMachine::MultiRequest do
5
5
 
@@ -1,14 +1,14 @@
1
- require 'test/helper'
2
- require 'test/stallion'
3
- require 'test/stub_server'
4
-
1
+ require 'spec/helper'
2
+ require 'spec/stallion'
3
+ require 'spec/stub_server'
4
+
5
5
  describe EventMachine::HttpRequest do
6
6
 
7
7
  def failed
8
8
  EventMachine.stop
9
9
  fail
10
10
  end
11
-
11
+
12
12
  it "should fail GET on DNS timeout" do
13
13
  EventMachine.run {
14
14
  http = EventMachine::HttpRequest.new('http://127.1.1.1/').get :timeout => 1
@@ -17,7 +17,7 @@ describe EventMachine::HttpRequest do
17
17
  http.response_header.status.should == 0
18
18
  EventMachine.stop
19
19
  }
20
- }
20
+ }
21
21
  end
22
22
 
23
23
  it "should fail GET on invalid host" do
@@ -54,7 +54,7 @@ describe EventMachine::HttpRequest do
54
54
  }
55
55
  }
56
56
  end
57
-
57
+
58
58
  it "should perform successfull GET with a URI passed as argument" do
59
59
  EventMachine.run {
60
60
  uri = URI.parse('http://127.0.0.1:8080/')
@@ -66,7 +66,7 @@ describe EventMachine::HttpRequest do
66
66
  http.response.should match(/Hello/)
67
67
  EventMachine.stop
68
68
  }
69
- }
69
+ }
70
70
  end
71
71
 
72
72
  it "should perform successfull HEAD with a URI passed as argument" do
@@ -80,9 +80,9 @@ describe EventMachine::HttpRequest do
80
80
  http.response.should == ""
81
81
  EventMachine.stop
82
82
  }
83
- }
83
+ }
84
84
  end
85
-
85
+
86
86
  # should be no different than a GET
87
87
  it "should perform successfull DELETE with a URI passed as argument" do
88
88
  EventMachine.run {
@@ -95,7 +95,7 @@ describe EventMachine::HttpRequest do
95
95
  http.response.should == ""
96
96
  EventMachine.stop
97
97
  }
98
- }
98
+ }
99
99
  end
100
100
 
101
101
  it "should return 404 on invalid path" do
@@ -103,7 +103,7 @@ describe EventMachine::HttpRequest do
103
103
  http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/fail').get
104
104
 
105
105
  http.errback { failed }
106
- http.callback {
106
+ http.callback {
107
107
  http.response_header.status.should == 404
108
108
  EventMachine.stop
109
109
  }
@@ -183,13 +183,13 @@ describe EventMachine::HttpRequest do
183
183
  http.errback { failed }
184
184
  http.callback {
185
185
  http.response_header.status.should == 200
186
-
186
+
187
187
  http.response.should match(/key1=1&key2\[0\]=2&key2\[1\]=3/)
188
188
  EventMachine.stop
189
189
  }
190
190
  }
191
191
  end
192
-
192
+
193
193
  it "should perform successfull POST with Ruby Hash/Array as params and with the correct content length" do
194
194
  EventMachine.run {
195
195
  http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/echo_content_length').post :body => {"key1" => "data1"}
@@ -197,7 +197,7 @@ describe EventMachine::HttpRequest do
197
197
  http.errback { failed }
198
198
  http.callback {
199
199
  http.response_header.status.should == 200
200
-
200
+
201
201
  http.response.to_i.should == 10
202
202
  EventMachine.stop
203
203
  }
@@ -223,7 +223,7 @@ describe EventMachine::HttpRequest do
223
223
  http = EventMachine::HttpRequest.new('http://digg.com/').get
224
224
 
225
225
  http.errback { failed }
226
- http.callback {
226
+ http.callback {
227
227
  http.response_header.status.should == 200
228
228
  EventMachine.stop
229
229
  }
@@ -242,8 +242,8 @@ describe EventMachine::HttpRequest do
242
242
  }
243
243
  }
244
244
  end
245
-
246
- it "should send proper OAuth auth header" do
245
+
246
+ it "should send proper OAuth auth header" do
247
247
  EventMachine.run {
248
248
  oauth_header = 'OAuth oauth_nonce="oqwgSYFUD87MHmJJDv7bQqOF2EPnVus7Wkqj5duNByU", b=c, d=e'
249
249
  http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/oauth_auth').get :head => {'authorization' => oauth_header}
@@ -254,7 +254,7 @@ describe EventMachine::HttpRequest do
254
254
  http.response.should == oauth_header
255
255
  EventMachine.stop
256
256
  }
257
- }
257
+ }
258
258
  end
259
259
 
260
260
  it "should work with keep-alive servers" do
@@ -318,7 +318,7 @@ describe EventMachine::HttpRequest do
318
318
  it "should optionally pass the response body progressively" do
319
319
  EventMachine.run {
320
320
  body = ''
321
- http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get
321
+ http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get
322
322
 
323
323
  http.errback { failed }
324
324
  http.stream { |chunk| body += chunk }
@@ -461,7 +461,7 @@ describe EventMachine::HttpRequest do
461
461
  }
462
462
  end
463
463
 
464
- it "should not override content-type when passing in ruby hash/array for body" do
464
+ it "should not override content-type when passing in ruby hash/array for body" do
465
465
  EventMachine.run {
466
466
  http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/echo_content_type').post({
467
467
  :body => {:a => :b}, :head => {'content-type' => 'text'}})
@@ -480,44 +480,153 @@ describe EventMachine::HttpRequest do
480
480
  EventMachine.run {
481
481
  http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/relative-location').get
482
482
 
483
- http.errback { failed }
484
- http.callback {
485
- http.response_header['LOCATION'].should == 'http://127.0.0.1:8080/forwarded'
486
- EventMachine.stop
487
- }
483
+ http.errback { failed }
484
+ http.callback {
485
+ http.response_header['LOCATION'].should == 'http://127.0.0.1:8080/forwarded'
486
+ EventMachine.stop
488
487
  }
488
+ }
489
489
  end
490
490
 
491
+ it 'should let you pass a block to be called once the client is created' do
492
+ client = nil
493
+ EventMachine.run {
494
+ request = EventMachine::HttpRequest.new('http://127.0.0.1:8080/')
495
+ http = request.post { |c|
496
+ c.options[:body] = {:callback_run => 'yes'}
497
+ client = c
498
+ }
499
+ http.errback { failed }
500
+ http.callback {
501
+ client.should be_kind_of(EventMachine::HttpClient)
502
+ http.response_header.status.should == 200
503
+ http.response.should match(/callback_run=yes/)
504
+ client.normalize_uri.should == Addressable::URI.parse('http://127.0.0.1:8080/')
505
+ EventMachine.stop
506
+ }
507
+ }
508
+ end
509
+
510
+ it "should retrieve multiple cookies" do
511
+ EventMachine::MockHttpRequest.register_file('http://www.google.ca:80/', :get, File.join(File.dirname(__FILE__), 'fixtures', 'google.ca'))
512
+ EventMachine.run {
513
+
514
+ http = EventMachine::MockHttpRequest.new('http://www.google.ca/').get
515
+ http.errback { fail }
516
+ http.callback {
517
+ c1 = "PREF=ID=9454187d21c4a6a6:TM=1258403955:LM=1258403955:S=2-mf1n5oV5yAeT9-; expires=Wed, 16-Nov-2011 20:39:15 GMT; path=/; domain=.google.ca"
518
+ c2 = "NID=28=lvxxVdiBQkCetu_WFaUxLyB7qPlHXS5OdAGYTqge_laVlCKVN8VYYeVBh4bNZiK_Oan2gm8oP9GA-FrZfMPC3ZMHeNq37MG2JH8AIW9LYucU8brOeuggMEbLNNXuiWg4; expires=Tue, 18-May-2010 20:39:15 GMT; path=/; domain=.google.ca; HttpOnly"
519
+ http.response_header.cookie.should == [c1, c2]
520
+
521
+ EventMachine.stop
522
+ }
523
+ }
524
+
525
+ EventMachine::MockHttpRequest.count('http://www.google.ca:80/', :get).should == 1
526
+ end
527
+
491
528
  context "connections via proxy" do
492
-
529
+
493
530
  it "should work with proxy servers" do
494
531
  EventMachine.run {
495
532
 
496
533
  http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get :proxy => {:host => '127.0.0.1', :port => 8082}
497
534
 
498
535
  http.errback { p http.inspect; failed }
499
- http.callback {
536
+ http.callback {
500
537
  http.response_header.status.should == 200
501
538
  http.response.should == 'Hello, World!'
502
539
  EventMachine.stop
503
540
  }
504
541
  }
505
- end
542
+ end
506
543
 
507
544
  it "should proxy POST data" do
508
545
  EventMachine.run {
509
546
 
510
- http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').post({
511
- :body => "data", :proxy => {:host => '127.0.0.1', :port => 8082}})
547
+ http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').post({
548
+ :body => "data", :proxy => {:host => '127.0.0.1', :port => 8082}})
512
549
 
513
- http.errback { failed }
514
- http.callback {
515
- http.response_header.status.should == 200
516
- http.response.should match(/data/)
517
- EventMachine.stop
518
- }
519
- }
520
- end
521
-
550
+ http.errback { failed }
551
+ http.callback {
552
+ http.response_header.status.should == 200
553
+ http.response.should match(/data/)
554
+ EventMachine.stop
555
+ }
556
+ }
557
+ end
558
+ end
559
+
560
+ context "websocket connection" do
561
+ # Spec: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-55
562
+ #
563
+ # ws.onopen = http.callback
564
+ # ws.onmessage = http.stream { |msg| }
565
+ # ws.errback = no connection
566
+ #
567
+
568
+ it "should invoke errback on failed upgrade" do
569
+ EventMachine.run {
570
+ http = EventMachine::HttpRequest.new('ws://127.0.0.1:8080/').get :timeout => 0
571
+
572
+ http.callback { failed }
573
+ http.errback {
574
+ http.response_header.status.should == 200
575
+ EventMachine.stop
576
+ }
577
+ }
578
+ end
579
+
580
+ it "should complete websocket handshake and transfer data from client to server and back" do
581
+ EventMachine.run {
582
+ MSG = "hello bi-directional data exchange"
583
+
584
+ EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 8085) do |ws|
585
+ ws.onmessage {|msg| ws.send msg}
586
+ end
587
+
588
+ http = EventMachine::HttpRequest.new('ws://127.0.0.1:8085/').get :timeout => 1
589
+ http.errback { failed }
590
+ http.callback {
591
+ http.response_header.status.should == 101
592
+ http.response_header['CONNECTION'].should match(/Upgrade/)
593
+ http.response_header['UPGRADE'].should match(/WebSocket/)
594
+
595
+ # push should only be invoked after handshake is complete
596
+ http.send(MSG)
597
+ }
598
+
599
+ http.stream { |chunk|
600
+ chunk.should == MSG
601
+ EventMachine.stop
602
+ }
603
+ }
604
+ end
605
+
606
+ it "should split multiple messages from websocket server into separate stream callbacks" do
607
+ EM.run do
608
+ messages = %w[1 2]
609
+ recieved = []
610
+
611
+ EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 8085) do |ws|
612
+ ws.onopen {
613
+ ws.send messages[0]
614
+ ws.send messages[1]
615
+ }
616
+ end
617
+
618
+ EventMachine.add_timer(0.1) do
619
+ http = EventMachine::HttpRequest.new('ws://127.0.0.1:8085/').get :timeout => 0
620
+ http.errback { failed }
621
+ http.callback { http.response_header.status.should == 101 }
622
+ http.stream {|msg|
623
+ msg.should == messages[recieved.size]
624
+ recieved.push msg
625
+
626
+ EventMachine.stop if recieved.size == messages.size
627
+ }
628
+ end
629
+ end
630
+ end
522
631
  end
523
- end
632
+ end
File without changes
File without changes
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: em-http-request
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.5
4
+ version: 0.2.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ilya Grigorik
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-12-12 00:00:00 -05:00
12
+ date: 2009-12-31 00:00:00 -05:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -50,6 +50,7 @@ files:
50
50
  - VERSION
51
51
  - examples/fetch.rb
52
52
  - examples/fibered-http.rb
53
+ - examples/oauth-tweet.rb
53
54
  - ext/buffer/em_buffer.c
54
55
  - ext/buffer/extconf.rb
55
56
  - ext/http11_client/ext_help.h
@@ -65,14 +66,14 @@ files:
65
66
  - lib/em-http/mock.rb
66
67
  - lib/em-http/multi.rb
67
68
  - lib/em-http/request.rb
68
- - test/helper.rb
69
- - test/mock/fixtures/google.ca
70
- - test/mock/test_mock.rb
71
- - test/stallion.rb
72
- - test/stub_server.rb
73
- - test/test_hash.rb
74
- - test/test_multi.rb
75
- - test/test_request.rb
69
+ - spec/fixtures/google.ca
70
+ - spec/hash_spec.rb
71
+ - spec/helper.rb
72
+ - spec/mock_spec.rb
73
+ - spec/multi_spec.rb
74
+ - spec/request_spec.rb
75
+ - spec/stallion.rb
76
+ - spec/stub_server.rb
76
77
  has_rdoc: true
77
78
  homepage: http://github.com/igrigorik/em-http-request
78
79
  licenses: []
@@ -102,12 +103,13 @@ signing_key:
102
103
  specification_version: 3
103
104
  summary: EventMachine based, async HTTP Request interface
104
105
  test_files:
105
- - test/helper.rb
106
- - test/mock/test_mock.rb
107
- - test/stallion.rb
108
- - test/stub_server.rb
109
- - test/test_hash.rb
110
- - test/test_multi.rb
111
- - test/test_request.rb
106
+ - spec/hash_spec.rb
107
+ - spec/helper.rb
108
+ - spec/mock_spec.rb
109
+ - spec/multi_spec.rb
110
+ - spec/request_spec.rb
111
+ - spec/stallion.rb
112
+ - spec/stub_server.rb
112
113
  - examples/fetch.rb
113
114
  - examples/fibered-http.rb
115
+ - examples/oauth-tweet.rb
data/test/helper.rb DELETED
@@ -1,5 +0,0 @@
1
- require 'rubygems'
2
- require 'spec'
3
- require 'pp'
4
-
5
- require 'lib/em-http'
data/test/test_hash.rb DELETED
@@ -1,19 +0,0 @@
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