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 +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
|