tap-http 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History +12 -0
- data/README +9 -70
- data/controllers/capture_controller.rb +55 -0
- data/lib/tap/http/get.rb +19 -0
- data/lib/tap/http/request.rb +263 -0
- data/lib/tap/http/submit.rb +31 -0
- data/lib/tap/test/http_test.rb +36 -121
- data/views/capture_controller/index.erb +30 -0
- metadata +17 -7
- data/cgi/echo.rb +0 -24
- data/cgi/http_to_yaml.rb +0 -108
- data/cgi/parse_http.rb +0 -129
- data/lib/tap/http/dispatch.rb +0 -411
- data/lib/tap/test/http_test/requests.rb +0 -194
data/lib/tap/http/dispatch.rb
DELETED
@@ -1,411 +0,0 @@
|
|
1
|
-
require 'tap/http/utils'
|
2
|
-
require 'net/http'
|
3
|
-
require 'thread'
|
4
|
-
|
5
|
-
module Tap
|
6
|
-
module Http
|
7
|
-
|
8
|
-
# :startdoc::manifest submits an http request
|
9
|
-
#
|
10
|
-
# Dispatch is a base class for submitting HTTP requests from a request
|
11
|
-
# hash. Multiple requests may be submitted on individual threads, up
|
12
|
-
# to a configurable limit.
|
13
|
-
#
|
14
|
-
# Request hashes are like the following:
|
15
|
-
#
|
16
|
-
# request_method: GET
|
17
|
-
# url: http://tap.rubyforge.org/
|
18
|
-
# headers: {}
|
19
|
-
# params: {}
|
20
|
-
# version: 1.1
|
21
|
-
#
|
22
|
-
# Missing fields are added from the task configuration. Note that since
|
23
|
-
# Dispatch takes hash inputs, it is often convenient to save requests in
|
24
|
-
# a .yml file and sequence dispatch with load:
|
25
|
-
#
|
26
|
-
# [requests.yml]
|
27
|
-
# - url: http://tap.rubyforge.org/
|
28
|
-
# - url: http://tap.rubyforge.org/about.html
|
29
|
-
#
|
30
|
-
# % rap load requests.yml --:i dispatch --+ dump
|
31
|
-
#
|
32
|
-
# :startdoc::manifest-end
|
33
|
-
# === Dispatch Methods
|
34
|
-
#
|
35
|
-
# Dispatch itself provides methods for constructing and submitting get and
|
36
|
-
# post HTTP requests from a request hash.
|
37
|
-
#
|
38
|
-
# res = Tap::Http::Dispatch.submit(
|
39
|
-
# :url => "http://tap.rubyforge.org",
|
40
|
-
# :version => '1.1',
|
41
|
-
# :request_method => 'GET',
|
42
|
-
# :headers => {},
|
43
|
-
# :params => {}
|
44
|
-
# )
|
45
|
-
# res.inspect # => "#<Net::HTTPOK 200 OK readbody=true>"
|
46
|
-
# res.body =~ /Tap/ # => true
|
47
|
-
#
|
48
|
-
# Headers and parameters take the form:
|
49
|
-
#
|
50
|
-
# {
|
51
|
-
# 'single' => 'value',
|
52
|
-
# 'multiple' => ['value one', 'value two']
|
53
|
-
# }
|
54
|
-
#
|
55
|
-
# To capture request hashes from web forms using Firefox, see the README.
|
56
|
-
class Dispatch < Tap::Task
|
57
|
-
class << self
|
58
|
-
def intern(*args, &block)
|
59
|
-
instance = new(*args)
|
60
|
-
instance.extend Support::Intern(:process_response)
|
61
|
-
instance.process_response_block = block
|
62
|
-
instance
|
63
|
-
end
|
64
|
-
|
65
|
-
# Constructs and submits an http request to the url using the request hash.
|
66
|
-
# Request hashes are like this:
|
67
|
-
#
|
68
|
-
# {
|
69
|
-
# :url => "http://tap.rubyforge.org",
|
70
|
-
# :version => '1.1',
|
71
|
-
# :request_method => 'GET',
|
72
|
-
# :headers => {},
|
73
|
-
# :params => {}
|
74
|
-
# }
|
75
|
-
#
|
76
|
-
# If left unspecified, the default configuration values will be used (but
|
77
|
-
# note that since the default url is nil, a url MUST be specified).
|
78
|
-
# Headers and parameters can use array values to specifiy multiple values
|
79
|
-
# for the same key.
|
80
|
-
#
|
81
|
-
# Submit only support get and post request methods; see construct_get and
|
82
|
-
# construct_post for more details. A block may be given to receive the
|
83
|
-
# Net::HTTP and request just prior to submission.
|
84
|
-
#
|
85
|
-
# Returns the Net::HTTP response.
|
86
|
-
#
|
87
|
-
def submit(request_hash)
|
88
|
-
url_or_uri = request_hash[:url] || configurations[:url].default
|
89
|
-
headers = request_hash[:headers] || configurations[:headers].default
|
90
|
-
params = request_hash[:params] || configurations[:params].default
|
91
|
-
request_method = request_hash[:request_method] || configurations[:request_method].default
|
92
|
-
version = request_hash[:version] || configurations[:version].default
|
93
|
-
|
94
|
-
raise ArgumentError, "no url specified" unless url_or_uri
|
95
|
-
uri = url_or_uri.kind_of?(URI) ? url_or_uri : URI.parse(url_or_uri)
|
96
|
-
uri.path = "/" if uri.path.empty?
|
97
|
-
|
98
|
-
# construct the request based on the method
|
99
|
-
request = case request_method.to_s
|
100
|
-
when /^get$/i then construct_get(uri, headers, params)
|
101
|
-
when /^post$/i then construct_post(uri, headers, params)
|
102
|
-
else
|
103
|
-
raise ArgumentError, "unsupported request method: #{request_method}"
|
104
|
-
end
|
105
|
-
|
106
|
-
# set the http version
|
107
|
-
version_method = "version_#{version.to_s.gsub(".", "_")}".to_sym
|
108
|
-
if ::Net::HTTP.respond_to?(version_method)
|
109
|
-
::Net::HTTP.send(version_method)
|
110
|
-
else
|
111
|
-
raise ArgumentError, "unsupported HTTP version: #{version}"
|
112
|
-
end
|
113
|
-
|
114
|
-
# submit the request
|
115
|
-
res = ::Net::HTTP.new(uri.host, uri.port).start do |http|
|
116
|
-
yield(http, request) if block_given?
|
117
|
-
http.request(request)
|
118
|
-
end
|
119
|
-
|
120
|
-
# fetch redirections
|
121
|
-
redirection_limit = request_hash[:redirection_limit]
|
122
|
-
redirection_limit ? fetch_redirection(res, redirection_limit) : res
|
123
|
-
end
|
124
|
-
|
125
|
-
# Constructs a Net::HTTP::Post query, setting headers and parameters.
|
126
|
-
#
|
127
|
-
# ==== Supported Content Types:
|
128
|
-
#
|
129
|
-
# - application/x-www-form-urlencoded (the default)
|
130
|
-
# - multipart/form-data
|
131
|
-
#
|
132
|
-
# The multipart/form-data content type may specify a boundary. If no
|
133
|
-
# boundary is specified, a randomly generated boundary will be used
|
134
|
-
# to delimit the parameters.
|
135
|
-
#
|
136
|
-
# post = construct_post(
|
137
|
-
# URI.parse('http://some.url/'),
|
138
|
-
# {:content_type => 'multipart/form-data; boundary=1234'},
|
139
|
-
# {:key => 'value'})
|
140
|
-
#
|
141
|
-
# post.body
|
142
|
-
# # => %Q{--1234\r
|
143
|
-
# # Content-Disposition: form-data; name="key"\r
|
144
|
-
# # \r
|
145
|
-
# # value\r
|
146
|
-
# # --1234--\r
|
147
|
-
# # }
|
148
|
-
#
|
149
|
-
# (Note the carriage returns are required in multipart content)
|
150
|
-
#
|
151
|
-
# The content-length header is determined automatically from the
|
152
|
-
# formatted request body; manually specified content-length headers
|
153
|
-
# will be overridden.
|
154
|
-
#
|
155
|
-
def construct_post(uri, headers, params)
|
156
|
-
req = ::Net::HTTP::Post.new( URI.encode("#{uri.path}#{format_query(uri)}") )
|
157
|
-
headers = headerize_keys(headers)
|
158
|
-
content_type = headers['Content-Type']
|
159
|
-
|
160
|
-
case content_type
|
161
|
-
when nil, /^application\/x-www-form-urlencoded$/i
|
162
|
-
req.body = format_www_form_urlencoded(params)
|
163
|
-
headers['Content-Type'] ||= "application/x-www-form-urlencoded"
|
164
|
-
headers['Content-Length'] = req.body.length
|
165
|
-
|
166
|
-
when /^multipart\/form-data(;\s*boundary=(.*))?$/i
|
167
|
-
# extract the boundary if it exists
|
168
|
-
boundary = $2 || rand.to_s[2..20]
|
169
|
-
|
170
|
-
req.body = format_multipart_form_data(params, boundary)
|
171
|
-
headers['Content-Type'] = "multipart/form-data; boundary=#{boundary}"
|
172
|
-
headers['Content-Length'] = req.body.length
|
173
|
-
|
174
|
-
else
|
175
|
-
raise ArgumentError, "unsupported Content-Type for POST: #{content_type}"
|
176
|
-
end
|
177
|
-
|
178
|
-
headers.each_pair { |key, value| req[key] = value }
|
179
|
-
req
|
180
|
-
end
|
181
|
-
|
182
|
-
# Constructs a Net::HTTP::Get query. All parameters in uri and params are
|
183
|
-
# encoded and added to the request URI.
|
184
|
-
#
|
185
|
-
# get = construct_get(URI.parse('http://some.url/path'), {}, {:key => 'value'})
|
186
|
-
# get.path # => "/path?key=value"
|
187
|
-
#
|
188
|
-
def construct_get(uri, headers, params)
|
189
|
-
req = ::Net::HTTP::Get.new( URI.encode("#{uri.path}#{format_query(uri, params)}") )
|
190
|
-
headerize_keys(headers).each_pair { |key, value| req[key] = value }
|
191
|
-
req
|
192
|
-
end
|
193
|
-
|
194
|
-
# Checks the type of the response; if it is a redirection, fetches the
|
195
|
-
# redirection. Otherwise return the response.
|
196
|
-
#
|
197
|
-
# Notes:
|
198
|
-
# - Fetch will recurse up to the input redirection limit (default 10)
|
199
|
-
# - Responses that are not Net::HTTPRedirection or Net::HTTPSuccess
|
200
|
-
# raise an error.
|
201
|
-
def fetch_redirection(res, limit=10)
|
202
|
-
raise 'exceeded the redirection limit' if limit < 1
|
203
|
-
|
204
|
-
case res
|
205
|
-
when ::Net::HTTPRedirection
|
206
|
-
redirect = ::Net::HTTP.get_response( URI.parse(res['location']) )
|
207
|
-
fetch_redirection(redirect, limit - 1)
|
208
|
-
when ::Net::HTTPSuccess
|
209
|
-
res
|
210
|
-
else
|
211
|
-
raise StandardError, res.error!
|
212
|
-
end
|
213
|
-
end
|
214
|
-
|
215
|
-
# Constructs a URI query string from the uri and the input parameters.
|
216
|
-
# Multiple values for a parameter may be specified using an array.
|
217
|
-
# The query is not encoded, so you may need to URI.encode it later.
|
218
|
-
#
|
219
|
-
# format_query(URI.parse('http://some.url/path'), {:key => 'value'})
|
220
|
-
# # => "?key=value"
|
221
|
-
#
|
222
|
-
# format_query(URI.parse('http://some.url/path?one=1'), {:two => '2'})
|
223
|
-
# # => "?one=1&two=2"
|
224
|
-
#
|
225
|
-
def format_query(uri, params={})
|
226
|
-
query = []
|
227
|
-
query << uri.query if uri.query
|
228
|
-
params.each_pair do |key, values|
|
229
|
-
values = [values] unless values.kind_of?(Array)
|
230
|
-
values.each { |value| query << "#{key}=#{value}" }
|
231
|
-
end
|
232
|
-
"#{query.empty? ? '' : '?'}#{query.join('&')}"
|
233
|
-
end
|
234
|
-
|
235
|
-
# Formats params as 'application/x-www-form-urlencoded' for use as the
|
236
|
-
# body of a post request. Multiple values for a parameter may be
|
237
|
-
# specified using an array. The result is obviously URI encoded.
|
238
|
-
#
|
239
|
-
# format_www_form_urlencoded(:key => 'value with spaces')
|
240
|
-
# # => "key=value%20with%20spaces"
|
241
|
-
#
|
242
|
-
def format_www_form_urlencoded(params={})
|
243
|
-
query = []
|
244
|
-
params.each_pair do |key, values|
|
245
|
-
values = [values] unless values.kind_of?(Array)
|
246
|
-
values.each { |value| query << "#{key}=#{value}" }
|
247
|
-
end
|
248
|
-
URI.encode( query.join('&') )
|
249
|
-
end
|
250
|
-
|
251
|
-
# Formats params as 'multipart/form-data' using the specified boundary,
|
252
|
-
# for use as the body of a post request. Multiple values for a parameter
|
253
|
-
# may be specified using an array. All newlines include a carriage
|
254
|
-
# return for proper formatting.
|
255
|
-
#
|
256
|
-
# format_multipart_form_data(:key => 'value')
|
257
|
-
# # => %Q{--1234567890\r
|
258
|
-
# # Content-Disposition: form-data; name="key"\r
|
259
|
-
# # \r
|
260
|
-
# # value\r
|
261
|
-
# # --1234567890--\r
|
262
|
-
# # }
|
263
|
-
#
|
264
|
-
# To specify a file, use a hash of file-related headers.
|
265
|
-
#
|
266
|
-
# format_multipart_form_data(:key => {
|
267
|
-
# 'Content-Type' => 'text/plain',
|
268
|
-
# 'Filename' => "path/to/file.txt"}
|
269
|
-
# )
|
270
|
-
# # => %Q{--1234567890\r
|
271
|
-
# # Content-Disposition: form-data; name="key"; filename="path/to/file.txt"\r
|
272
|
-
# # Content-Type: text/plain\r
|
273
|
-
# # \r
|
274
|
-
# # \r
|
275
|
-
# # --1234567890--\r
|
276
|
-
# # }
|
277
|
-
#
|
278
|
-
def format_multipart_form_data(params, boundary="1234567890")
|
279
|
-
body = []
|
280
|
-
params.each_pair do |key, values|
|
281
|
-
values = [values] unless values.kind_of?(Array)
|
282
|
-
|
283
|
-
values.each do |value|
|
284
|
-
body << case value
|
285
|
-
when Hash
|
286
|
-
hash = headerize_keys(value)
|
287
|
-
filename = hash.delete('Filename') || ""
|
288
|
-
content = File.exists?(filename) ? File.read(filename) : ""
|
289
|
-
|
290
|
-
header = "Content-Disposition: form-data; name=\"#{key.to_s}\"; filename=\"#{filename}\"\r\n"
|
291
|
-
hash.each_pair { |key, value| header << "#{key}: #{value}\r\n" }
|
292
|
-
"#{header}\r\n#{content}\r\n"
|
293
|
-
else
|
294
|
-
%Q{Content-Disposition: form-data; name="#{key.to_s}"\r\n\r\n#{value.to_s}\r\n}
|
295
|
-
end
|
296
|
-
end
|
297
|
-
end
|
298
|
-
|
299
|
-
body.collect {|p| "--#{boundary}\r\n#{p}" }.join('') + "--#{boundary}--\r\n"
|
300
|
-
end
|
301
|
-
|
302
|
-
protected
|
303
|
-
|
304
|
-
# Helper to headerize the keys of a hash to headers.
|
305
|
-
# See Utils#headerize.
|
306
|
-
def headerize_keys(hash) # :nodoc:
|
307
|
-
result = {}
|
308
|
-
hash.each_pair do |key, value|
|
309
|
-
result[Utils.headerize(key)] = value
|
310
|
-
end
|
311
|
-
result
|
312
|
-
end
|
313
|
-
end
|
314
|
-
|
315
|
-
config :url, nil # the target url
|
316
|
-
config :headers, {}, &c.hash # a hash of request headers
|
317
|
-
config :params, {}, &c.hash # a hash of query parameters
|
318
|
-
config :request_method, 'GET' # the request method (get or post)
|
319
|
-
config :version, 1.1 # the HTTP version
|
320
|
-
config :redirection_limit, nil, &c.integer_or_nil # the redirection limit for the request
|
321
|
-
|
322
|
-
config :max_threads, 10, &c.integer # the maximum number of request threads
|
323
|
-
|
324
|
-
# Prepares the request_hash by symbolizing keys and adding missing
|
325
|
-
# parameters using the current configuration values.
|
326
|
-
def prepare(request_hash)
|
327
|
-
request_hash.inject(
|
328
|
-
:url => url,
|
329
|
-
:headers => headers,
|
330
|
-
:params => params,
|
331
|
-
:request_method => request_method,
|
332
|
-
:version => version,
|
333
|
-
:redirection_limit => redirection_limit
|
334
|
-
) do |options, (key, value)|
|
335
|
-
options[(key.to_sym rescue key) || key] = value
|
336
|
-
options
|
337
|
-
end
|
338
|
-
end
|
339
|
-
|
340
|
-
def process(*requests)
|
341
|
-
# build a queue of all the requests to be handled
|
342
|
-
queue = Queue.new
|
343
|
-
requests.each_with_index do |request, index|
|
344
|
-
queue.enq [prepare(request), index]
|
345
|
-
index += 1
|
346
|
-
end
|
347
|
-
|
348
|
-
# submit and retrieve all requests before processing
|
349
|
-
# responses. this assures responses are processed
|
350
|
-
# in order, in case it matters.
|
351
|
-
lock = Mutex.new
|
352
|
-
responses = []
|
353
|
-
request_threads = Array.new(max_threads) do
|
354
|
-
Thread.new do
|
355
|
-
begin
|
356
|
-
while !queue.empty?
|
357
|
-
request, index = queue.deq(true)
|
358
|
-
log(request[:request_method], request[:url])
|
359
|
-
|
360
|
-
res = Dispatch.submit(request)
|
361
|
-
lock.synchronize { responses[index] = res }
|
362
|
-
end
|
363
|
-
rescue(ThreadError)
|
364
|
-
# Catch errors due to the queue being empty.
|
365
|
-
# (this should not occur as the queue is checked)
|
366
|
-
raise $! unless $!.message == 'queue empty'
|
367
|
-
end
|
368
|
-
end
|
369
|
-
end
|
370
|
-
request_threads.each {|thread| thread.join }
|
371
|
-
|
372
|
-
# process responses and collect results
|
373
|
-
errors = []
|
374
|
-
responses = responses.collect do |res|
|
375
|
-
begin
|
376
|
-
process_response(res)
|
377
|
-
rescue(ResponseError)
|
378
|
-
errors << [$!, responses.index(res)]
|
379
|
-
nil
|
380
|
-
end
|
381
|
-
end
|
382
|
-
|
383
|
-
unless errors.empty?
|
384
|
-
handle_response_errors(responses, errors)
|
385
|
-
end
|
386
|
-
|
387
|
-
responses
|
388
|
-
end
|
389
|
-
|
390
|
-
# Hook for processing a response. By default process_response
|
391
|
-
# simply logs the response message and returns the response.
|
392
|
-
def process_response(res)
|
393
|
-
log(nil, res.message)
|
394
|
-
res
|
395
|
-
end
|
396
|
-
|
397
|
-
# A hook for handling a batch of response errors, perhaps
|
398
|
-
# doing something meaningful with the successful responses.
|
399
|
-
# By default, concatenates the error messages and raises
|
400
|
-
# a new ResponseError.
|
401
|
-
def handle_response_errors(responses, errors)
|
402
|
-
errors.collect! {|error, n| "request #{n}: #{error.message}"}
|
403
|
-
errors.unshift("Error processing responses:")
|
404
|
-
raise ResponseError, errors.join("\n")
|
405
|
-
end
|
406
|
-
|
407
|
-
class ResponseError < StandardError
|
408
|
-
end
|
409
|
-
end
|
410
|
-
end
|
411
|
-
end
|
@@ -1,194 +0,0 @@
|
|
1
|
-
module Tap
|
2
|
-
module Test
|
3
|
-
module HttpTest
|
4
|
-
|
5
|
-
# A collection of sample requests used in testing.
|
6
|
-
module Requests
|
7
|
-
GET_REQUESTS = {}
|
8
|
-
POST_REQUESTS = {}
|
9
|
-
|
10
|
-
def self.add(type, name, request, expected)
|
11
|
-
collection = case type
|
12
|
-
when :get then GET_REQUESTS
|
13
|
-
when :post then POST_REQUESTS
|
14
|
-
end
|
15
|
-
|
16
|
-
collection["#{type}_#{name}"] = [request.lstrip.gsub(/\n/, "\r\n"), expected]
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
#
|
24
|
-
# get requests
|
25
|
-
#
|
26
|
-
|
27
|
-
Tap::Test::HttpTest::Requests.add :get, :basic, %q{
|
28
|
-
GET /path HTTP/1.1
|
29
|
-
Host: www.example.com
|
30
|
-
Keep-Alive: 300
|
31
|
-
Connection: keep-alive
|
32
|
-
}, {
|
33
|
-
:url => "http://www.example.com/path",
|
34
|
-
:version => '1.1',
|
35
|
-
:request_method => 'GET',
|
36
|
-
:headers => {
|
37
|
-
"Host" => "www.example.com",
|
38
|
-
"Keep-Alive" => "300",
|
39
|
-
"Connection" => 'keep-alive'},
|
40
|
-
:params => {}
|
41
|
-
}
|
42
|
-
|
43
|
-
Tap::Test::HttpTest::Requests.add :get, :header_less, %q{
|
44
|
-
GET /path HTTP/1.1
|
45
|
-
}, {
|
46
|
-
:url => "/path",
|
47
|
-
:version => '1.1',
|
48
|
-
:request_method => 'GET',
|
49
|
-
:headers => {},
|
50
|
-
:params => {}
|
51
|
-
}
|
52
|
-
|
53
|
-
Tap::Test::HttpTest::Requests.add :get, :version_less, %q{
|
54
|
-
GET /path
|
55
|
-
}, {
|
56
|
-
:url => "/path",
|
57
|
-
:version => '0.9',
|
58
|
-
:request_method => 'GET',
|
59
|
-
:headers => {},
|
60
|
-
:params => {}
|
61
|
-
}
|
62
|
-
|
63
|
-
Tap::Test::HttpTest::Requests.add :get, :with_query, %q{
|
64
|
-
GET /path?one=value%20one&two=value%20two HTTP/1.1
|
65
|
-
}, {
|
66
|
-
:url => "/path",
|
67
|
-
:version => '1.1',
|
68
|
-
:request_method => 'GET',
|
69
|
-
:headers => {},
|
70
|
-
:params => {
|
71
|
-
'one' => 'value one',
|
72
|
-
'two' => 'value two'}
|
73
|
-
}
|
74
|
-
|
75
|
-
#
|
76
|
-
# post requests
|
77
|
-
#
|
78
|
-
|
79
|
-
Tap::Test::HttpTest::Requests.add :post, :with_multipart_form_data, %q{
|
80
|
-
POST /path HTTP/1.1
|
81
|
-
Host: www.example.com
|
82
|
-
Content-Type: multipart/form-data; boundary=1234567890
|
83
|
-
Content-Length: 158
|
84
|
-
|
85
|
-
--1234567890
|
86
|
-
Content-Disposition: form-data; name="one"
|
87
|
-
|
88
|
-
value one
|
89
|
-
--1234567890
|
90
|
-
Content-Disposition: form-data; name="two"
|
91
|
-
|
92
|
-
value two
|
93
|
-
--1234567890--
|
94
|
-
}, {
|
95
|
-
:url => "http://www.example.com/path",
|
96
|
-
:version => '1.1',
|
97
|
-
:request_method => 'POST',
|
98
|
-
:headers => {
|
99
|
-
"Host" => 'www.example.com',
|
100
|
-
"Content-Type" => "multipart/form-data; boundary=1234567890",
|
101
|
-
"Content-Length" => "158"},
|
102
|
-
:params => {
|
103
|
-
'one' => 'value one',
|
104
|
-
'two' => 'value two'}
|
105
|
-
}
|
106
|
-
|
107
|
-
Tap::Test::HttpTest::Requests.add :post, :with_multipart_data_and_multiple_values, %q{
|
108
|
-
POST /path HTTP/1.1
|
109
|
-
Host: www.example.com
|
110
|
-
Content-Type: multipart/form-data; boundary=1234567890
|
111
|
-
Content-Length: 158
|
112
|
-
|
113
|
-
--1234567890
|
114
|
-
Content-Disposition: form-data; name="key"
|
115
|
-
|
116
|
-
value one
|
117
|
-
--1234567890
|
118
|
-
Content-Disposition: form-data; name="key"
|
119
|
-
|
120
|
-
value two
|
121
|
-
--1234567890--
|
122
|
-
}, {
|
123
|
-
:url => "http://www.example.com/path",
|
124
|
-
:version => '1.1',
|
125
|
-
:request_method => 'POST',
|
126
|
-
:headers => {
|
127
|
-
"Host" => 'www.example.com',
|
128
|
-
"Content-Type" => "multipart/form-data; boundary=1234567890",
|
129
|
-
"Content-Length" => "158"},
|
130
|
-
:params => {
|
131
|
-
'key' => ["value one", "value two"]}
|
132
|
-
}
|
133
|
-
|
134
|
-
Tap::Test::HttpTest::Requests.add :post, :with_file_data, %q{
|
135
|
-
POST /path HTTP/1.1
|
136
|
-
Host: www.example.com
|
137
|
-
Content-Type: multipart/form-data; boundary=1234567890
|
138
|
-
Content-Length: 148
|
139
|
-
|
140
|
-
--1234567890
|
141
|
-
Content-Disposition: form-data; name="key"; filename="file.txt"
|
142
|
-
Content-Type: application/octet-stream
|
143
|
-
|
144
|
-
value one
|
145
|
-
--1234567890--
|
146
|
-
}, {
|
147
|
-
:url => "http://www.example.com/path",
|
148
|
-
:version => '1.1',
|
149
|
-
:request_method => 'POST',
|
150
|
-
:headers => {
|
151
|
-
"Host" => 'www.example.com',
|
152
|
-
"Content-Type" => "multipart/form-data; boundary=1234567890",
|
153
|
-
"Content-Length" => "148"},
|
154
|
-
:params => {
|
155
|
-
'key' => {'Filename' => 'file.txt', 'Content-Type' => 'application/octet-stream'}}
|
156
|
-
}
|
157
|
-
|
158
|
-
Tap::Test::HttpTest::Requests.add :post, :with_mixed_multi_value_file_data, %q{
|
159
|
-
POST /path HTTP/1.1
|
160
|
-
Host: www.example.com
|
161
|
-
Content-Type: multipart/form-data; boundary=1234567890
|
162
|
-
Content-Length: 329
|
163
|
-
|
164
|
-
--1234567890
|
165
|
-
Content-Disposition: form-data; name="key"
|
166
|
-
|
167
|
-
one
|
168
|
-
--1234567890
|
169
|
-
Content-Disposition: form-data; name="key"; filename="one.txt"
|
170
|
-
Content-Type: application/octet-stream
|
171
|
-
|
172
|
-
value one
|
173
|
-
--1234567890
|
174
|
-
Content-Disposition: form-data; name="key"; filename="two.txt"
|
175
|
-
Content-Type: text/plain
|
176
|
-
|
177
|
-
value two
|
178
|
-
--1234567890--
|
179
|
-
}, {
|
180
|
-
:url => "http://www.example.com/path",
|
181
|
-
:version => '1.1',
|
182
|
-
:request_method => 'POST',
|
183
|
-
:headers => {
|
184
|
-
"Host" => 'www.example.com',
|
185
|
-
"Content-Type" => "multipart/form-data; boundary=1234567890",
|
186
|
-
"Content-Length" => "329"},
|
187
|
-
:params => {
|
188
|
-
'key' => [
|
189
|
-
"one",
|
190
|
-
{'Filename' => 'one.txt', 'Content-Type' => 'application/octet-stream'},
|
191
|
-
{'Filename' => 'two.txt', 'Content-Type' => 'text/plain'}]}
|
192
|
-
}
|
193
|
-
|
194
|
-
|