em-http-request 0.2.5 → 0.2.6
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of em-http-request might be problematic. Click here for more details.
- data/README.rdoc +33 -14
- data/Rakefile +0 -1
- data/VERSION +1 -1
- data/examples/oauth-tweet.rb +50 -0
- data/ext/http11_client/http11_client.c +13 -1
- data/lib/em-http/client.rb +70 -10
- data/lib/em-http/mock.rb +5 -4
- data/lib/em-http/request.rb +9 -8
- data/{test/mock → spec}/fixtures/google.ca +0 -0
- data/spec/hash_spec.rb +24 -0
- data/spec/helper.rb +7 -0
- data/{test/mock/test_mock.rb → spec/mock_spec.rb} +29 -40
- data/{test/test_multi.rb → spec/multi_spec.rb} +2 -2
- data/{test/test_request.rb → spec/request_spec.rb} +150 -41
- data/{test → spec}/stallion.rb +0 -0
- data/{test → spec}/stub_server.rb +0 -0
- metadata +19 -17
- data/test/helper.rb +0 -5
- data/test/test_hash.rb +0 -19
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
|
-
|
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
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.2.
|
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
|
-
|
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)
|
data/lib/em-http/client.rb
CHANGED
@@ -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
|
-
|
251
|
-
@options[:body].
|
252
|
-
|
253
|
-
|
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
|
-
|
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
|
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
|
data/lib/em-http/request.rb
CHANGED
@@ -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
@@ -1,32 +1,25 @@
|
|
1
|
-
require '
|
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
|
-
|
12
|
-
|
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
|
-
|
17
|
-
EM.run
|
18
|
-
|
19
|
-
http.
|
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
|
-
|
22
|
-
|
23
|
-
|
24
|
-
fail
|
25
|
-
EM.stop
|
26
|
-
end
|
27
|
-
end
|
18
|
+
EventMachine.stop
|
19
|
+
}
|
20
|
+
}
|
28
21
|
|
29
|
-
|
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
|
-
|
57
|
-
|
58
|
-
|
59
|
-
http.
|
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
|
-
|
62
|
-
|
63
|
-
|
64
|
-
fail
|
65
|
-
EM.stop
|
66
|
-
end
|
67
|
-
end
|
56
|
+
EventMachine.stop
|
57
|
+
}
|
58
|
+
}
|
68
59
|
|
69
|
-
|
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
|
-
|
74
|
-
|
64
|
+
EventMachine::MockHttpRequest.pass_through_requests = false
|
65
|
+
EventMachine.run {
|
75
66
|
proc {
|
76
|
-
http =
|
67
|
+
http = EventMachine::MockHttpRequest.new('http://www.google.ca/').get
|
77
68
|
}.should raise_error
|
78
|
-
|
79
|
-
|
80
|
-
|
69
|
+
EventMachine.stop
|
70
|
+
}
|
81
71
|
end
|
82
|
-
|
83
72
|
end
|
@@ -1,14 +1,14 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
3
|
-
require '
|
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
|
-
|
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
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
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
|
-
|
511
|
-
|
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
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
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
|
data/{test → spec}/stallion.rb
RENAMED
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.
|
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
|
+
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
|
-
-
|
69
|
-
-
|
70
|
-
-
|
71
|
-
-
|
72
|
-
-
|
73
|
-
-
|
74
|
-
-
|
75
|
-
-
|
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
|
-
-
|
106
|
-
-
|
107
|
-
-
|
108
|
-
-
|
109
|
-
-
|
110
|
-
-
|
111
|
-
-
|
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
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
|