net-http-pipeline 0.1 → 1.0
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.
- data.tar.gz.sig +0 -0
- data/.autotest +3 -18
- data/.gemtest +0 -0
- data/History.txt +12 -0
- data/README.txt +5 -3
- data/Rakefile +3 -0
- data/lib/net/http/pipeline.rb +293 -26
- data/test/test_net_http_pipeline.rb +489 -36
- metadata +18 -16
- metadata.gz.sig +0 -0
data.tar.gz.sig
CHANGED
Binary file
|
data/.autotest
CHANGED
@@ -2,22 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'autotest/restart'
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
# at.libs << ":../some/external"
|
9
|
-
#
|
10
|
-
# at.add_exception 'vendor'
|
11
|
-
#
|
12
|
-
# at.add_mapping(/dependency.rb/) do |f, _|
|
13
|
-
# at.files_matching(/test_.*rb$/)
|
14
|
-
# end
|
15
|
-
#
|
16
|
-
# %w(TestA TestB).each do |klass|
|
17
|
-
# at.extra_class_map[klass] = "test/test_misc.rb"
|
18
|
-
# end
|
19
|
-
# end
|
5
|
+
Autotest.add_hook :initialize do |at|
|
6
|
+
at.testlib = 'minitest/autorun'
|
7
|
+
end
|
20
8
|
|
21
|
-
# Autotest.add_hook :run_command do |at|
|
22
|
-
# system "rake build"
|
23
|
-
# end
|
data/.gemtest
ADDED
File without changes
|
data/History.txt
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
=== 1.0 / 2011-03-29
|
2
|
+
|
3
|
+
* API change
|
4
|
+
* Net::HTTP::Pipeline#pipeline requires an Array of Net::HTTPRequests now.
|
5
|
+
* Major enhancement
|
6
|
+
* If a sequence of requests contains a non-idempotent request #pipeline now
|
7
|
+
waits for a response to the previous request.
|
8
|
+
* Minor enhancements
|
9
|
+
* Check for HTTP/1.1 and persistent connections before attempting
|
10
|
+
pipelining
|
11
|
+
* Added Net::HTTP#persistent= to avoid pipelining-capability check.
|
12
|
+
|
1
13
|
=== 0.1 / 2010-12-07
|
2
14
|
|
3
15
|
* Minor enhancement
|
data/README.txt
CHANGED
@@ -5,8 +5,9 @@
|
|
5
5
|
|
6
6
|
== DESCRIPTION:
|
7
7
|
|
8
|
-
An HTTP/1.1 pipelining implementation atop Net::HTTP.
|
9
|
-
|
8
|
+
An HTTP/1.1 pipelining implementation atop Net::HTTP. A pipelined connection
|
9
|
+
sends multiple requests to the HTTP server without waiting for the responses.
|
10
|
+
The server will respond in-order.
|
10
11
|
|
11
12
|
== FEATURES/PROBLEMS:
|
12
13
|
|
@@ -21,8 +22,9 @@ proof of concept.
|
|
21
22
|
Net::HTTP.start 'localhost' do |http|
|
22
23
|
req1 = Net::HTTP::Get.new '/'
|
23
24
|
req2 = Net::HTTP::Get.new '/'
|
25
|
+
req3 = Net::HTTP::Get.new '/'
|
24
26
|
|
25
|
-
http.pipeline req1, req2 do |res|
|
27
|
+
http.pipeline [req1, req2, req3] do |res|
|
26
28
|
puts res.code
|
27
29
|
puts res.body[0..60].inspect
|
28
30
|
puts
|
data/Rakefile
CHANGED
data/lib/net/http/pipeline.rb
CHANGED
@@ -1,21 +1,28 @@
|
|
1
1
|
require 'net/http'
|
2
2
|
|
3
3
|
##
|
4
|
-
# An HTTP/1.1 pipelining implementation atop Net::HTTP.
|
4
|
+
# An HTTP/1.1 pipelining implementation atop Net::HTTP. This library is
|
5
5
|
# compliant with RFC 2616 8.1.2.2.
|
6
6
|
#
|
7
|
-
# Pipeline allows
|
8
|
-
# HTTP/1.1 server.
|
7
|
+
# Pipeline allows you to create a bunch of requests then send them all to an
|
8
|
+
# HTTP/1.1 server without waiting for responses. The server will return HTTP
|
9
|
+
# responses in-order.
|
10
|
+
#
|
11
|
+
# Net::HTTP::Pipeline does not assume the server supports pipelining. If you
|
12
|
+
# know the server supports pipelining you can set Net::HTTP#pipelining to
|
13
|
+
# true.
|
9
14
|
#
|
10
15
|
# = Example
|
11
16
|
#
|
12
17
|
# require 'net/http/pipeline'
|
13
18
|
#
|
14
19
|
# Net::HTTP.start 'localhost' do |http|
|
15
|
-
#
|
16
|
-
#
|
20
|
+
# requests = []
|
21
|
+
# requests << Net::HTTP::Get.new('/')
|
22
|
+
# requests << Net::HTTP::Get.new('/')
|
23
|
+
# requests << Net::HTTP::Get.new('/')
|
17
24
|
#
|
18
|
-
# http.pipeline
|
25
|
+
# http.pipeline requests do |res|
|
19
26
|
# puts res.code
|
20
27
|
# puts res.body[0..60].inspect
|
21
28
|
# puts
|
@@ -24,49 +31,225 @@ require 'net/http'
|
|
24
31
|
|
25
32
|
module Net::HTTP::Pipeline
|
26
33
|
|
27
|
-
|
34
|
+
##
|
35
|
+
# The version of net-http-pipeline you are using
|
36
|
+
|
37
|
+
VERSION = '1.0'
|
28
38
|
|
29
39
|
##
|
30
40
|
# Pipeline error class
|
31
41
|
|
32
42
|
class Error < RuntimeError
|
43
|
+
|
44
|
+
##
|
45
|
+
# Remaining requests that have not been sent to the HTTP server
|
46
|
+
|
47
|
+
attr_reader :requests
|
48
|
+
|
49
|
+
##
|
50
|
+
# Retrieved responses up to the error point
|
51
|
+
|
52
|
+
attr_reader :responses
|
53
|
+
|
54
|
+
##
|
55
|
+
# Creates a new Error with +message+, a list of +requests+ that have not
|
56
|
+
# been sent to the server and a list of +responses+ that have been
|
57
|
+
# retrieved from the server.
|
58
|
+
|
59
|
+
def initialize message, requests, responses
|
60
|
+
super message
|
61
|
+
|
62
|
+
@requests = requests
|
63
|
+
@responses = responses
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
##
|
69
|
+
# Raised when an invalid version is given
|
70
|
+
|
71
|
+
class VersionError < Error
|
72
|
+
##
|
73
|
+
# Creates a new VersionError with a list of +requests+ that have not been
|
74
|
+
# sent to the server and a list of +responses+ that have been retrieved
|
75
|
+
# from the server.
|
76
|
+
|
77
|
+
def initialize requests, responses
|
78
|
+
super 'HTTP/1.1 or newer required', requests, responses
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
##
|
83
|
+
# Raised when the server appears to not support persistent connections
|
84
|
+
|
85
|
+
class PersistenceError < Error
|
86
|
+
##
|
87
|
+
# Creates a new PersistenceError with a list of +requests+ that have not
|
88
|
+
# been sent to the server and a list of +responses+ that have been
|
89
|
+
# retrieved from the server.
|
90
|
+
|
91
|
+
def initialize requests, responses
|
92
|
+
super 'persistent connections required', requests, responses
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
##
|
97
|
+
# Raised when the server appears to not support pipelining connections
|
98
|
+
|
99
|
+
class PipelineError < Error
|
100
|
+
##
|
101
|
+
# Creates a new PipelineError with a list of +requests+ that have not been
|
102
|
+
# sent to the server and a list of +responses+ that have been retrieved
|
103
|
+
# from the server.
|
104
|
+
|
105
|
+
def initialize requests, responses
|
106
|
+
super 'pipeline connections are not supported', requests, responses
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
##
|
111
|
+
# Raised if an error occurs while reading responses.
|
112
|
+
|
113
|
+
class ResponseError < Error
|
114
|
+
##
|
115
|
+
# The original exception
|
116
|
+
|
117
|
+
attr_accessor :original
|
118
|
+
|
119
|
+
##
|
120
|
+
# Creates a new ResponseError with an original +exception+, a list of
|
121
|
+
# +requests+ that were in-flight and a list of +responses+ that have been
|
122
|
+
# retrieved from the server.
|
123
|
+
|
124
|
+
def initialize exception, requests, responses
|
125
|
+
@original = exception
|
126
|
+
message = "error handling responses: #{original} (#{original.class})"
|
127
|
+
super message, requests, responses
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
##
|
132
|
+
# Pipelining capability accessor.
|
133
|
+
#
|
134
|
+
# Pipeline assumes servers do not support pipelining by default. The first
|
135
|
+
# request is not pipelined while Pipeline ensures that the server is
|
136
|
+
# HTTP/1.1 or newer and defaults to persistent connections.
|
137
|
+
#
|
138
|
+
# If you know the server is HTTP/1.1 and defaults to persistent
|
139
|
+
# connections you can set this to true when you create the Net::HTTP object.
|
140
|
+
|
141
|
+
attr_accessor :pipelining
|
142
|
+
|
143
|
+
##
|
144
|
+
# Is +req+ idempotent according to RFC 2616?
|
145
|
+
|
146
|
+
def idempotent? req
|
147
|
+
case req
|
148
|
+
when Net::HTTP::Delete, Net::HTTP::Get, Net::HTTP::Head,
|
149
|
+
Net::HTTP::Options, Net::HTTP::Put, Net::HTTP::Trace then
|
150
|
+
true
|
151
|
+
end
|
33
152
|
end
|
34
153
|
|
35
154
|
##
|
36
155
|
# Pipelines +requests+ to the HTTP server yielding responses if a block is
|
37
156
|
# given. Returns all responses recieved.
|
38
157
|
#
|
39
|
-
#
|
158
|
+
# The Net::HTTP connection must be started before calling #pipeline.
|
159
|
+
#
|
160
|
+
# Raises an exception if the connection is not pipeline-capable or if the
|
40
161
|
# HTTP session has not been started.
|
41
162
|
|
42
|
-
def pipeline
|
43
|
-
|
44
|
-
@curr_http_version >= '1.1'
|
45
|
-
raise Error, 'Net::HTTP not started' unless started?
|
163
|
+
def pipeline requests, &block # :yields: response
|
164
|
+
responses = []
|
46
165
|
|
47
|
-
|
48
|
-
|
49
|
-
req.exec @socket, @curr_http_version, edit_path(req.path)
|
50
|
-
end
|
166
|
+
raise Error.new('Net::HTTP not started', requests, responses) unless
|
167
|
+
started?
|
51
168
|
|
52
|
-
responses
|
169
|
+
raise VersionError.new(requests, responses) if '1.1' > @curr_http_version
|
170
|
+
|
171
|
+
pipeline_check requests, responses, &block
|
172
|
+
|
173
|
+
retried = responses.length
|
53
174
|
|
54
|
-
requests.
|
175
|
+
until requests.empty? do
|
55
176
|
begin
|
56
|
-
|
57
|
-
end while res.kind_of? Net::HTTPContinue
|
177
|
+
in_flight = pipeline_send requests
|
58
178
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
179
|
+
pipeline_receive in_flight, responses, &block
|
180
|
+
rescue Net::HTTP::Pipeline::ResponseError => e
|
181
|
+
e.requests.reverse_each do |request|
|
182
|
+
requests.unshift request
|
183
|
+
end
|
184
|
+
|
185
|
+
raise if responses.length == retried or not idempotent? requests.first
|
186
|
+
|
187
|
+
retried = responses.length
|
188
|
+
|
189
|
+
pipeline_reset requests, responses
|
63
190
|
|
64
|
-
|
191
|
+
retry
|
192
|
+
end
|
65
193
|
end
|
66
194
|
|
67
195
|
responses
|
68
196
|
end
|
69
197
|
|
198
|
+
##
|
199
|
+
# Ensures the connection supports pipelining.
|
200
|
+
#
|
201
|
+
# If the server has not been tested for pipelining support one of the
|
202
|
+
# +requests+ will be consumed and placed in +responses+.
|
203
|
+
#
|
204
|
+
# A VersionError will be raised if the server is not HTTP/1.1 or newer.
|
205
|
+
#
|
206
|
+
# A PersistenceError will be raised if the server does not support
|
207
|
+
# persistent connections.
|
208
|
+
#
|
209
|
+
# A PipelineError will be raised if the it was previously determined that
|
210
|
+
# the server does not support pipelining.
|
211
|
+
|
212
|
+
def pipeline_check requests, responses
|
213
|
+
if instance_variable_defined? :@pipelining then
|
214
|
+
return if @pipelining
|
215
|
+
raise PipelineError.new(requests, responses) unless @pipelining
|
216
|
+
else
|
217
|
+
@pipelining = false
|
218
|
+
end
|
219
|
+
|
220
|
+
req = requests.shift
|
221
|
+
retried = false
|
222
|
+
|
223
|
+
begin
|
224
|
+
res = request req
|
225
|
+
rescue Timeout::Error, EOFError, Errno::ECONNABORTED, Errno::ECONNRESET,
|
226
|
+
Errno::EPIPE, Net::HTTPBadResponse => e
|
227
|
+
if retried then
|
228
|
+
requests.unshift req
|
229
|
+
raise ResponseError.new(e, requests, responses)
|
230
|
+
end
|
231
|
+
|
232
|
+
retried = true
|
233
|
+
|
234
|
+
pipeline_reset requests, responses
|
235
|
+
|
236
|
+
retry
|
237
|
+
end
|
238
|
+
|
239
|
+
responses << res
|
240
|
+
|
241
|
+
yield res if block_given?
|
242
|
+
|
243
|
+
@pipelining = pipeline_keep_alive? res
|
244
|
+
|
245
|
+
if '1.1' > @curr_http_version then
|
246
|
+
@pipelining = false
|
247
|
+
raise VersionError.new(requests, responses)
|
248
|
+
elsif not @pipelining then
|
249
|
+
raise PersistenceError.new(requests, responses)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
70
253
|
##
|
71
254
|
# Updates the HTTP version and ensures the connection has keep-alive.
|
72
255
|
|
@@ -83,6 +266,14 @@ module Net::HTTP::Pipeline
|
|
83
266
|
end
|
84
267
|
end
|
85
268
|
|
269
|
+
##
|
270
|
+
# Closes the connection and rescues any IOErrors this may cause
|
271
|
+
|
272
|
+
def pipeline_finish
|
273
|
+
finish
|
274
|
+
rescue IOError
|
275
|
+
end
|
276
|
+
|
86
277
|
if Net::HTTPResponse.allocate.respond_to? :connection_close? then
|
87
278
|
##
|
88
279
|
# Checks for an connection close header
|
@@ -92,10 +283,86 @@ module Net::HTTP::Pipeline
|
|
92
283
|
end
|
93
284
|
else
|
94
285
|
def pipeline_keep_alive? res
|
95
|
-
not
|
286
|
+
not res['connection'].to_s =~ /close/i
|
96
287
|
end
|
97
288
|
end
|
98
289
|
|
290
|
+
##
|
291
|
+
# Receives HTTP responses for +in_flight+ requests and adds them to
|
292
|
+
# +responses+
|
293
|
+
|
294
|
+
def pipeline_receive in_flight, responses
|
295
|
+
while req = in_flight.shift do
|
296
|
+
begin
|
297
|
+
begin
|
298
|
+
res = Net::HTTPResponse.read_new @socket
|
299
|
+
end while res.kind_of? Net::HTTPContinue
|
300
|
+
|
301
|
+
res.reading_body @socket, req.response_body_permitted? do
|
302
|
+
responses << res
|
303
|
+
yield res if block_given?
|
304
|
+
end
|
305
|
+
|
306
|
+
pipeline_end_transport res
|
307
|
+
rescue StandardError, Timeout::Error
|
308
|
+
in_flight.unshift req
|
309
|
+
raise
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
responses
|
314
|
+
rescue Timeout::Error, EOFError, Errno::ECONNABORTED, Errno::ECONNRESET,
|
315
|
+
Errno::EPIPE, Net::HTTPBadResponse => e
|
316
|
+
pipeline_finish
|
317
|
+
|
318
|
+
raise ResponseError.new(e, in_flight, responses)
|
319
|
+
end
|
320
|
+
|
321
|
+
##
|
322
|
+
# Resets this connection
|
323
|
+
|
324
|
+
def pipeline_reset requests, responses
|
325
|
+
pipeline_finish
|
326
|
+
|
327
|
+
start
|
328
|
+
rescue Errno::ECONNREFUSED
|
329
|
+
raise Error.new("connection refused: #{address}:#{port}", requests,
|
330
|
+
responses)
|
331
|
+
rescue Errno::EHOSTDOWN
|
332
|
+
raise Error.new("host down: #{address}:#{port}", requests, responses)
|
333
|
+
end
|
334
|
+
|
335
|
+
##
|
336
|
+
# Sends +requests+ to the HTTP server and removes them from the +requests+
|
337
|
+
# list. Returns the requests that have been pipelined and are in-flight.
|
338
|
+
#
|
339
|
+
# If a non-idempotent request is first in +requests+ it will be sent and no
|
340
|
+
# further requests will be pipelined.
|
341
|
+
#
|
342
|
+
# If a non-idempotent request is encountered after an idempotent request it
|
343
|
+
# will not be sent.
|
344
|
+
|
345
|
+
def pipeline_send requests
|
346
|
+
in_flight = []
|
347
|
+
|
348
|
+
while req = requests.shift do
|
349
|
+
idempotent = idempotent? req
|
350
|
+
|
351
|
+
unless idempotent or in_flight.empty? then
|
352
|
+
requests.unshift req
|
353
|
+
break
|
354
|
+
end
|
355
|
+
|
356
|
+
begin_transport req
|
357
|
+
req.exec @socket, @curr_http_version, edit_path(req.path)
|
358
|
+
in_flight << req
|
359
|
+
|
360
|
+
break unless idempotent
|
361
|
+
end
|
362
|
+
|
363
|
+
in_flight
|
364
|
+
end
|
365
|
+
|
99
366
|
end
|
100
367
|
|
101
368
|
class Net::HTTP
|
@@ -4,12 +4,27 @@ require 'stringio'
|
|
4
4
|
|
5
5
|
class TestNetHttpPipeline < MiniTest::Unit::TestCase
|
6
6
|
|
7
|
+
include Net::HTTP::Pipeline
|
8
|
+
|
9
|
+
def setup
|
10
|
+
@curr_http_version = '1.1'
|
11
|
+
@started = true
|
12
|
+
|
13
|
+
@get1 = Net::HTTP::Get.new '/'
|
14
|
+
@get2 = Net::HTTP::Get.new '/'
|
15
|
+
@get3 = Net::HTTP::Get.new '/'
|
16
|
+
@post = Net::HTTP::Post.new '/'
|
17
|
+
end
|
18
|
+
|
7
19
|
##
|
8
20
|
# Net::BufferedIO stub
|
9
21
|
|
10
22
|
class Buffer
|
11
23
|
attr_accessor :read_io, :write_io
|
12
|
-
def initialize
|
24
|
+
def initialize exception = nil, immediate = false
|
25
|
+
@readline = immediate
|
26
|
+
@exception = exception
|
27
|
+
|
13
28
|
@read_io = StringIO.new
|
14
29
|
@write_io = StringIO.new
|
15
30
|
@closed = false
|
@@ -34,6 +49,8 @@ class TestNetHttpPipeline < MiniTest::Unit::TestCase
|
|
34
49
|
end
|
35
50
|
|
36
51
|
def readline
|
52
|
+
raise @exception if @exception and @readline
|
53
|
+
@readline = true
|
37
54
|
@read_io.readline.chomp "\r\n"
|
38
55
|
end
|
39
56
|
|
@@ -50,15 +67,8 @@ class TestNetHttpPipeline < MiniTest::Unit::TestCase
|
|
50
67
|
end
|
51
68
|
end
|
52
69
|
|
53
|
-
include Net::HTTP::Pipeline
|
54
|
-
|
55
70
|
attr_writer :started
|
56
71
|
|
57
|
-
def setup
|
58
|
-
@curr_http_version = '1.1'
|
59
|
-
@started = true
|
60
|
-
end
|
61
|
-
|
62
72
|
def D(*) end
|
63
73
|
|
64
74
|
def begin_transport req
|
@@ -68,14 +78,28 @@ class TestNetHttpPipeline < MiniTest::Unit::TestCase
|
|
68
78
|
path
|
69
79
|
end
|
70
80
|
|
71
|
-
def
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
81
|
+
def finish
|
82
|
+
@socket.close
|
83
|
+
end
|
84
|
+
|
85
|
+
def http_get
|
86
|
+
get = []
|
87
|
+
get << 'GET / HTTP/1.1'
|
88
|
+
get << 'Accept: */*'
|
89
|
+
get << 'User-Agent: Ruby' if RUBY_VERSION > '1.9'
|
90
|
+
get.push nil, nil
|
91
|
+
|
92
|
+
get.join "\r\n"
|
93
|
+
end
|
77
94
|
|
78
|
-
|
95
|
+
def http_post
|
96
|
+
get = []
|
97
|
+
get << 'POST / HTTP/1.1'
|
98
|
+
get << 'Accept: */*'
|
99
|
+
get << 'User-Agent: Ruby' if RUBY_VERSION > '1.9'
|
100
|
+
get.push nil, nil
|
101
|
+
|
102
|
+
get.join "\r\n"
|
79
103
|
end
|
80
104
|
|
81
105
|
def http_response body, *extra_header
|
@@ -88,6 +112,31 @@ class TestNetHttpPipeline < MiniTest::Unit::TestCase
|
|
88
112
|
http_response.join("\r\n") << body
|
89
113
|
end
|
90
114
|
|
115
|
+
def http_bad_response
|
116
|
+
http_response = []
|
117
|
+
http_response << 'HTP/1.1 200 OK'
|
118
|
+
http_response << 'Content-Length: 0'
|
119
|
+
http_response.push nil, nil # Array chomps on #join
|
120
|
+
|
121
|
+
http_response.join("\r\n")
|
122
|
+
end
|
123
|
+
|
124
|
+
def request req
|
125
|
+
req.exec @socket, @curr_http_version, edit_path(req.path)
|
126
|
+
|
127
|
+
res = Net::HTTPResponse.read_new @socket
|
128
|
+
|
129
|
+
res.reading_body @socket, req.response_body_permitted? do
|
130
|
+
yield res if block_given?
|
131
|
+
end
|
132
|
+
|
133
|
+
@curr_http_version = res.http_version
|
134
|
+
|
135
|
+
@socket.close unless pipeline_keep_alive? res
|
136
|
+
|
137
|
+
res
|
138
|
+
end
|
139
|
+
|
91
140
|
def response
|
92
141
|
r = Net::HTTPResponse.allocate
|
93
142
|
def r.http_version() Net::HTTP::HTTPVersion end
|
@@ -98,70 +147,370 @@ class TestNetHttpPipeline < MiniTest::Unit::TestCase
|
|
98
147
|
r
|
99
148
|
end
|
100
149
|
|
150
|
+
def start
|
151
|
+
end
|
152
|
+
|
101
153
|
def started?() @started end
|
102
154
|
|
155
|
+
# tests start
|
156
|
+
|
157
|
+
def test_idempotent_eh
|
158
|
+
http = Net::HTTP.new 'localhost'
|
159
|
+
|
160
|
+
assert http.idempotent? Net::HTTP::Delete.new '/'
|
161
|
+
assert http.idempotent? Net::HTTP::Get.new '/'
|
162
|
+
assert http.idempotent? Net::HTTP::Head.new '/'
|
163
|
+
assert http.idempotent? Net::HTTP::Options.new '/'
|
164
|
+
assert http.idempotent? Net::HTTP::Put.new '/'
|
165
|
+
assert http.idempotent? Net::HTTP::Trace.new '/'
|
166
|
+
|
167
|
+
refute http.idempotent? Net::HTTP::Post.new '/'
|
168
|
+
end
|
169
|
+
|
103
170
|
def test_pipeline
|
104
171
|
@socket = Buffer.new
|
105
|
-
|
106
172
|
@socket.read_io.write http_response('Worked 1!')
|
107
173
|
@socket.read_io.write http_response('Worked 2!')
|
108
|
-
|
109
|
-
req1 = Net::HTTP::Get.new '/'
|
110
|
-
req2 = Net::HTTP::Get.new '/'
|
111
|
-
|
112
174
|
@socket.start
|
113
175
|
|
114
|
-
|
176
|
+
requests = [@get1, @get2]
|
177
|
+
|
178
|
+
responses = pipeline requests
|
115
179
|
|
116
180
|
@socket.finish
|
117
181
|
|
118
|
-
expected =
|
119
|
-
expected << http_request
|
120
|
-
expected << http_request
|
182
|
+
expected = http_get * 2
|
121
183
|
|
122
184
|
assert_equal expected, @socket.write_io.read
|
123
185
|
refute @socket.closed?
|
124
186
|
|
125
187
|
assert_equal 'Worked 1!', responses.first.body
|
126
188
|
assert_equal 'Worked 2!', responses.last.body
|
189
|
+
|
190
|
+
assert_empty requests
|
127
191
|
end
|
128
192
|
|
129
|
-
def
|
193
|
+
def test_pipeline_block
|
130
194
|
@socket = Buffer.new
|
131
|
-
|
132
|
-
@socket.read_io.write http_response('Worked
|
133
|
-
|
134
|
-
req1 = Net::HTTP::Get.new '/'
|
135
|
-
|
195
|
+
@socket.read_io.write http_response('Worked 1!')
|
196
|
+
@socket.read_io.write http_response('Worked 2!')
|
136
197
|
@socket.start
|
137
198
|
|
138
|
-
|
199
|
+
requests = [@get1, @get2]
|
200
|
+
responses = []
|
201
|
+
|
202
|
+
pipeline requests do |response| responses << response end
|
139
203
|
|
140
204
|
@socket.finish
|
141
205
|
|
142
|
-
|
206
|
+
assert_equal 'Worked 1!', responses.first.body
|
207
|
+
assert_equal 'Worked 2!', responses.last.body
|
143
208
|
end
|
144
209
|
|
145
210
|
def test_pipeline_http_1_0
|
146
211
|
@curr_http_version = '1.0'
|
147
212
|
|
148
|
-
|
149
|
-
|
213
|
+
@socket = Buffer.new
|
214
|
+
@socket.read_io.write http_response('Worked 1!', 'Connection: close')
|
215
|
+
@socket.start
|
216
|
+
|
217
|
+
e = assert_raises Net::HTTP::Pipeline::VersionError do
|
218
|
+
pipeline [@get1, @get2]
|
150
219
|
end
|
151
220
|
|
152
|
-
assert_equal
|
221
|
+
assert_equal [@get1, @get2], e.requests
|
222
|
+
assert_empty e.responses
|
223
|
+
end
|
224
|
+
|
225
|
+
def test_pipeline_non_idempotent
|
226
|
+
@socket = Buffer.new
|
227
|
+
@socket.read_io.write http_response('Worked 1!')
|
228
|
+
@socket.read_io.write http_response('Worked 2!')
|
229
|
+
@socket.read_io.write http_response('Worked 3!')
|
230
|
+
@socket.read_io.write http_response('Worked 4!')
|
231
|
+
@socket.start
|
232
|
+
|
233
|
+
responses = pipeline [@get1, @get2, @post, @get3]
|
234
|
+
|
235
|
+
@socket.finish
|
236
|
+
|
237
|
+
expected = ''
|
238
|
+
expected << http_get * 2
|
239
|
+
expected << http_post
|
240
|
+
expected << http_get
|
241
|
+
|
242
|
+
assert_equal expected, @socket.write_io.read
|
243
|
+
refute @socket.closed?
|
244
|
+
|
245
|
+
assert_equal 'Worked 1!', responses.shift.body
|
246
|
+
assert_equal 'Worked 2!', responses.shift.body
|
247
|
+
assert_equal 'Worked 3!', responses.shift.body
|
248
|
+
assert_equal 'Worked 4!', responses.shift.body
|
249
|
+
|
250
|
+
assert responses.empty?
|
153
251
|
end
|
154
252
|
|
155
253
|
def test_pipeline_not_started
|
156
254
|
@started = false
|
157
255
|
|
158
256
|
e = assert_raises Net::HTTP::Pipeline::Error do
|
159
|
-
pipeline
|
257
|
+
pipeline []
|
160
258
|
end
|
161
259
|
|
162
260
|
assert_equal 'Net::HTTP not started', e.message
|
163
261
|
end
|
164
262
|
|
263
|
+
def test_pipeline_retry
|
264
|
+
self.pipelining = true
|
265
|
+
@error_socket = Buffer.new Errno::ECONNRESET
|
266
|
+
@error_socket.read_io.write http_response('Worked 1!')
|
267
|
+
@error_socket.start
|
268
|
+
|
269
|
+
@good_socket = Buffer.new
|
270
|
+
@good_socket.read_io.write http_response('Worked 2!')
|
271
|
+
@good_socket.read_io.write http_response('Worked 3!')
|
272
|
+
@good_socket.start
|
273
|
+
|
274
|
+
@socket = @error_socket
|
275
|
+
|
276
|
+
def start
|
277
|
+
@socket = @good_socket
|
278
|
+
end
|
279
|
+
|
280
|
+
requests = [@get1, @get2, @get3]
|
281
|
+
|
282
|
+
responses = pipeline requests
|
283
|
+
|
284
|
+
@error_socket.finish
|
285
|
+
@good_socket.finish
|
286
|
+
|
287
|
+
assert_equal http_get * 3, @error_socket.write_io.read
|
288
|
+
assert @error_socket.closed?
|
289
|
+
|
290
|
+
assert_equal http_get * 2, @good_socket.write_io.read
|
291
|
+
refute @good_socket.closed?
|
292
|
+
|
293
|
+
assert_equal 'Worked 1!', responses.shift.body
|
294
|
+
assert_equal 'Worked 2!', responses.shift.body
|
295
|
+
assert_equal 'Worked 3!', responses.shift.body
|
296
|
+
assert_empty responses
|
297
|
+
|
298
|
+
assert_empty requests
|
299
|
+
end
|
300
|
+
|
301
|
+
def test_pipeline_retry_fail_post
|
302
|
+
self.pipelining = true
|
303
|
+
|
304
|
+
@socket = Buffer.new Errno::ECONNRESET, true
|
305
|
+
@socket.start
|
306
|
+
|
307
|
+
requests = [@post]
|
308
|
+
|
309
|
+
e = assert_raises Net::HTTP::Pipeline::ResponseError do
|
310
|
+
pipeline requests
|
311
|
+
end
|
312
|
+
|
313
|
+
@socket.finish
|
314
|
+
|
315
|
+
assert_equal http_post, @socket.write_io.read
|
316
|
+
|
317
|
+
assert_empty e.responses
|
318
|
+
|
319
|
+
assert_equal [@post], e.requests
|
320
|
+
end
|
321
|
+
|
322
|
+
def test_pipeline_retry_fail_different
|
323
|
+
self.pipelining = true
|
324
|
+
@error_socket = Buffer.new Errno::ECONNRESET
|
325
|
+
@error_socket.read_io.write http_response('Worked 1!')
|
326
|
+
@error_socket.start
|
327
|
+
|
328
|
+
@error_socket2 = Buffer.new Errno::ECONNRESET
|
329
|
+
@error_socket2.read_io.write http_response('Worked 2!')
|
330
|
+
@error_socket2.start
|
331
|
+
|
332
|
+
@good_socket = Buffer.new
|
333
|
+
@good_socket.read_io.write http_response('Worked 3!')
|
334
|
+
@good_socket.start
|
335
|
+
|
336
|
+
@socket = @error_socket
|
337
|
+
|
338
|
+
@sockets = [@error_socket2, @good_socket]
|
339
|
+
|
340
|
+
def start
|
341
|
+
@socket = @sockets.shift
|
342
|
+
end
|
343
|
+
|
344
|
+
requests = [@get1, @get2, @get3]
|
345
|
+
|
346
|
+
responses = pipeline requests
|
347
|
+
|
348
|
+
@error_socket.finish
|
349
|
+
@error_socket2.finish
|
350
|
+
@good_socket.finish
|
351
|
+
|
352
|
+
assert_equal http_get * 3, @error_socket.write_io.read
|
353
|
+
assert @error_socket.closed?
|
354
|
+
|
355
|
+
assert_equal http_get * 2, @error_socket2.write_io.read
|
356
|
+
assert @error_socket2.closed?
|
357
|
+
|
358
|
+
assert_equal http_get, @good_socket.write_io.read
|
359
|
+
refute @good_socket.closed?
|
360
|
+
|
361
|
+
assert_equal 'Worked 1!', responses.shift.body
|
362
|
+
assert_equal 'Worked 2!', responses.shift.body
|
363
|
+
assert_equal 'Worked 3!', responses.shift.body
|
364
|
+
assert_empty responses
|
365
|
+
|
366
|
+
assert_empty requests
|
367
|
+
end
|
368
|
+
|
369
|
+
def test_pipeline_retry_fail_same
|
370
|
+
self.pipelining = true
|
371
|
+
|
372
|
+
@error_socket = Buffer.new Errno::ECONNRESET
|
373
|
+
@error_socket.read_io.write http_response('Worked 1!')
|
374
|
+
@error_socket.start
|
375
|
+
|
376
|
+
@error_socket2 = Buffer.new Errno::ECONNRESET, true
|
377
|
+
@error_socket2.start
|
378
|
+
|
379
|
+
@socket = @error_socket
|
380
|
+
|
381
|
+
def start
|
382
|
+
@socket = @error_socket2
|
383
|
+
end
|
384
|
+
|
385
|
+
requests = [@get1, @get2, @get3]
|
386
|
+
|
387
|
+
e = assert_raises Net::HTTP::Pipeline::ResponseError do
|
388
|
+
pipeline requests
|
389
|
+
end
|
390
|
+
|
391
|
+
@error_socket.finish
|
392
|
+
@error_socket2.finish
|
393
|
+
|
394
|
+
assert_equal http_get * 3, @error_socket.write_io.read
|
395
|
+
assert @error_socket.closed?
|
396
|
+
|
397
|
+
assert_equal http_get * 2, @error_socket2.write_io.read
|
398
|
+
assert @error_socket2.closed?
|
399
|
+
|
400
|
+
responses = e.responses
|
401
|
+
assert_equal 'Worked 1!', responses.shift.body
|
402
|
+
assert_empty responses
|
403
|
+
|
404
|
+
assert_equal [@get2, @get3], e.requests
|
405
|
+
end
|
406
|
+
|
407
|
+
# end #pipeline tests
|
408
|
+
|
409
|
+
def test_pipeline_check
|
410
|
+
@socket = Buffer.new
|
411
|
+
@socket.read_io.write http_response('Worked 1!')
|
412
|
+
@socket.start
|
413
|
+
|
414
|
+
requests = [@get1, @get2]
|
415
|
+
responses = []
|
416
|
+
|
417
|
+
pipeline_check requests, responses
|
418
|
+
|
419
|
+
assert_equal [@get2], requests
|
420
|
+
assert_equal 1, responses.length
|
421
|
+
assert_equal 'Worked 1!', responses.first.body
|
422
|
+
assert pipelining
|
423
|
+
end
|
424
|
+
|
425
|
+
def test_pipeline_check_again
|
426
|
+
self.pipelining = false
|
427
|
+
|
428
|
+
@socket = Buffer.new
|
429
|
+
@socket.start
|
430
|
+
|
431
|
+
e = assert_raises Net::HTTP::Pipeline::PipelineError do
|
432
|
+
pipeline_check [@get1, @get2], []
|
433
|
+
end
|
434
|
+
|
435
|
+
assert_equal [@get1, @get2], e.requests
|
436
|
+
assert_empty e.responses
|
437
|
+
refute pipelining
|
438
|
+
end
|
439
|
+
|
440
|
+
def test_pipeline_check_bad_response
|
441
|
+
@socket = Buffer.new
|
442
|
+
@socket.read_io.write http_bad_response
|
443
|
+
@socket.start
|
444
|
+
|
445
|
+
@socket2 = Buffer.new
|
446
|
+
@socket2.read_io.write http_response('Worked 1!')
|
447
|
+
@socket2.start
|
448
|
+
|
449
|
+
def start
|
450
|
+
@socket = @socket2
|
451
|
+
end
|
452
|
+
|
453
|
+
requests = [@get1, @get2]
|
454
|
+
responses = []
|
455
|
+
|
456
|
+
pipeline_check requests, responses
|
457
|
+
|
458
|
+
assert_equal [@get2], requests
|
459
|
+
assert_equal 1, responses.length
|
460
|
+
assert_equal 'Worked 1!', responses.first.body
|
461
|
+
assert pipelining
|
462
|
+
end
|
463
|
+
|
464
|
+
def test_pipeline_check_http_1_0
|
465
|
+
@socket = Buffer.new
|
466
|
+
@socket.read_io.write <<-HTTP_1_0
|
467
|
+
HTTP/1.0 200 OK\r
|
468
|
+
Content-Length: 9\r
|
469
|
+
\r
|
470
|
+
Worked 1!
|
471
|
+
HTTP_1_0
|
472
|
+
@socket.start
|
473
|
+
|
474
|
+
e = assert_raises Net::HTTP::Pipeline::VersionError do
|
475
|
+
pipeline_check [@get1, @get2], []
|
476
|
+
end
|
477
|
+
|
478
|
+
assert_equal [@get2], e.requests
|
479
|
+
assert_equal 1, e.responses.length
|
480
|
+
assert_equal 'Worked 1!', e.responses.first.body
|
481
|
+
refute pipelining
|
482
|
+
end
|
483
|
+
|
484
|
+
def test_pipeline_check_non_persistent
|
485
|
+
@socket = Buffer.new
|
486
|
+
@socket.read_io.write http_response('Worked 1!', 'Connection: close')
|
487
|
+
@socket.start
|
488
|
+
|
489
|
+
e = assert_raises Net::HTTP::Pipeline::PersistenceError do
|
490
|
+
pipeline_check [@get1, @get2], []
|
491
|
+
end
|
492
|
+
|
493
|
+
assert_equal [@get2], e.requests
|
494
|
+
assert_equal 1, e.responses.length
|
495
|
+
assert_equal 'Worked 1!', e.responses.first.body
|
496
|
+
refute pipelining
|
497
|
+
end
|
498
|
+
|
499
|
+
def test_pipeline_check_pipelining
|
500
|
+
self.pipelining = true
|
501
|
+
@socket = Buffer.new
|
502
|
+
@socket.start
|
503
|
+
|
504
|
+
requests = [@get1, @get2]
|
505
|
+
responses = []
|
506
|
+
|
507
|
+
pipeline_check requests, responses
|
508
|
+
|
509
|
+
assert_equal [@get1, @get2], requests
|
510
|
+
assert_empty responses
|
511
|
+
assert pipelining
|
512
|
+
end
|
513
|
+
|
165
514
|
def test_pipeline_end_transport
|
166
515
|
@curr_http_version = nil
|
167
516
|
|
@@ -199,5 +548,109 @@ class TestNetHttpPipeline < MiniTest::Unit::TestCase
|
|
199
548
|
refute pipeline_keep_alive? res
|
200
549
|
end
|
201
550
|
|
551
|
+
def test_pipeline_receive
|
552
|
+
@socket = Buffer.new
|
553
|
+
@socket.read_io.write http_response('Worked 1!')
|
554
|
+
@socket.read_io.write http_response('Worked 2!')
|
555
|
+
@socket.start
|
556
|
+
|
557
|
+
in_flight = [@get1, @get2]
|
558
|
+
responses = []
|
559
|
+
|
560
|
+
r = pipeline_receive in_flight, responses
|
561
|
+
|
562
|
+
@socket.finish
|
563
|
+
|
564
|
+
refute @socket.closed?
|
565
|
+
|
566
|
+
assert_equal 'Worked 1!', responses.first.body
|
567
|
+
assert_equal 'Worked 2!', responses.last.body
|
568
|
+
|
569
|
+
assert_same r, responses
|
570
|
+
end
|
571
|
+
|
572
|
+
def test_pipeline_receive_bad_response
|
573
|
+
@socket = Buffer.new Errno::ECONNRESET
|
574
|
+
@socket.read_io.write http_response('Worked 1!')
|
575
|
+
@socket.start
|
576
|
+
|
577
|
+
in_flight = [@get1, @get2]
|
578
|
+
responses = []
|
579
|
+
|
580
|
+
e = assert_raises Net::HTTP::Pipeline::ResponseError do
|
581
|
+
pipeline_receive in_flight, responses
|
582
|
+
end
|
583
|
+
|
584
|
+
@socket.finish
|
585
|
+
|
586
|
+
assert @socket.closed?
|
587
|
+
|
588
|
+
assert_equal [@get2], e.requests
|
589
|
+
assert_equal 1, e.responses.length
|
590
|
+
assert_equal 'Worked 1!', e.responses.first.body
|
591
|
+
|
592
|
+
assert_kind_of Errno::ECONNRESET, e.original
|
593
|
+
end
|
594
|
+
|
595
|
+
def test_pipeline_receive_timeout
|
596
|
+
@socket = Buffer.new Timeout::Error
|
597
|
+
@socket.read_io.write http_response('Worked 1!')
|
598
|
+
@socket.start
|
599
|
+
|
600
|
+
in_flight = [@get1, @get2]
|
601
|
+
responses = []
|
602
|
+
|
603
|
+
e = assert_raises Net::HTTP::Pipeline::ResponseError do
|
604
|
+
pipeline_receive in_flight, responses
|
605
|
+
end
|
606
|
+
|
607
|
+
@socket.finish
|
608
|
+
|
609
|
+
assert @socket.closed?
|
610
|
+
|
611
|
+
assert_equal [@get2], e.requests
|
612
|
+
assert_equal 1, e.responses.length
|
613
|
+
assert_equal 'Worked 1!', e.responses.first.body
|
614
|
+
|
615
|
+
assert_kind_of Timeout::Error, e.original
|
616
|
+
end
|
617
|
+
|
618
|
+
def test_pipeline_send
|
619
|
+
@socket = Buffer.new
|
620
|
+
@socket.start
|
621
|
+
|
622
|
+
requests = [@get1, @get2, @post, @get3]
|
623
|
+
|
624
|
+
in_flight = pipeline_send requests
|
625
|
+
|
626
|
+
@socket.finish
|
627
|
+
|
628
|
+
assert_equal [@get1, @get2], in_flight
|
629
|
+
assert_equal [@post, @get3], requests
|
630
|
+
|
631
|
+
expected = ''
|
632
|
+
expected << http_get * 2
|
633
|
+
|
634
|
+
assert_equal expected, @socket.write_io.read
|
635
|
+
end
|
636
|
+
|
637
|
+
def test_pipeline_send_non_idempotent
|
638
|
+
@socket = Buffer.new
|
639
|
+
@socket.start
|
640
|
+
|
641
|
+
requests = [@post, @get3]
|
642
|
+
|
643
|
+
in_flight = pipeline_send requests
|
644
|
+
|
645
|
+
@socket.finish
|
646
|
+
|
647
|
+
assert_equal [@post], in_flight
|
648
|
+
assert_equal [@get3], requests
|
649
|
+
|
650
|
+
expected = http_post
|
651
|
+
|
652
|
+
assert_equal expected, @socket.write_io.read
|
653
|
+
end
|
654
|
+
|
202
655
|
end
|
203
656
|
|
metadata
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: net-http-pipeline
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
5
|
-
prerelease:
|
4
|
+
hash: 15
|
5
|
+
prerelease:
|
6
6
|
segments:
|
7
|
-
- 0
|
8
7
|
- 1
|
9
|
-
|
8
|
+
- 0
|
9
|
+
version: "1.0"
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Eric Hodel
|
@@ -35,7 +35,7 @@ cert_chain:
|
|
35
35
|
x52qPcexcYZR7w==
|
36
36
|
-----END CERTIFICATE-----
|
37
37
|
|
38
|
-
date:
|
38
|
+
date: 2011-03-29 00:00:00 -07:00
|
39
39
|
default_executable:
|
40
40
|
dependencies:
|
41
41
|
- !ruby/object:Gem::Dependency
|
@@ -46,12 +46,12 @@ dependencies:
|
|
46
46
|
requirements:
|
47
47
|
- - ">="
|
48
48
|
- !ruby/object:Gem::Version
|
49
|
-
hash:
|
49
|
+
hash: 11
|
50
50
|
segments:
|
51
|
-
-
|
52
|
-
- 5
|
51
|
+
- 2
|
53
52
|
- 0
|
54
|
-
|
53
|
+
- 2
|
54
|
+
version: 2.0.2
|
55
55
|
type: :development
|
56
56
|
version_requirements: *id001
|
57
57
|
- !ruby/object:Gem::Dependency
|
@@ -62,17 +62,18 @@ dependencies:
|
|
62
62
|
requirements:
|
63
63
|
- - ">="
|
64
64
|
- !ruby/object:Gem::Version
|
65
|
-
hash:
|
65
|
+
hash: 41
|
66
66
|
segments:
|
67
67
|
- 2
|
68
|
-
-
|
69
|
-
-
|
70
|
-
version: 2.
|
68
|
+
- 9
|
69
|
+
- 1
|
70
|
+
version: 2.9.1
|
71
71
|
type: :development
|
72
72
|
version_requirements: *id002
|
73
73
|
description: |-
|
74
|
-
An HTTP/1.1 pipelining implementation atop Net::HTTP.
|
75
|
-
|
74
|
+
An HTTP/1.1 pipelining implementation atop Net::HTTP. A pipelined connection
|
75
|
+
sends multiple requests to the HTTP server without waiting for the responses.
|
76
|
+
The server will respond in-order.
|
76
77
|
email:
|
77
78
|
- drbrain@segment7.net
|
78
79
|
executables: []
|
@@ -92,6 +93,7 @@ files:
|
|
92
93
|
- lib/net/http/pipeline.rb
|
93
94
|
- sample/two_get.rb
|
94
95
|
- test/test_net_http_pipeline.rb
|
96
|
+
- .gemtest
|
95
97
|
has_rdoc: true
|
96
98
|
homepage: http://seattlerb.rubyforge.org/net-http-pipeline
|
97
99
|
licenses: []
|
@@ -123,7 +125,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
123
125
|
requirements: []
|
124
126
|
|
125
127
|
rubyforge_project: net-http-pipeline
|
126
|
-
rubygems_version: 1.
|
128
|
+
rubygems_version: 1.6.2
|
127
129
|
signing_key:
|
128
130
|
specification_version: 3
|
129
131
|
summary: An HTTP/1.1 pipelining implementation atop Net::HTTP
|
metadata.gz.sig
CHANGED
Binary file
|