em-http-request-samesite 1.1.7
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.
- checksums.yaml +7 -0
- data/.gemtest +0 -0
- data/.gitignore +9 -0
- data/.rspec +0 -0
- data/.travis.yml +7 -0
- data/Changelog.md +68 -0
- data/Gemfile +14 -0
- data/README.md +63 -0
- data/Rakefile +10 -0
- data/benchmarks/clients.rb +170 -0
- data/benchmarks/em-excon.rb +87 -0
- data/benchmarks/em-profile.gif +0 -0
- data/benchmarks/em-profile.txt +65 -0
- data/benchmarks/server.rb +48 -0
- data/em-http-request.gemspec +32 -0
- data/examples/.gitignore +1 -0
- data/examples/digest_auth/client.rb +25 -0
- data/examples/digest_auth/server.rb +28 -0
- data/examples/fetch.rb +30 -0
- data/examples/fibered-http.rb +51 -0
- data/examples/multi.rb +25 -0
- data/examples/oauth-tweet.rb +35 -0
- data/examples/socks5.rb +23 -0
- data/lib/em-http-request.rb +1 -0
- data/lib/em-http.rb +20 -0
- data/lib/em-http/client.rb +341 -0
- data/lib/em-http/core_ext/bytesize.rb +6 -0
- data/lib/em-http/decoders.rb +252 -0
- data/lib/em-http/http_client_options.rb +49 -0
- data/lib/em-http/http_connection.rb +321 -0
- data/lib/em-http/http_connection_options.rb +70 -0
- data/lib/em-http/http_encoding.rb +149 -0
- data/lib/em-http/http_header.rb +83 -0
- data/lib/em-http/http_status_codes.rb +57 -0
- data/lib/em-http/middleware/digest_auth.rb +112 -0
- data/lib/em-http/middleware/json_response.rb +15 -0
- data/lib/em-http/middleware/oauth.rb +40 -0
- data/lib/em-http/middleware/oauth2.rb +28 -0
- data/lib/em-http/multi.rb +57 -0
- data/lib/em-http/request.rb +23 -0
- data/lib/em-http/version.rb +5 -0
- data/lib/em/io_streamer.rb +49 -0
- data/spec/client_fiber_spec.rb +23 -0
- data/spec/client_spec.rb +1000 -0
- data/spec/digest_auth_spec.rb +48 -0
- data/spec/dns_spec.rb +41 -0
- data/spec/encoding_spec.rb +49 -0
- data/spec/external_spec.rb +150 -0
- data/spec/fixtures/google.ca +16 -0
- data/spec/fixtures/gzip-sample.gz +0 -0
- data/spec/gzip_spec.rb +91 -0
- data/spec/helper.rb +31 -0
- data/spec/http_proxy_spec.rb +268 -0
- data/spec/middleware/oauth2_spec.rb +15 -0
- data/spec/middleware_spec.rb +143 -0
- data/spec/multi_spec.rb +104 -0
- data/spec/pipelining_spec.rb +66 -0
- data/spec/redirect_spec.rb +430 -0
- data/spec/socksify_proxy_spec.rb +60 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/ssl_spec.rb +71 -0
- data/spec/stallion.rb +334 -0
- data/spec/stub_server.rb +45 -0
- metadata +265 -0
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'simple_oauth'
|
2
|
+
|
3
|
+
module EventMachine
|
4
|
+
module Middleware
|
5
|
+
|
6
|
+
class OAuth
|
7
|
+
include HttpEncoding
|
8
|
+
|
9
|
+
def initialize(opts = {})
|
10
|
+
@opts = opts.dup
|
11
|
+
# Allow both `oauth` gem and `simple_oauth` gem opts formats
|
12
|
+
@opts[:token] ||= @opts.delete(:access_token)
|
13
|
+
@opts[:token_secret] ||= @opts.delete(:access_token_secret)
|
14
|
+
end
|
15
|
+
|
16
|
+
def request(client, head, body)
|
17
|
+
request = client.req
|
18
|
+
uri = request.uri.join(encode_query(request.uri, request.query))
|
19
|
+
params = {}
|
20
|
+
|
21
|
+
# from https://github.com/oauth/oauth-ruby/blob/master/lib/oauth/request_proxy/em_http_request.rb
|
22
|
+
if ["POST", "PUT"].include?(request.method)
|
23
|
+
head["content-type"] ||= "application/x-www-form-urlencoded" if body.is_a? Hash
|
24
|
+
form_encoded = head["content-type"].to_s.downcase.start_with?("application/x-www-form-urlencoded")
|
25
|
+
|
26
|
+
if form_encoded
|
27
|
+
CGI.parse(client.normalize_body(body)).each do |k,v|
|
28
|
+
# Since `CGI.parse` always returns values as an array
|
29
|
+
params[k] = v.size == 1 ? v.first : v
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
head["Authorization"] = SimpleOAuth::Header.new(request.method, uri, params, @opts)
|
35
|
+
|
36
|
+
[head,body]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module EventMachine
|
2
|
+
module Middleware
|
3
|
+
class OAuth2
|
4
|
+
include EM::HttpEncoding
|
5
|
+
attr_accessor :access_token
|
6
|
+
|
7
|
+
def initialize(opts={})
|
8
|
+
self.access_token = opts[:access_token] or raise "No :access_token provided"
|
9
|
+
end
|
10
|
+
|
11
|
+
def request(client, head, body)
|
12
|
+
uri = client.req.uri.dup
|
13
|
+
update_uri! uri
|
14
|
+
client.req.set_uri uri
|
15
|
+
|
16
|
+
[head, body]
|
17
|
+
end
|
18
|
+
|
19
|
+
def update_uri!(uri)
|
20
|
+
if uri.query.nil?
|
21
|
+
uri.query = encode_param(:access_token, access_token)
|
22
|
+
else
|
23
|
+
uri.query += "&#{encode_param(:access_token, access_token)}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module EventMachine
|
2
|
+
|
3
|
+
# EventMachine based Multi request client, based on a streaming HTTPRequest class,
|
4
|
+
# which allows you to open multiple parallel connections and return only when all
|
5
|
+
# of them finish. (i.e. ideal for parallelizing workloads)
|
6
|
+
#
|
7
|
+
# == Example
|
8
|
+
#
|
9
|
+
# EventMachine.run {
|
10
|
+
#
|
11
|
+
# multi = EventMachine::MultiRequest.new
|
12
|
+
#
|
13
|
+
# # add multiple requests to the multi-handler
|
14
|
+
# multi.add(:a, EventMachine::HttpRequest.new('http://www.google.com/').get)
|
15
|
+
# multi.add(:b, EventMachine::HttpRequest.new('http://www.yahoo.com/').get)
|
16
|
+
#
|
17
|
+
# multi.callback {
|
18
|
+
# p multi.responses[:callback]
|
19
|
+
# p multi.responses[:errback]
|
20
|
+
#
|
21
|
+
# EventMachine.stop
|
22
|
+
# }
|
23
|
+
# }
|
24
|
+
#
|
25
|
+
|
26
|
+
class MultiRequest
|
27
|
+
include EventMachine::Deferrable
|
28
|
+
|
29
|
+
attr_reader :requests, :responses
|
30
|
+
|
31
|
+
def initialize
|
32
|
+
@requests = {}
|
33
|
+
@responses = {:callback => {}, :errback => {}}
|
34
|
+
end
|
35
|
+
|
36
|
+
def add(name, conn)
|
37
|
+
raise 'Duplicate Multi key' if @requests.key? name
|
38
|
+
|
39
|
+
@requests[name] = conn
|
40
|
+
|
41
|
+
conn.callback { @responses[:callback][name] = conn; check_progress }
|
42
|
+
conn.errback { @responses[:errback][name] = conn; check_progress }
|
43
|
+
end
|
44
|
+
|
45
|
+
def finished?
|
46
|
+
(@responses[:callback].size + @responses[:errback].size) == @requests.size
|
47
|
+
end
|
48
|
+
|
49
|
+
protected
|
50
|
+
|
51
|
+
# invoke callback if all requests have completed
|
52
|
+
def check_progress
|
53
|
+
succeed(self) if finished?
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module EventMachine
|
2
|
+
class HttpRequest
|
3
|
+
@middleware = []
|
4
|
+
|
5
|
+
def self.new(uri, options={})
|
6
|
+
uri = uri.clone
|
7
|
+
connopt = HttpConnectionOptions.new(uri, options)
|
8
|
+
|
9
|
+
c = HttpConnection.new
|
10
|
+
c.connopts = connopt
|
11
|
+
c.uri = uri
|
12
|
+
c
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.use(klass, *args, &block)
|
16
|
+
@middleware << klass.new(*args, &block)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.middleware
|
20
|
+
@middleware
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'em/streamer'
|
2
|
+
|
3
|
+
# similar to EventMachine::FileStreamer, but for any IO object
|
4
|
+
module EventMachine
|
5
|
+
class IOStreamer
|
6
|
+
include Deferrable
|
7
|
+
CHUNK_SIZE = 16384
|
8
|
+
|
9
|
+
# @param [EventMachine::Connection] connection
|
10
|
+
# @param [IO] io Data source
|
11
|
+
# @param [Integer] Data size
|
12
|
+
#
|
13
|
+
# @option opts [Boolean] :http_chunks (false) Use HTTP 1.1 style chunked-encoding semantics.
|
14
|
+
def initialize(connection, io, opts = {})
|
15
|
+
@connection = connection
|
16
|
+
@io = io
|
17
|
+
@http_chunks = opts[:http_chunks]
|
18
|
+
|
19
|
+
@buff = String.new
|
20
|
+
@io.binmode if @io.respond_to?(:binmode)
|
21
|
+
stream_one_chunk
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
# Used internally to stream one chunk at a time over multiple reactor ticks
|
27
|
+
# @private
|
28
|
+
def stream_one_chunk
|
29
|
+
loop do
|
30
|
+
if @io.eof?
|
31
|
+
@connection.send_data "0\r\n\r\n" if @http_chunks
|
32
|
+
succeed
|
33
|
+
break
|
34
|
+
end
|
35
|
+
|
36
|
+
if @connection.respond_to?(:get_outbound_data_size) && (@connection.get_outbound_data_size > FileStreamer::BackpressureLevel)
|
37
|
+
EventMachine::next_tick { stream_one_chunk }
|
38
|
+
break
|
39
|
+
end
|
40
|
+
|
41
|
+
if @io.read(CHUNK_SIZE, @buff)
|
42
|
+
@connection.send_data("#{@buff.length.to_s(16)}\r\n") if @http_chunks
|
43
|
+
@connection.send_data(@buff)
|
44
|
+
@connection.send_data("\r\n") if @http_chunks
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'fiber'
|
3
|
+
|
4
|
+
describe EventMachine::HttpRequest do
|
5
|
+
context "with fibers" do
|
6
|
+
|
7
|
+
it "should be transparent to connection errors" do
|
8
|
+
EventMachine.run do
|
9
|
+
Fiber.new do
|
10
|
+
f = Fiber.current
|
11
|
+
fired = false
|
12
|
+
http = EventMachine::HttpRequest.new('http://non-existing.domain/', :connection_timeout => 0.1).get
|
13
|
+
http.callback { failed(http) }
|
14
|
+
http.errback { f.resume :errback }
|
15
|
+
|
16
|
+
Fiber.yield.should == :errback
|
17
|
+
EM.stop
|
18
|
+
end.resume
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
data/spec/client_spec.rb
ADDED
@@ -0,0 +1,1000 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe EventMachine::HttpRequest do
|
4
|
+
|
5
|
+
def failed(http=nil)
|
6
|
+
EventMachine.stop
|
7
|
+
http ? fail(http.error) : fail
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should perform successful GET" do
|
11
|
+
EventMachine.run {
|
12
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/').get
|
13
|
+
|
14
|
+
http.errback { failed(http) }
|
15
|
+
http.callback {
|
16
|
+
http.response_header.status.should == 200
|
17
|
+
http.response.should match(/Hello/)
|
18
|
+
EventMachine.stop
|
19
|
+
}
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should perform successful GET with a URI passed as argument" do
|
24
|
+
EventMachine.run {
|
25
|
+
uri = URI.parse('http://127.0.0.1:8090/')
|
26
|
+
http = EventMachine::HttpRequest.new(uri).get
|
27
|
+
|
28
|
+
http.errback { failed(http) }
|
29
|
+
http.callback {
|
30
|
+
http.response_header.status.should == 200
|
31
|
+
http.response.should match(/Hello/)
|
32
|
+
EventMachine.stop
|
33
|
+
}
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should succeed GET on missing path" do
|
38
|
+
EventMachine.run {
|
39
|
+
lambda {
|
40
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090').get
|
41
|
+
http.callback {
|
42
|
+
http.response.should match(/Hello/)
|
43
|
+
EventMachine.stop
|
44
|
+
}
|
45
|
+
}.should_not raise_error
|
46
|
+
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should raise error on invalid URL" do
|
51
|
+
EventMachine.run {
|
52
|
+
lambda {
|
53
|
+
EventMachine::HttpRequest.new('random?text').get
|
54
|
+
}.should raise_error(Addressable::URI::InvalidURIError)
|
55
|
+
|
56
|
+
EM.stop
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should perform successful HEAD with a URI passed as argument" do
|
61
|
+
EventMachine.run {
|
62
|
+
uri = URI.parse('http://127.0.0.1:8090/')
|
63
|
+
http = EventMachine::HttpRequest.new(uri).head
|
64
|
+
|
65
|
+
http.errback { failed(http) }
|
66
|
+
http.callback {
|
67
|
+
http.response_header.status.should == 200
|
68
|
+
http.response.should == ""
|
69
|
+
EventMachine.stop
|
70
|
+
}
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should perform successful DELETE with a URI passed as argument" do
|
75
|
+
EventMachine.run {
|
76
|
+
uri = URI.parse('http://127.0.0.1:8090/')
|
77
|
+
http = EventMachine::HttpRequest.new(uri).delete
|
78
|
+
|
79
|
+
http.errback { failed(http) }
|
80
|
+
http.callback {
|
81
|
+
http.response_header.status.should == 200
|
82
|
+
http.response.should == ""
|
83
|
+
EventMachine.stop
|
84
|
+
}
|
85
|
+
}
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should return 404 on invalid path" do
|
89
|
+
EventMachine.run {
|
90
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/fail').get
|
91
|
+
|
92
|
+
http.errback { failed(http) }
|
93
|
+
http.callback {
|
94
|
+
http.response_header.status.should == 404
|
95
|
+
EventMachine.stop
|
96
|
+
}
|
97
|
+
}
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should return HTTP reason" do
|
101
|
+
EventMachine.run {
|
102
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/fail').get
|
103
|
+
|
104
|
+
http.errback { failed(http) }
|
105
|
+
http.callback {
|
106
|
+
http.response_header.status.should == 404
|
107
|
+
http.response_header.http_reason.should == 'Not Found'
|
108
|
+
EventMachine.stop
|
109
|
+
}
|
110
|
+
}
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should return HTTP reason 'unknown' on a non-standard status code" do
|
114
|
+
EventMachine.run {
|
115
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/fail_with_nonstandard_response').get
|
116
|
+
|
117
|
+
http.errback { failed(http) }
|
118
|
+
http.callback {
|
119
|
+
http.response_header.status.should == 420
|
120
|
+
http.response_header.http_reason.should == 'unknown'
|
121
|
+
EventMachine.stop
|
122
|
+
}
|
123
|
+
}
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should build query parameters from Hash" do
|
127
|
+
EventMachine.run {
|
128
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/').get :query => {:q => 'test'}
|
129
|
+
|
130
|
+
http.errback { failed(http) }
|
131
|
+
http.callback {
|
132
|
+
http.response_header.status.should == 200
|
133
|
+
http.response.should match(/test/)
|
134
|
+
EventMachine.stop
|
135
|
+
}
|
136
|
+
}
|
137
|
+
end
|
138
|
+
|
139
|
+
it "should pass query parameters string" do
|
140
|
+
EventMachine.run {
|
141
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/').get :query => "q=test"
|
142
|
+
|
143
|
+
http.errback { failed(http) }
|
144
|
+
http.callback {
|
145
|
+
http.response_header.status.should == 200
|
146
|
+
http.response.should match(/test/)
|
147
|
+
EventMachine.stop
|
148
|
+
}
|
149
|
+
}
|
150
|
+
end
|
151
|
+
|
152
|
+
it "should encode an array of query parameters" do
|
153
|
+
EventMachine.run {
|
154
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/echo_query').get :query => {:hash =>['value1','value2']}
|
155
|
+
|
156
|
+
http.errback { failed(http) }
|
157
|
+
http.callback {
|
158
|
+
http.response_header.status.should == 200
|
159
|
+
http.response.should match(/hash\[\]=value1&hash\[\]=value2/)
|
160
|
+
EventMachine.stop
|
161
|
+
}
|
162
|
+
}
|
163
|
+
end
|
164
|
+
|
165
|
+
it "should perform successful PUT" do
|
166
|
+
EventMachine.run {
|
167
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/').put :body => "data"
|
168
|
+
|
169
|
+
http.errback { failed(http) }
|
170
|
+
http.callback {
|
171
|
+
http.response_header.status.should == 200
|
172
|
+
http.response.should match(/data/)
|
173
|
+
EventMachine.stop
|
174
|
+
}
|
175
|
+
}
|
176
|
+
end
|
177
|
+
|
178
|
+
it "should perform successful POST" do
|
179
|
+
EventMachine.run {
|
180
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/').post :body => "data"
|
181
|
+
|
182
|
+
http.errback { failed(http) }
|
183
|
+
http.callback {
|
184
|
+
http.response_header.status.should == 200
|
185
|
+
http.response.should match(/data/)
|
186
|
+
EventMachine.stop
|
187
|
+
}
|
188
|
+
}
|
189
|
+
end
|
190
|
+
|
191
|
+
it "should perform successful PATCH" do
|
192
|
+
EventMachine.run {
|
193
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/').patch :body => "data"
|
194
|
+
|
195
|
+
http.errback { failed(http) }
|
196
|
+
http.callback {
|
197
|
+
http.response_header.status.should == 200
|
198
|
+
http.response.should match(/data/)
|
199
|
+
EventMachine.stop
|
200
|
+
}
|
201
|
+
}
|
202
|
+
end
|
203
|
+
|
204
|
+
it "should escape body on POST" do
|
205
|
+
EventMachine.run {
|
206
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/').post :body => {:stuff => 'string&string'}
|
207
|
+
|
208
|
+
http.errback { failed(http) }
|
209
|
+
http.callback {
|
210
|
+
http.response_header.status.should == 200
|
211
|
+
http.response.should == "stuff=string%26string"
|
212
|
+
EventMachine.stop
|
213
|
+
}
|
214
|
+
}
|
215
|
+
end
|
216
|
+
|
217
|
+
it "should perform successful POST with Ruby Hash/Array as params" do
|
218
|
+
EventMachine.run {
|
219
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/').post :body => {"key1" => 1, "key2" => [2,3]}
|
220
|
+
|
221
|
+
http.errback { failed(http) }
|
222
|
+
http.callback {
|
223
|
+
http.response_header.status.should == 200
|
224
|
+
|
225
|
+
http.response.should match(/key1=1&key2\[0\]=2&key2\[1\]=3/)
|
226
|
+
EventMachine.stop
|
227
|
+
}
|
228
|
+
}
|
229
|
+
end
|
230
|
+
|
231
|
+
it "should set content-length to 0 on posts with empty bodies" do
|
232
|
+
EventMachine.run {
|
233
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/echo_content_length_from_header').post
|
234
|
+
|
235
|
+
http.errback { failed(http) }
|
236
|
+
http.callback {
|
237
|
+
http.response_header.status.should == 200
|
238
|
+
|
239
|
+
http.response.strip.split(':')[1].should == '0'
|
240
|
+
EventMachine.stop
|
241
|
+
}
|
242
|
+
}
|
243
|
+
end
|
244
|
+
|
245
|
+
it "should perform successful POST with Ruby Hash/Array as params and with the correct content length" do
|
246
|
+
EventMachine.run {
|
247
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/echo_content_length').post :body => {"key1" => "data1"}
|
248
|
+
|
249
|
+
http.errback { failed(http) }
|
250
|
+
http.callback {
|
251
|
+
http.response_header.status.should == 200
|
252
|
+
|
253
|
+
http.response.to_i.should == 10
|
254
|
+
EventMachine.stop
|
255
|
+
}
|
256
|
+
}
|
257
|
+
end
|
258
|
+
|
259
|
+
xit "should support expect-continue header" do
|
260
|
+
EventMachine.run {
|
261
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090').post :body => "data", :head => { 'expect' => '100-continue' }
|
262
|
+
|
263
|
+
http.errback { failed(http) }
|
264
|
+
http.callback {
|
265
|
+
http.response_header.status.should == 200
|
266
|
+
http.response.should == "data"
|
267
|
+
EventMachine.stop
|
268
|
+
}
|
269
|
+
}
|
270
|
+
end
|
271
|
+
|
272
|
+
it "should perform successful GET with custom header" do
|
273
|
+
EventMachine.run {
|
274
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/').get :head => {'if-none-match' => 'evar!'}
|
275
|
+
|
276
|
+
http.errback { p http; failed(http) }
|
277
|
+
http.callback {
|
278
|
+
http.response_header.status.should == 304
|
279
|
+
EventMachine.stop
|
280
|
+
}
|
281
|
+
}
|
282
|
+
end
|
283
|
+
|
284
|
+
it "should perform basic auth" do
|
285
|
+
EventMachine.run {
|
286
|
+
|
287
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/authtest').get :head => {'authorization' => ['user', 'pass']}
|
288
|
+
|
289
|
+
http.errback { failed(http) }
|
290
|
+
http.callback {
|
291
|
+
http.response_header.status.should == 200
|
292
|
+
EventMachine.stop
|
293
|
+
}
|
294
|
+
}
|
295
|
+
end
|
296
|
+
|
297
|
+
it "should perform basic auth via the URL" do
|
298
|
+
EventMachine.run {
|
299
|
+
|
300
|
+
http = EventMachine::HttpRequest.new('http://user:pass@127.0.0.1:8090/authtest').get
|
301
|
+
|
302
|
+
http.errback { failed(http) }
|
303
|
+
http.callback {
|
304
|
+
http.response_header.status.should == 200
|
305
|
+
EventMachine.stop
|
306
|
+
}
|
307
|
+
}
|
308
|
+
end
|
309
|
+
|
310
|
+
it "should return peer's IP address" do
|
311
|
+
EventMachine.run {
|
312
|
+
|
313
|
+
conn = EventMachine::HttpRequest.new('http://127.0.0.1:8090/')
|
314
|
+
conn.peer.should be_nil
|
315
|
+
|
316
|
+
http = conn.get
|
317
|
+
http.peer.should be_nil
|
318
|
+
|
319
|
+
http.errback { failed(http) }
|
320
|
+
http.callback {
|
321
|
+
conn.peer.should == '127.0.0.1'
|
322
|
+
http.peer.should == '127.0.0.1'
|
323
|
+
|
324
|
+
EventMachine.stop
|
325
|
+
}
|
326
|
+
}
|
327
|
+
end
|
328
|
+
|
329
|
+
it "should remove all newlines from long basic auth header" do
|
330
|
+
EventMachine.run {
|
331
|
+
auth = {'authorization' => ['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzz']}
|
332
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/auth').get :head => auth
|
333
|
+
http.errback { failed(http) }
|
334
|
+
http.callback {
|
335
|
+
http.response_header.status.should == 200
|
336
|
+
http.response.should == "Basic YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhOnp6enp6enp6enp6enp6enp6enp6enp6enp6enp6eg=="
|
337
|
+
EventMachine.stop
|
338
|
+
}
|
339
|
+
}
|
340
|
+
end
|
341
|
+
|
342
|
+
it "should send proper OAuth auth header" do
|
343
|
+
EventMachine.run {
|
344
|
+
oauth_header = 'OAuth oauth_nonce="oqwgSYFUD87MHmJJDv7bQqOF2EPnVus7Wkqj5duNByU", b=c, d=e'
|
345
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/auth').get :head => {
|
346
|
+
'authorization' => oauth_header
|
347
|
+
}
|
348
|
+
|
349
|
+
http.errback { failed(http) }
|
350
|
+
http.callback {
|
351
|
+
http.response_header.status.should == 200
|
352
|
+
http.response.should == oauth_header
|
353
|
+
EventMachine.stop
|
354
|
+
}
|
355
|
+
}
|
356
|
+
end
|
357
|
+
|
358
|
+
it "should return ETag and Last-Modified headers" do
|
359
|
+
EventMachine.run {
|
360
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/echo_query').get
|
361
|
+
|
362
|
+
http.errback { failed(http) }
|
363
|
+
http.callback {
|
364
|
+
http.response_header.status.should == 200
|
365
|
+
http.response_header.etag.should match('abcdefg')
|
366
|
+
http.response_header.last_modified.should match('Fri, 13 Aug 2010 17:31:21 GMT')
|
367
|
+
EventMachine.stop
|
368
|
+
}
|
369
|
+
}
|
370
|
+
end
|
371
|
+
|
372
|
+
it "should return raw headers in a hash" do
|
373
|
+
EventMachine.run {
|
374
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/echo_headers').get
|
375
|
+
|
376
|
+
http.errback { failed(http) }
|
377
|
+
http.callback {
|
378
|
+
http.response_header.status.should == 200
|
379
|
+
http.response_header.raw['Set-Cookie'].should match('test=yes')
|
380
|
+
http.response_header.raw['X-Forward-Host'].should match('proxy.local')
|
381
|
+
EventMachine.stop
|
382
|
+
}
|
383
|
+
}
|
384
|
+
end
|
385
|
+
|
386
|
+
it "should detect deflate encoding" do
|
387
|
+
EventMachine.run {
|
388
|
+
|
389
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/deflate').get :head => {"accept-encoding" => "deflate"}
|
390
|
+
|
391
|
+
http.errback { failed(http) }
|
392
|
+
http.callback {
|
393
|
+
http.response_header.status.should == 200
|
394
|
+
http.response_header["CONTENT_ENCODING"].should == "deflate"
|
395
|
+
http.response.should == "compressed"
|
396
|
+
|
397
|
+
EventMachine.stop
|
398
|
+
}
|
399
|
+
}
|
400
|
+
end
|
401
|
+
|
402
|
+
it "should auto-detect and decode gzip encoding" do
|
403
|
+
EventMachine.run {
|
404
|
+
|
405
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/gzip').get :head => {"accept-encoding" => "gzip, compressed"}
|
406
|
+
|
407
|
+
http.errback { failed(http) }
|
408
|
+
http.callback {
|
409
|
+
http.response_header.status.should == 200
|
410
|
+
http.response_header["CONTENT_ENCODING"].should == "gzip"
|
411
|
+
http.response.should == "compressed"
|
412
|
+
|
413
|
+
EventMachine.stop
|
414
|
+
}
|
415
|
+
}
|
416
|
+
end
|
417
|
+
|
418
|
+
it "should stream gzip responses" do
|
419
|
+
expected_response = Zlib::GzipReader.open(File.dirname(__FILE__) + "/fixtures/gzip-sample.gz") { |f| f.read }
|
420
|
+
actual_response = ''
|
421
|
+
|
422
|
+
EventMachine.run {
|
423
|
+
|
424
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/gzip-large').get :head => {"accept-encoding" => "gzip, compressed"}
|
425
|
+
|
426
|
+
http.errback { failed(http) }
|
427
|
+
http.callback {
|
428
|
+
http.response_header.status.should == 200
|
429
|
+
http.response_header["CONTENT_ENCODING"].should == "gzip"
|
430
|
+
http.response.should == ''
|
431
|
+
|
432
|
+
actual_response.should == expected_response
|
433
|
+
|
434
|
+
EventMachine.stop
|
435
|
+
}
|
436
|
+
http.stream do |chunk|
|
437
|
+
actual_response << chunk
|
438
|
+
end
|
439
|
+
}
|
440
|
+
end
|
441
|
+
|
442
|
+
it "should not decode the response when configured so" do
|
443
|
+
EventMachine.run {
|
444
|
+
|
445
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/gzip').get :head => {
|
446
|
+
"accept-encoding" => "gzip, compressed"
|
447
|
+
}, :decoding => false
|
448
|
+
|
449
|
+
http.errback { failed(http) }
|
450
|
+
http.callback {
|
451
|
+
http.response_header.status.should == 200
|
452
|
+
http.response_header["CONTENT_ENCODING"].should == "gzip"
|
453
|
+
|
454
|
+
raw = http.response
|
455
|
+
Zlib::GzipReader.new(StringIO.new(raw)).read.should == "compressed"
|
456
|
+
|
457
|
+
EventMachine.stop
|
458
|
+
}
|
459
|
+
}
|
460
|
+
end
|
461
|
+
|
462
|
+
it "should default to requesting compressed response" do
|
463
|
+
EventMachine.run {
|
464
|
+
|
465
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/echo_accept_encoding').get
|
466
|
+
|
467
|
+
http.errback { failed(http) }
|
468
|
+
http.callback {
|
469
|
+
http.response_header.status.should == 200
|
470
|
+
http.response.should == "gzip, compressed"
|
471
|
+
|
472
|
+
EventMachine.stop
|
473
|
+
}
|
474
|
+
}
|
475
|
+
end
|
476
|
+
|
477
|
+
it "should default to requesting compressed response" do
|
478
|
+
EventMachine.run {
|
479
|
+
|
480
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/echo_accept_encoding').get :compressed => false
|
481
|
+
|
482
|
+
http.errback { failed(http) }
|
483
|
+
http.callback {
|
484
|
+
http.response_header.status.should == 200
|
485
|
+
http.response.should == ""
|
486
|
+
|
487
|
+
EventMachine.stop
|
488
|
+
}
|
489
|
+
}
|
490
|
+
end
|
491
|
+
|
492
|
+
it "should timeout after 0.1 seconds of inactivity" do
|
493
|
+
EventMachine.run {
|
494
|
+
t = Time.now.to_i
|
495
|
+
EventMachine.heartbeat_interval = 0.1
|
496
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/timeout', :inactivity_timeout => 0.1).get
|
497
|
+
|
498
|
+
http.errback {
|
499
|
+
http.error.should == Errno::ETIMEDOUT
|
500
|
+
(Time.now.to_i - t).should <= 1
|
501
|
+
EventMachine.stop
|
502
|
+
}
|
503
|
+
http.callback { failed(http) }
|
504
|
+
}
|
505
|
+
end
|
506
|
+
|
507
|
+
it "should complete a Location: with a relative path" do
|
508
|
+
EventMachine.run {
|
509
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/relative-location').get
|
510
|
+
|
511
|
+
http.errback { failed(http) }
|
512
|
+
http.callback {
|
513
|
+
http.response_header['LOCATION'].should == 'http://127.0.0.1:8090/forwarded'
|
514
|
+
EventMachine.stop
|
515
|
+
}
|
516
|
+
}
|
517
|
+
end
|
518
|
+
|
519
|
+
context "body content-type encoding" do
|
520
|
+
it "should not set content type on string in body" do
|
521
|
+
EventMachine.run {
|
522
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/echo_content_type').post :body => "data"
|
523
|
+
|
524
|
+
http.errback { failed(http) }
|
525
|
+
http.callback {
|
526
|
+
http.response_header.status.should == 200
|
527
|
+
http.response.should be_empty
|
528
|
+
EventMachine.stop
|
529
|
+
}
|
530
|
+
}
|
531
|
+
end
|
532
|
+
|
533
|
+
it "should set content-type automatically when passed a ruby hash/array for body" do
|
534
|
+
EventMachine.run {
|
535
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/echo_content_type').post :body => {:a => :b}
|
536
|
+
|
537
|
+
http.errback { failed(http) }
|
538
|
+
http.callback {
|
539
|
+
http.response_header.status.should == 200
|
540
|
+
http.response.should match("application/x-www-form-urlencoded")
|
541
|
+
EventMachine.stop
|
542
|
+
}
|
543
|
+
}
|
544
|
+
end
|
545
|
+
|
546
|
+
it "should not override content-type when passing in ruby hash/array for body" do
|
547
|
+
EventMachine.run {
|
548
|
+
ct = 'text; charset=utf-8'
|
549
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/echo_content_type').post({
|
550
|
+
:body => {:a => :b}, :head => {'content-type' => ct}})
|
551
|
+
|
552
|
+
http.errback { failed(http) }
|
553
|
+
http.callback {
|
554
|
+
http.response_header.status.should == 200
|
555
|
+
http.content_charset.should == Encoding.find('utf-8') if defined? Encoding
|
556
|
+
http.response_header["CONTENT_TYPE"].should == ct
|
557
|
+
EventMachine.stop
|
558
|
+
}
|
559
|
+
}
|
560
|
+
end
|
561
|
+
|
562
|
+
it "should default to external encoding on invalid encoding" do
|
563
|
+
EventMachine.run {
|
564
|
+
ct = 'text/html; charset=utf-8lias'
|
565
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/echo_content_type').post({
|
566
|
+
:body => {:a => :b}, :head => {'content-type' => ct}})
|
567
|
+
|
568
|
+
http.errback { failed(http) }
|
569
|
+
http.callback {
|
570
|
+
http.response_header.status.should == 200
|
571
|
+
http.content_charset.should == Encoding.find('utf-8') if defined? Encoding
|
572
|
+
http.response_header["CONTENT_TYPE"].should == ct
|
573
|
+
EventMachine.stop
|
574
|
+
}
|
575
|
+
}
|
576
|
+
end
|
577
|
+
|
578
|
+
it "should processed escaped content-type" do
|
579
|
+
EventMachine.run {
|
580
|
+
ct = "text/html; charset=\"ISO-8859-4\""
|
581
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/echo_content_type').post({
|
582
|
+
:body => {:a => :b}, :head => {'content-type' => ct}})
|
583
|
+
|
584
|
+
http.errback { failed(http) }
|
585
|
+
http.callback {
|
586
|
+
http.response_header.status.should == 200
|
587
|
+
http.content_charset.should == Encoding.find('ISO-8859-4') if defined? Encoding
|
588
|
+
http.response_header["CONTENT_TYPE"].should == ct
|
589
|
+
EventMachine.stop
|
590
|
+
}
|
591
|
+
}
|
592
|
+
end
|
593
|
+
end
|
594
|
+
|
595
|
+
context "optional header callback" do
|
596
|
+
it "should optionally pass the response headers" do
|
597
|
+
EventMachine.run {
|
598
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/').get
|
599
|
+
|
600
|
+
http.errback { failed(http) }
|
601
|
+
http.headers { |hash|
|
602
|
+
hash.should be_an_kind_of Hash
|
603
|
+
hash.should include 'CONNECTION'
|
604
|
+
hash.should include 'CONTENT_LENGTH'
|
605
|
+
}
|
606
|
+
|
607
|
+
http.callback {
|
608
|
+
http.response_header.status.should == 200
|
609
|
+
http.response.should match(/Hello/)
|
610
|
+
EventMachine.stop
|
611
|
+
}
|
612
|
+
}
|
613
|
+
end
|
614
|
+
|
615
|
+
it "should allow to terminate current connection from header callback" do
|
616
|
+
EventMachine.run {
|
617
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/').get
|
618
|
+
|
619
|
+
http.callback { failed(http) }
|
620
|
+
http.headers { |hash|
|
621
|
+
hash.should be_an_kind_of Hash
|
622
|
+
hash.should include 'CONNECTION'
|
623
|
+
hash.should include 'CONTENT_LENGTH'
|
624
|
+
|
625
|
+
http.close('header callback terminated connection')
|
626
|
+
}
|
627
|
+
|
628
|
+
http.errback { |e|
|
629
|
+
http.response_header.status.should == 200
|
630
|
+
http.error.should == 'header callback terminated connection'
|
631
|
+
http.response.should == ''
|
632
|
+
EventMachine.stop
|
633
|
+
}
|
634
|
+
}
|
635
|
+
end
|
636
|
+
end
|
637
|
+
|
638
|
+
it "should optionally pass the response body progressively" do
|
639
|
+
EventMachine.run {
|
640
|
+
body = ''
|
641
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/').get
|
642
|
+
|
643
|
+
http.errback { failed(http) }
|
644
|
+
http.stream { |chunk| body += chunk }
|
645
|
+
|
646
|
+
http.callback {
|
647
|
+
http.response_header.status.should == 200
|
648
|
+
http.response.should == ''
|
649
|
+
body.should match(/Hello/)
|
650
|
+
EventMachine.stop
|
651
|
+
}
|
652
|
+
}
|
653
|
+
end
|
654
|
+
|
655
|
+
it "should optionally pass the deflate-encoded response body progressively" do
|
656
|
+
EventMachine.run {
|
657
|
+
body = ''
|
658
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/deflate').get :head => {
|
659
|
+
"accept-encoding" => "deflate, compressed"
|
660
|
+
}
|
661
|
+
|
662
|
+
http.errback { failed(http) }
|
663
|
+
http.stream { |chunk| body += chunk }
|
664
|
+
|
665
|
+
http.callback {
|
666
|
+
http.response_header.status.should == 200
|
667
|
+
http.response_header["CONTENT_ENCODING"].should == "deflate"
|
668
|
+
http.response.should == ''
|
669
|
+
body.should == "compressed"
|
670
|
+
EventMachine.stop
|
671
|
+
}
|
672
|
+
}
|
673
|
+
end
|
674
|
+
|
675
|
+
it "should accept & return cookie header to user" do
|
676
|
+
EventMachine.run {
|
677
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/set_cookie').get
|
678
|
+
|
679
|
+
http.errback { failed(http) }
|
680
|
+
http.callback {
|
681
|
+
http.response_header.status.should == 200
|
682
|
+
http.response_header.cookie.should == "id=1; expires=Sat, 09 Aug 2031 17:53:39 GMT; path=/;"
|
683
|
+
EventMachine.stop
|
684
|
+
}
|
685
|
+
}
|
686
|
+
end
|
687
|
+
|
688
|
+
it "should return array of cookies on multiple Set-Cookie headers" do
|
689
|
+
EventMachine.run {
|
690
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/set_multiple_cookies').get
|
691
|
+
|
692
|
+
http.errback { failed(http) }
|
693
|
+
http.callback {
|
694
|
+
http.response_header.status.should == 200
|
695
|
+
http.response_header.cookie.size.should == 2
|
696
|
+
http.response_header.cookie.first.should == "id=1; expires=Sat, 09 Aug 2031 17:53:39 GMT; path=/;"
|
697
|
+
http.response_header.cookie.last.should == "id=2;"
|
698
|
+
|
699
|
+
EventMachine.stop
|
700
|
+
}
|
701
|
+
}
|
702
|
+
end
|
703
|
+
|
704
|
+
it "should pass cookie header to server from string" do
|
705
|
+
EventMachine.run {
|
706
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/echo_cookie').get :head => {'cookie' => 'id=2;'}
|
707
|
+
|
708
|
+
http.errback { failed(http) }
|
709
|
+
http.callback {
|
710
|
+
http.response.should == "id=2;"
|
711
|
+
EventMachine.stop
|
712
|
+
}
|
713
|
+
}
|
714
|
+
end
|
715
|
+
|
716
|
+
it "should pass cookie header to server from Hash" do
|
717
|
+
EventMachine.run {
|
718
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/echo_cookie').get :head => {'cookie' => {'id' => 2}}
|
719
|
+
|
720
|
+
http.errback { failed(http) }
|
721
|
+
http.callback {
|
722
|
+
http.response.should == "id=2;"
|
723
|
+
EventMachine.stop
|
724
|
+
}
|
725
|
+
}
|
726
|
+
end
|
727
|
+
|
728
|
+
it "should get the body without Content-Length" do
|
729
|
+
EventMachine.run {
|
730
|
+
@s = StubServer.new("HTTP/1.1 200 OK\r\n\r\nFoo")
|
731
|
+
|
732
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8081/').get
|
733
|
+
http.errback { failed(http) }
|
734
|
+
http.callback {
|
735
|
+
http.response.should match(/Foo/)
|
736
|
+
http.response_header['CONTENT_LENGTH'].should be_nil
|
737
|
+
|
738
|
+
@s.stop
|
739
|
+
EventMachine.stop
|
740
|
+
}
|
741
|
+
}
|
742
|
+
end
|
743
|
+
|
744
|
+
context "when talking to a stub HTTP/1.0 server" do
|
745
|
+
it "should get the body without Content-Length" do
|
746
|
+
|
747
|
+
EventMachine.run {
|
748
|
+
@s = StubServer.new("HTTP/1.0 200 OK\r\nConnection: close\r\n\r\nFoo")
|
749
|
+
|
750
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8081/').get
|
751
|
+
http.errback { failed(http) }
|
752
|
+
http.callback {
|
753
|
+
http.response.should match(/Foo/)
|
754
|
+
http.response_header['CONTENT_LENGTH'].should be_nil
|
755
|
+
|
756
|
+
@s.stop
|
757
|
+
EventMachine.stop
|
758
|
+
}
|
759
|
+
}
|
760
|
+
end
|
761
|
+
|
762
|
+
it "should work with \\n instead of \\r\\n" do
|
763
|
+
EventMachine.run {
|
764
|
+
@s = StubServer.new("HTTP/1.0 200 OK\nContent-Type: text/plain\nContent-Length: 3\nConnection: close\n\nFoo")
|
765
|
+
|
766
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8081/').get
|
767
|
+
http.errback { failed(http) }
|
768
|
+
http.callback {
|
769
|
+
http.response_header.status.should == 200
|
770
|
+
http.response_header['CONTENT_TYPE'].should == 'text/plain'
|
771
|
+
http.response.should match(/Foo/)
|
772
|
+
|
773
|
+
@s.stop
|
774
|
+
EventMachine.stop
|
775
|
+
}
|
776
|
+
}
|
777
|
+
end
|
778
|
+
|
779
|
+
it "should handle invalid HTTP response" do
|
780
|
+
EventMachine.run {
|
781
|
+
@s = StubServer.new("<html></html>")
|
782
|
+
|
783
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8081/').get
|
784
|
+
http.callback { failed(http) }
|
785
|
+
http.errback {
|
786
|
+
http.error.should_not be_nil
|
787
|
+
EM.stop
|
788
|
+
}
|
789
|
+
}
|
790
|
+
end
|
791
|
+
end
|
792
|
+
|
793
|
+
it "should stream a file off disk" do
|
794
|
+
EventMachine.run {
|
795
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/').post :file => 'spec/fixtures/google.ca'
|
796
|
+
|
797
|
+
http.errback { failed(http) }
|
798
|
+
http.callback {
|
799
|
+
http.response.should match('google')
|
800
|
+
EventMachine.stop
|
801
|
+
}
|
802
|
+
}
|
803
|
+
end
|
804
|
+
|
805
|
+
it "streams POST request from disk via Pathname" do
|
806
|
+
EventMachine.run {
|
807
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/').post :body => Pathname.new('spec/fixtures/google.ca')
|
808
|
+
http.errback { failed(http) }
|
809
|
+
http.callback {
|
810
|
+
http.response.should match('google')
|
811
|
+
EventMachine.stop
|
812
|
+
}
|
813
|
+
}
|
814
|
+
end
|
815
|
+
|
816
|
+
it "streams POST request from IO object" do
|
817
|
+
EventMachine.run {
|
818
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/').post :body => StringIO.new(File.read('spec/fixtures/google.ca'))
|
819
|
+
http.errback { failed(http) }
|
820
|
+
http.callback {
|
821
|
+
http.response.should match('google')
|
822
|
+
EventMachine.stop
|
823
|
+
}
|
824
|
+
}
|
825
|
+
end
|
826
|
+
|
827
|
+
it "should reconnect if connection was closed between requests" do
|
828
|
+
EventMachine.run {
|
829
|
+
conn = EM::HttpRequest.new('http://127.0.0.1:8090/')
|
830
|
+
req = conn.get
|
831
|
+
|
832
|
+
req.callback do
|
833
|
+
conn.close('client closing connection')
|
834
|
+
|
835
|
+
EM.next_tick do
|
836
|
+
req = conn.get :path => "/gzip"
|
837
|
+
req.callback do
|
838
|
+
req.response_header.status.should == 200
|
839
|
+
req.response.should match('compressed')
|
840
|
+
EventMachine.stop
|
841
|
+
end
|
842
|
+
end
|
843
|
+
end
|
844
|
+
}
|
845
|
+
end
|
846
|
+
|
847
|
+
it "should report error if connection was closed by server on client keepalive requests" do
|
848
|
+
EventMachine.run {
|
849
|
+
conn = EM::HttpRequest.new('http://127.0.0.1:8090/')
|
850
|
+
req = conn.get :keepalive => true
|
851
|
+
|
852
|
+
req.callback do
|
853
|
+
req = conn.get
|
854
|
+
|
855
|
+
req.callback { failed(http) }
|
856
|
+
req.errback do
|
857
|
+
req.error.should match('connection closed by server')
|
858
|
+
EventMachine.stop
|
859
|
+
end
|
860
|
+
end
|
861
|
+
}
|
862
|
+
end
|
863
|
+
|
864
|
+
it 'should handle malformed Content-Type header repetitions' do
|
865
|
+
EventMachine.run {
|
866
|
+
response =<<-HTTP.gsub(/^ +/, '').strip
|
867
|
+
HTTP/1.0 200 OK
|
868
|
+
Content-Type: text/plain; charset=iso-8859-1
|
869
|
+
Content-Type: text/plain; charset=utf-8
|
870
|
+
Content-Length: 5
|
871
|
+
Connection: close
|
872
|
+
|
873
|
+
Hello
|
874
|
+
HTTP
|
875
|
+
|
876
|
+
@s = StubServer.new(response)
|
877
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8081/').get
|
878
|
+
http.errback { failed(http) }
|
879
|
+
http.callback {
|
880
|
+
http.content_charset.should == Encoding::ISO_8859_1 if defined? Encoding
|
881
|
+
EventMachine.stop
|
882
|
+
}
|
883
|
+
}
|
884
|
+
end
|
885
|
+
|
886
|
+
it "should allow indifferent access to headers" do
|
887
|
+
EventMachine.run {
|
888
|
+
response =<<-HTTP.gsub(/^ +/, '').strip
|
889
|
+
HTTP/1.0 200 OK
|
890
|
+
Content-Type: text/plain; charset=utf-8
|
891
|
+
X-Custom-Header: foo
|
892
|
+
Content-Length: 5
|
893
|
+
Connection: close
|
894
|
+
|
895
|
+
Hello
|
896
|
+
HTTP
|
897
|
+
|
898
|
+
@s = StubServer.new(response)
|
899
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8081/').get
|
900
|
+
http.errback { failed(http) }
|
901
|
+
http.callback {
|
902
|
+
http.response_header["Content-Type"].should == "text/plain; charset=utf-8"
|
903
|
+
http.response_header["CONTENT_TYPE"].should == "text/plain; charset=utf-8"
|
904
|
+
|
905
|
+
http.response_header["Content-Length"].should == "5"
|
906
|
+
http.response_header["CONTENT_LENGTH"].should == "5"
|
907
|
+
|
908
|
+
http.response_header["X-Custom-Header"].should == "foo"
|
909
|
+
http.response_header["X_CUSTOM_HEADER"].should == "foo"
|
910
|
+
|
911
|
+
EventMachine.stop
|
912
|
+
}
|
913
|
+
}
|
914
|
+
end
|
915
|
+
|
916
|
+
it "should close connection on invalid HTTP response" do
|
917
|
+
EventMachine.run {
|
918
|
+
response =<<-HTTP.gsub(/^ +/, '').strip
|
919
|
+
HTTP/1.1 403 Forbidden
|
920
|
+
Content-Type: text/plain
|
921
|
+
Content-Length: 13
|
922
|
+
|
923
|
+
Access Denied
|
924
|
+
|
925
|
+
HTTP/1.1 403 Forbidden
|
926
|
+
Content-Type: text/plain
|
927
|
+
Content-Length: 13
|
928
|
+
|
929
|
+
Access Denied
|
930
|
+
HTTP
|
931
|
+
|
932
|
+
@s = StubServer.new(response)
|
933
|
+
lambda {
|
934
|
+
conn = EventMachine::HttpRequest.new('http://127.0.0.1:8081/')
|
935
|
+
req = conn.get
|
936
|
+
req.errback { failed(http) }
|
937
|
+
req.callback { EM.stop }
|
938
|
+
}.should_not raise_error
|
939
|
+
|
940
|
+
}
|
941
|
+
end
|
942
|
+
|
943
|
+
context "User-Agent" do
|
944
|
+
it 'should default to "EventMachine HttpClient"' do
|
945
|
+
EventMachine.run {
|
946
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/echo-user-agent').get
|
947
|
+
|
948
|
+
http.errback { failed(http) }
|
949
|
+
http.callback {
|
950
|
+
http.response.should == '"EventMachine HttpClient"'
|
951
|
+
EventMachine.stop
|
952
|
+
}
|
953
|
+
}
|
954
|
+
end
|
955
|
+
|
956
|
+
it 'should keep header if given empty string' do
|
957
|
+
EventMachine.run {
|
958
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/echo-user-agent').get(:head => { 'user-agent'=>'' })
|
959
|
+
|
960
|
+
http.errback { failed(http) }
|
961
|
+
http.callback {
|
962
|
+
http.response.should == '""'
|
963
|
+
EventMachine.stop
|
964
|
+
}
|
965
|
+
}
|
966
|
+
end
|
967
|
+
|
968
|
+
it 'should ommit header if given nil' do
|
969
|
+
EventMachine.run {
|
970
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8090/echo-user-agent').get(:head => { 'user-agent'=>nil })
|
971
|
+
|
972
|
+
http.errback { failed(http) }
|
973
|
+
http.callback {
|
974
|
+
http.response.should == 'nil'
|
975
|
+
EventMachine.stop
|
976
|
+
}
|
977
|
+
}
|
978
|
+
end
|
979
|
+
end
|
980
|
+
|
981
|
+
context "IPv6" do
|
982
|
+
it "should perform successful GET" do
|
983
|
+
EventMachine.run {
|
984
|
+
@s = StubServer.new({
|
985
|
+
response: "HTTP/1.1 200 OK\r\n\r\nHello IPv6",
|
986
|
+
port: 8091,
|
987
|
+
host: '::1',
|
988
|
+
})
|
989
|
+
http = EventMachine::HttpRequest.new('http://[::1]:8091/').get
|
990
|
+
|
991
|
+
http.errback { failed(http) }
|
992
|
+
http.callback {
|
993
|
+
http.response_header.status.should == 200
|
994
|
+
http.response.should match(/Hello IPv6/)
|
995
|
+
EventMachine.stop
|
996
|
+
}
|
997
|
+
}
|
998
|
+
end
|
999
|
+
end
|
1000
|
+
end
|