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/History
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
== 0.3.0 / 2009-02-19
|
2
|
+
|
3
|
+
Rework of cgi scripts a tap controllers. Nearly complete
|
4
|
+
internal refactoring (this is not a backwards compatible
|
5
|
+
release).
|
6
|
+
|
7
|
+
* Reworked CGI scripts as Tap Controllers
|
8
|
+
* Reworked Dispatch as the Request module
|
9
|
+
* Added Get and Submit tasks
|
10
|
+
* Reworked HttpTest to allow specification of a
|
11
|
+
Rack application
|
12
|
+
|
1
13
|
== 0.2.1 / 2009-02-17
|
2
14
|
|
3
15
|
Minor updates to utilize Tap 0.12.0
|
data/README
CHANGED
@@ -16,85 +16,24 @@ they allow the capture and resubmission of web forms.
|
|
16
16
|
|
17
17
|
=== Usage
|
18
18
|
|
19
|
-
TapHttp submits http requests using the Tap::Http::
|
20
|
-
parameters, and other configurations may be specified, but
|
21
|
-
|
19
|
+
TapHttp submits http requests using the Tap::Http::Request module. Headers,
|
20
|
+
parameters, and other configurations may be specified, but only a request
|
21
|
+
method and uri are required.
|
22
22
|
|
23
23
|
include Tap::Http
|
24
24
|
|
25
|
-
res =
|
26
|
-
:params => {'q' => 'tap-http'},
|
27
|
-
:url => 'http://www.google.com/search')
|
28
|
-
|
25
|
+
res = Request.get('http://www.google.com/search')
|
29
26
|
res.body[0,80] # => "<!doctype html><head><title>tap-http - Google Search</title><style>body{backgrou"
|
30
27
|
|
31
|
-
===
|
28
|
+
=== Submitting Web Forms
|
32
29
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
* Install {Firefox}[http://www.mozilla.com/en-US/firefox/]
|
38
|
-
* Install {Ubiquity}[http://labs.mozilla.com/2008/08/introducing-ubiquity/]
|
39
|
-
* Install {redirect-http}[http://gist.github.com/25932]
|
40
|
-
|
41
|
-
Start a tap server from the command line (of course tap-http must be installed):
|
30
|
+
Http requests from web forms may be captured and resubmitted using a combination
|
31
|
+
of tools. To do so start a tap server from the command line (of course tap-http
|
32
|
+
must be installed):
|
42
33
|
|
43
34
|
% tap server
|
44
35
|
|
45
|
-
Now
|
46
|
-
invoke the redirection.
|
47
|
-
|
48
|
-
* Bring up Ubiquity in Firefox by pressing 'option+space'
|
49
|
-
* Enter the command: 'redirect-http http://localhost:8080/http_to_yaml'
|
50
|
-
|
51
|
-
You should see a notice that the form is being redirected. Fill out the form
|
52
|
-
and submit as normal; the redirect command will send the form to the tap server
|
53
|
-
instead of performing the original action. The tap server returns a yaml file
|
54
|
-
with the http configuration.
|
55
|
-
|
56
|
-
# Copy and paste into a configuration file. Multiple configs
|
57
|
-
# can be added to a single file to perform batch submission.
|
58
|
-
- headers:
|
59
|
-
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
|
60
|
-
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
|
61
|
-
Accept-Encoding: gzip,deflate
|
62
|
-
Accept-Language: en-us,en;q=0.5
|
63
|
-
Connection: keep-alive
|
64
|
-
Host: www.google.com
|
65
|
-
Keep-Alive: "300"
|
66
|
-
Referer: http://www.google.com/
|
67
|
-
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.0.4) Gecko/2008102920 Firefox/3.0.4
|
68
|
-
params:
|
69
|
-
aq: f
|
70
|
-
btnG: Google Search
|
71
|
-
hl: en
|
72
|
-
oq: ""
|
73
|
-
q: tap-http
|
74
|
-
request_method: GET
|
75
|
-
url: http://www.google.com/search
|
76
|
-
version: 1.1
|
77
|
-
|
78
|
-
Save the file as 'request.yml' and resubmit the form using the Tap::Http::Dispatch
|
79
|
-
task.
|
80
|
-
|
81
|
-
% rap load requests.yml --:i dispatch --+ dump --no-audit
|
82
|
-
I[10:51:40] load request.yml
|
83
|
-
I[10:51:40] GET http://www.google.com/search
|
84
|
-
I[10:51:41] OK
|
85
|
-
# date: 2008-11-25 10:51:41
|
86
|
-
---
|
87
|
-
tap/http/request (2772040):
|
88
|
-
- - !ruby/object:Net::HTTPOK
|
89
|
-
body: !binary |
|
90
|
-
H4sIAAAAAAAC/6xabXPbNhL+3l/B0BeN1FAUJfktoihf07pppmkm06TXu0lz
|
91
|
-
HZAEScQgQZOQZVfhf79dgBRJS4ndmRvPSAC42F3sPvsCyssnoQjkXU6NRKZ8
|
92
|
-
tUwoCVdLySSnK0nycSJlboyNl0LEnBrvKCmCZDnRz5elvIMvX4R3W58EV3Eh
|
93
|
-
1lm4OIqiyA0EF8XiyHEc...
|
94
|
-
|
95
|
-
Note the result is encoded as gzip, as per the parameters. As with all tasks,
|
96
|
-
the request results could be passed into a workflow. Alternatively, the
|
97
|
-
configuration could be used to submit the request using Dispatch.
|
36
|
+
Now open a browser and work through the {tutorial}[http://localhost:8080/capture].
|
98
37
|
|
99
38
|
=== Bugs/Known Issues
|
100
39
|
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'tap/controller'
|
2
|
+
require 'tap/http/utils'
|
3
|
+
|
4
|
+
class CaptureController < Tap::Controller
|
5
|
+
|
6
|
+
def index
|
7
|
+
render 'index.erb'
|
8
|
+
end
|
9
|
+
|
10
|
+
def say
|
11
|
+
"<pre>#{request.params['words']}</pre>"
|
12
|
+
end
|
13
|
+
|
14
|
+
# Echos back redirected HTTP requests as YAML, suitable for use with the
|
15
|
+
# Tap::Http::Submit task. All HTTP parameters and headers are echoed back
|
16
|
+
# directly, except for the '__original_action' parameter which is used in
|
17
|
+
# conjuction with the 'Referer' header to reconstruct the original url of
|
18
|
+
# the request. The '__original_action' parameter is not echoed.
|
19
|
+
def http_to_yaml
|
20
|
+
headers = {}
|
21
|
+
request.env.each_pair do |key, value|
|
22
|
+
headers[key] = value unless key =~ /^(rack|tap)/
|
23
|
+
end
|
24
|
+
params = request.params
|
25
|
+
|
26
|
+
original_action = params.delete("__original_action").to_s
|
27
|
+
referer = headers['Referer'].to_s
|
28
|
+
uri = Tap::Http::Utils.determine_url(original_action, referer)
|
29
|
+
headers['Host'] = URI.parse(uri).host
|
30
|
+
|
31
|
+
config = {
|
32
|
+
'headers' => headers,
|
33
|
+
'params' => params,
|
34
|
+
'uri' => uri,
|
35
|
+
'request_method' => request.request_method
|
36
|
+
}
|
37
|
+
|
38
|
+
response['Content-Type'] = "text/plain"
|
39
|
+
%Q{# Save as a configuration file. Resubmit using:
|
40
|
+
#
|
41
|
+
# % rap load <config_file> --: submit --: dump --no-audit
|
42
|
+
#
|
43
|
+
#{YAML.dump(config)}
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
def echo
|
48
|
+
headers = {}
|
49
|
+
request.env.each_pair do |key, value|
|
50
|
+
headers[key] = value unless key =~ /^(rack|tap)/
|
51
|
+
end
|
52
|
+
|
53
|
+
"<pre>#{headers.to_yaml}#{request.params.to_yaml}</pre>"
|
54
|
+
end
|
55
|
+
end
|
data/lib/tap/http/get.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'tap/http/request'
|
2
|
+
|
3
|
+
module Tap
|
4
|
+
module Http
|
5
|
+
# Tap::Http::Get::manifest gets the uri
|
6
|
+
# Submits an Http request to the specified uri and returns the message body.
|
7
|
+
class Get < Tap::Task
|
8
|
+
include Request
|
9
|
+
|
10
|
+
def process(uri)
|
11
|
+
log :get, uri
|
12
|
+
res = Request.get(uri)
|
13
|
+
log(nil, res.message)
|
14
|
+
res.body
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,263 @@
|
|
1
|
+
require 'tap/http/utils'
|
2
|
+
require 'net/http'
|
3
|
+
require 'rack/utils'
|
4
|
+
|
5
|
+
module Tap
|
6
|
+
module Http
|
7
|
+
|
8
|
+
# Request is a module for submitting HTTP requests. Request take a request
|
9
|
+
# method, a uri, and an options hash allowing these parameters:
|
10
|
+
#
|
11
|
+
# headers:: a hash of headers
|
12
|
+
# params:: a hash of parameters, values may be arrays when multiple
|
13
|
+
# values are assigned to a single key
|
14
|
+
# version: the HTTP version, by default 1.1
|
15
|
+
# redirection_limit:: the number of redirections allowed, by default 10
|
16
|
+
#
|
17
|
+
module Request
|
18
|
+
module_function
|
19
|
+
|
20
|
+
# Constructs and submits an http request to the uri using the specified
|
21
|
+
# request method and options (see above). Currently only get and post
|
22
|
+
# are supported as request methods. A block may be given to receive the
|
23
|
+
# Net::HTTP and request just prior to submission.
|
24
|
+
#
|
25
|
+
# Returns the Net::HTTP response.
|
26
|
+
def request(request_method, uri, opts={})
|
27
|
+
uri = "http://#{uri}" unless uri.to_s =~ /^http/
|
28
|
+
uri = URI.parse(uri) unless uri.kind_of?(URI)
|
29
|
+
uri.path = "/" if uri.path.empty?
|
30
|
+
|
31
|
+
headers = opts[:headers] || {}
|
32
|
+
params = opts[:params] || {}
|
33
|
+
|
34
|
+
# construct the request
|
35
|
+
request = case request_method.to_s
|
36
|
+
when /^get$/i then construct_get(uri, headers, params)
|
37
|
+
when /^post$/i then construct_post(uri, headers, params)
|
38
|
+
else
|
39
|
+
raise ArgumentError, "unsupported request method: #{request_method}"
|
40
|
+
end
|
41
|
+
|
42
|
+
# set the http version
|
43
|
+
version = opts[:version] || 1.1
|
44
|
+
version_method = "version_#{version.to_s.gsub(".", "_")}".to_sym
|
45
|
+
if ::Net::HTTP.respond_to?(version_method)
|
46
|
+
::Net::HTTP.send(version_method)
|
47
|
+
else
|
48
|
+
raise ArgumentError, "unsupported HTTP version: #{version}"
|
49
|
+
end
|
50
|
+
|
51
|
+
# submit the request
|
52
|
+
res = ::Net::HTTP.new(uri.host, uri.port).start do |http|
|
53
|
+
yield(http, request) if block_given?
|
54
|
+
http.request(request)
|
55
|
+
end
|
56
|
+
|
57
|
+
# fetch redirections
|
58
|
+
redirection_limit = opts[:redirection_limit] || 10
|
59
|
+
redirection_limit ? fetch_redirection(res, redirection_limit) : res
|
60
|
+
end
|
61
|
+
|
62
|
+
# Shortcut to submit a get request.
|
63
|
+
def get(uri, opts={})
|
64
|
+
request(:get, uri, opts)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Shortcut to submit a post request.
|
68
|
+
def post(uri, opts={})
|
69
|
+
request(:post, uri, opts)
|
70
|
+
end
|
71
|
+
|
72
|
+
def escape(str)
|
73
|
+
Rack::Utils.escape(str)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Constructs a Net::HTTP::Post query, setting headers and parameters.
|
77
|
+
#
|
78
|
+
# ==== Supported Content Types:
|
79
|
+
#
|
80
|
+
# - application/x-www-form-urlencoded (the default)
|
81
|
+
# - multipart/form-data
|
82
|
+
#
|
83
|
+
# The multipart/form-data content type may specify a boundary. If no
|
84
|
+
# boundary is specified, a randomly generated boundary will be used
|
85
|
+
# to delimit the parameters.
|
86
|
+
#
|
87
|
+
# post = construct_post(
|
88
|
+
# URI.parse('http://some.url/'),
|
89
|
+
# {:content_type => 'multipart/form-data; boundary=1234'},
|
90
|
+
# {:key => 'value'})
|
91
|
+
#
|
92
|
+
# post.body
|
93
|
+
# # => %Q{--1234\r
|
94
|
+
# # Content-Disposition: form-data; name="key"\r
|
95
|
+
# # \r
|
96
|
+
# # value\r
|
97
|
+
# # --1234--\r
|
98
|
+
# # }
|
99
|
+
#
|
100
|
+
# (Note the carriage returns are required in multipart content)
|
101
|
+
#
|
102
|
+
# The content-length header is determined automatically from the
|
103
|
+
# formatted request body; manually specified content-length headers
|
104
|
+
# will be overridden.
|
105
|
+
#
|
106
|
+
def construct_post(uri, headers, params)
|
107
|
+
req = ::Net::HTTP::Post.new( "#{uri.path}#{format_query(uri)}" )
|
108
|
+
headers = headerize_keys(headers)
|
109
|
+
content_type = headers['Content-Type']
|
110
|
+
|
111
|
+
case content_type
|
112
|
+
when nil, /^application\/x-www-form-urlencoded$/i
|
113
|
+
req.body = format_www_form_urlencoded(params)
|
114
|
+
headers['Content-Type'] ||= "application/x-www-form-urlencoded"
|
115
|
+
headers['Content-Length'] = req.body.length
|
116
|
+
|
117
|
+
when /^multipart\/form-data(;\s*boundary=(.*))?$/i
|
118
|
+
# extract the boundary if it exists
|
119
|
+
boundary = $2 || rand.to_s[2..20]
|
120
|
+
|
121
|
+
req.body = format_multipart_form_data(params, boundary)
|
122
|
+
headers['Content-Type'] = "multipart/form-data; boundary=#{boundary}"
|
123
|
+
headers['Content-Length'] = req.body.length
|
124
|
+
|
125
|
+
else
|
126
|
+
raise ArgumentError, "unsupported Content-Type for POST: #{content_type}"
|
127
|
+
end
|
128
|
+
|
129
|
+
headers.each_pair {|key, value| req[key] = value }
|
130
|
+
req
|
131
|
+
end
|
132
|
+
|
133
|
+
# Constructs a Net::HTTP::Get query. All parameters in uri and params are
|
134
|
+
# encoded and added to the request URI.
|
135
|
+
#
|
136
|
+
# get = construct_get(URI.parse('http://some.url/path'), {}, {:key => 'value'})
|
137
|
+
# get.path # => "/path?key=value"
|
138
|
+
#
|
139
|
+
def construct_get(uri, headers, params)
|
140
|
+
req = ::Net::HTTP::Get.new( "#{uri.path}#{format_query(uri, params)}" )
|
141
|
+
headerize_keys(headers).each_pair {|key, value| req[key] = value }
|
142
|
+
req
|
143
|
+
end
|
144
|
+
|
145
|
+
# Checks the type of the response; if it is a redirection, fetches the
|
146
|
+
# redirection. Otherwise return the response.
|
147
|
+
#
|
148
|
+
# Notes:
|
149
|
+
# - Fetch will recurse up to the input redirection limit (default 10)
|
150
|
+
# - Responses that are not Net::HTTPRedirection or Net::HTTPSuccess
|
151
|
+
# raise an error.
|
152
|
+
def fetch_redirection(res, limit=10)
|
153
|
+
raise 'exceeded the redirection limit' if limit < 1
|
154
|
+
|
155
|
+
case res
|
156
|
+
when ::Net::HTTPRedirection
|
157
|
+
redirect = ::Net::HTTP.get_response( URI.parse(res['location']) )
|
158
|
+
fetch_redirection(redirect, limit - 1)
|
159
|
+
when ::Net::HTTPSuccess
|
160
|
+
res
|
161
|
+
else
|
162
|
+
raise StandardError, res.error!
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# Constructs a URI query string from the uri and the input parameters.
|
167
|
+
# Multiple values for a parameter may be specified using an array.
|
168
|
+
#
|
169
|
+
# format_query(URI.parse('http://some.url/path'), {:key => 'value'})
|
170
|
+
# # => "?key=value"
|
171
|
+
#
|
172
|
+
# format_query(URI.parse('http://some.url/path?one=1'), {:two => '2'})
|
173
|
+
# # => "?one=1&two=2"
|
174
|
+
#
|
175
|
+
def format_query(uri, params={})
|
176
|
+
query = []
|
177
|
+
query << uri.query if uri.query
|
178
|
+
params.each_pair do |key, values|
|
179
|
+
values = [values] unless values.kind_of?(Array)
|
180
|
+
values.each { |value| query << "#{escape(key)}=#{escape(value)}" }
|
181
|
+
end
|
182
|
+
"#{query.empty? ? '' : '?'}#{query.join('&')}"
|
183
|
+
end
|
184
|
+
|
185
|
+
# Formats params as 'application/x-www-form-urlencoded' for use as the
|
186
|
+
# body of a post request. Multiple values for a parameter may be
|
187
|
+
# specified using an array. The result is obviously URI encoded.
|
188
|
+
#
|
189
|
+
# format_www_form_urlencoded(:key => 'value with spaces')
|
190
|
+
# # => "key=value+with+spaces"
|
191
|
+
#
|
192
|
+
def format_www_form_urlencoded(params={})
|
193
|
+
query = []
|
194
|
+
params.each_pair do |key, values|
|
195
|
+
values = [values] unless values.kind_of?(Array)
|
196
|
+
values.each { |value| query << "#{escape(key)}=#{escape(value)}" }
|
197
|
+
end
|
198
|
+
query.join('&')
|
199
|
+
end
|
200
|
+
|
201
|
+
# Formats params as 'multipart/form-data' using the specified boundary,
|
202
|
+
# for use as the body of a post request. Multiple values for a parameter
|
203
|
+
# may be specified using an array. All newlines include a carriage
|
204
|
+
# return for proper formatting.
|
205
|
+
#
|
206
|
+
# format_multipart_form_data(:key => 'value')
|
207
|
+
# # => %Q{--1234567890\r
|
208
|
+
# # Content-Disposition: form-data; name="key"\r
|
209
|
+
# # \r
|
210
|
+
# # value\r
|
211
|
+
# # --1234567890--\r
|
212
|
+
# # }
|
213
|
+
#
|
214
|
+
# To specify a file, use a hash of file-related headers.
|
215
|
+
#
|
216
|
+
# format_multipart_form_data(:key => {
|
217
|
+
# 'Content-Type' => 'text/plain',
|
218
|
+
# 'Filename' => "path/to/file.txt"}
|
219
|
+
# )
|
220
|
+
# # => %Q{--1234567890\r
|
221
|
+
# # Content-Disposition: form-data; name="key"; filename="path/to/file.txt"\r
|
222
|
+
# # Content-Type: text/plain\r
|
223
|
+
# # \r
|
224
|
+
# # \r
|
225
|
+
# # --1234567890--\r
|
226
|
+
# # }
|
227
|
+
#
|
228
|
+
def format_multipart_form_data(params, boundary="1234567890")
|
229
|
+
body = []
|
230
|
+
params.each_pair do |key, values|
|
231
|
+
values = [values] unless values.kind_of?(Array)
|
232
|
+
|
233
|
+
values.each do |value|
|
234
|
+
body << case value
|
235
|
+
when Hash
|
236
|
+
hash = headerize_keys(value)
|
237
|
+
filename = hash.delete('Filename') || ""
|
238
|
+
content = File.exists?(filename) ? File.read(filename) : ""
|
239
|
+
|
240
|
+
header = "Content-Disposition: form-data; name=\"#{key.to_s}\"; filename=\"#{filename}\"\r\n"
|
241
|
+
hash.each_pair { |key, value| header << "#{key}: #{value}\r\n" }
|
242
|
+
"#{header}\r\n#{content}\r\n"
|
243
|
+
else
|
244
|
+
%Q{Content-Disposition: form-data; name="#{key.to_s}"\r\n\r\n#{value.to_s}\r\n}
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
body.collect {|p| "--#{boundary}\r\n#{p}" }.join('') + "--#{boundary}--\r\n"
|
250
|
+
end
|
251
|
+
|
252
|
+
# Helper to headerize the keys of a hash to headers.
|
253
|
+
# See Utils#headerize.
|
254
|
+
def headerize_keys(hash)
|
255
|
+
result = {}
|
256
|
+
hash.each_pair do |key, value|
|
257
|
+
result[Utils.headerize(key)] = value
|
258
|
+
end
|
259
|
+
result
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'tap/http/request'
|
2
|
+
|
3
|
+
module Tap
|
4
|
+
module Http
|
5
|
+
# Tap::Http::Submit::manifest submits a captured http request
|
6
|
+
class Submit < Tap::Task
|
7
|
+
|
8
|
+
def process(opts)
|
9
|
+
opts = symbolize(opts)
|
10
|
+
|
11
|
+
request_method = opts.delete(:request_method) || 'GET'
|
12
|
+
uri = opts.delete(:uri)
|
13
|
+
|
14
|
+
log request_method, uri
|
15
|
+
res = Request.request(request_method, uri, opts)
|
16
|
+
log(nil, res.message)
|
17
|
+
res.body
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
# taken from ActiveSupport
|
23
|
+
def symbolize(hash) # :nodoc:
|
24
|
+
hash.inject({}) do |options, (key, value)|
|
25
|
+
options[(key.to_sym rescue key) || key] = value
|
26
|
+
options
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/tap/test/http_test.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
+
require 'rack'
|
1
2
|
require 'webrick'
|
2
|
-
require '
|
3
|
+
require 'thread'
|
3
4
|
require 'tap/test/subset_test'
|
4
|
-
require '
|
5
|
+
require 'stringio'
|
5
6
|
|
6
7
|
module Tap
|
7
8
|
module Test
|
@@ -11,42 +12,24 @@ module Tap
|
|
11
12
|
# validate echoed requests.
|
12
13
|
#
|
13
14
|
module HttpTest
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
attr_accessor :server_thread, :server
|
15
|
+
|
16
|
+
class MockServer
|
17
|
+
def initialize(body, status=200, headers={})
|
18
|
+
@response = [status, headers, [body]]
|
19
|
+
end
|
21
20
|
|
22
|
-
def
|
23
|
-
|
24
|
-
log_level = ENV["WEB_LOG_LEVEL"] ? ENV["WEB_LOG_LEVEL"].upcase : "WARN"
|
25
|
-
logger = Log.new($stderr, Log.const_get( log_level ) )
|
26
|
-
|
27
|
-
self.server = HTTPServer.new(:Port => 2000,
|
28
|
-
:Logger => logger,
|
29
|
-
:AccessLog => [
|
30
|
-
[ logger, AccessLog::COMMON_LOG_FORMAT ],
|
31
|
-
[ logger, AccessLog::REFERER_LOG_FORMAT ]])
|
32
|
-
|
33
|
-
server.mount_proc("/") do |req, res|
|
34
|
-
res.body << req.request_line
|
35
|
-
res.body << req.raw_header.join('')
|
36
|
-
|
37
|
-
# an extra line must be added to delimit the headers from the body.
|
38
|
-
if req.body
|
39
|
-
res.body << "\r\n"
|
40
|
-
res.body << req.body
|
41
|
-
end
|
42
|
-
|
43
|
-
res['Content-Type'] = "text/html"
|
44
|
-
end
|
21
|
+
def call(env)
|
22
|
+
@response
|
45
23
|
end
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
24
|
+
end
|
25
|
+
|
26
|
+
class EchoServer
|
27
|
+
def self.call(env)
|
28
|
+
body = env['rack.input'].read
|
29
|
+
headers = {}
|
30
|
+
env.each_pair {|key, value| headers[key] = [value] unless key =~ /^rack/ }
|
31
|
+
|
32
|
+
[200, headers, [body]]
|
50
33
|
end
|
51
34
|
end
|
52
35
|
|
@@ -54,96 +37,28 @@ module Tap
|
|
54
37
|
base.send(:include, Tap::Test::SubsetTest)
|
55
38
|
end
|
56
39
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
40
|
+
def default_config(log_dev=StringIO.new(''))
|
41
|
+
common_logger = WEBrick::Log.new(log_dev, WEBrick::Log.const_get(:WARN) )
|
42
|
+
{
|
43
|
+
:Port => 2000,
|
44
|
+
:Logger => common_logger,
|
45
|
+
:AccessLog => common_logger
|
46
|
+
}
|
64
47
|
end
|
65
48
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
user
|
77
|
-
addr peeraddr
|
78
|
-
attributes
|
79
|
-
keep_alive}
|
80
|
-
|
81
|
-
UNCHECKED_REQUEST_ATTRIBUTES = %w{
|
82
|
-
request_line
|
83
|
-
unparsed_uri
|
84
|
-
request_uri
|
85
|
-
request_time
|
86
|
-
raw_header
|
87
|
-
query_string}
|
88
|
-
|
89
|
-
# Parses expected and actual as an http request (using Tap::Net.parse_http_request)
|
90
|
-
# and asserts that all of the REQUEST_ATTRIBUTES are equal. See the parse_http_request
|
91
|
-
# documentation for some important notes, particularly involving "\r\n" vs "\n" and
|
92
|
-
# post requests.
|
93
|
-
def assert_request_equal(expected, actual)
|
94
|
-
e = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
|
95
|
-
e.parse( StringIO.new(expected) )
|
96
|
-
|
97
|
-
a = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
|
98
|
-
a.parse( StringIO.new(actual) )
|
99
|
-
|
100
|
-
errors = []
|
101
|
-
REQUEST_ATTRIBUTES.each do |attribute|
|
102
|
-
exp = e.send(attribute)
|
103
|
-
act = a.send(attribute)
|
104
|
-
next if exp == act
|
105
|
-
errors << "<#{PP.singleline_pp(exp, '')}> expected for #{attribute} but was:\n<#{PP.singleline_pp(act, '')}>."
|
106
|
-
end
|
107
|
-
|
108
|
-
if errors.empty?
|
109
|
-
# this rather unecessary assertion is used simply to
|
110
|
-
# make assert_request_equal cause an assertion.
|
111
|
-
assert errors.empty?
|
112
|
-
else
|
113
|
-
flunk errors.join("\n")
|
49
|
+
def web_test(app=EchoServer, config=default_config)
|
50
|
+
subset_test("WEB", "w") do
|
51
|
+
begin
|
52
|
+
server = ::WEBrick::HTTPServer.new(config);
|
53
|
+
server.mount("/", Rack::Handler::WEBrick, app);
|
54
|
+
Thread.new { server.start }
|
55
|
+
yield
|
56
|
+
ensure
|
57
|
+
server.shutdown
|
58
|
+
end
|
114
59
|
end
|
115
60
|
end
|
116
61
|
|
117
|
-
# Convenience method that strips str, strips each line of str, and
|
118
|
-
# then rejoins them using "\r\n" as is typical of HTTP messages.
|
119
|
-
#
|
120
|
-
# strip_align %Q{
|
121
|
-
# GET /echo HTTP/1.1
|
122
|
-
# Accept: */*
|
123
|
-
# Host: localhost:2000}
|
124
|
-
#
|
125
|
-
# # => "GET /echo HTTP/1.1\r\nAccept: */*\r\nHost: localhost:2000\r\n"
|
126
|
-
def strip_align(str)
|
127
|
-
str.strip.split(/\r?\n/).collect do |line|
|
128
|
-
"#{line.strip}\r\n"
|
129
|
-
end.compact.join('')
|
130
|
-
end
|
131
|
-
|
132
|
-
# Turns a hash of parameters into an encoded HTTP query string.
|
133
|
-
# Multiple values for a given key can be specified by an array.
|
134
|
-
#
|
135
|
-
# to_query('key' => 'value', 'array' => ['one', 'two']) # => "array=one&array=two&key=value"
|
136
|
-
#
|
137
|
-
# Note: the order of the parameters in the result is determined
|
138
|
-
# by hash.each_pair and is thus fairly unpredicatable.
|
139
|
-
def to_query(hash)
|
140
|
-
query = []
|
141
|
-
hash.each_pair do |key,values|
|
142
|
-
values = values.kind_of?(Array) ? values : [values]
|
143
|
-
values.each { |value| query << "#{key}=#{value}" }
|
144
|
-
end
|
145
|
-
URI.encode(query.join('&'))
|
146
|
-
end
|
147
62
|
end
|
148
63
|
end
|
149
64
|
end
|