tap-http 0.2.1 → 0.3.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/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
|