tap-http 0.3.0 → 0.3.1
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 +6 -0
- data/README +0 -10
- data/controllers/capture_controller.rb +47 -31
- data/lib/tap/http/request.rb +2 -2
- data/lib/tap/http/utils.rb +53 -68
- data/views/capture_controller/{index.erb → tutorial.erb} +12 -2
- metadata +2 -2
data/History
CHANGED
data/README
CHANGED
@@ -35,16 +35,6 @@ must be installed):
|
|
35
35
|
|
36
36
|
Now open a browser and work through the {tutorial}[http://localhost:8080/capture].
|
37
37
|
|
38
|
-
=== Bugs/Known Issues
|
39
|
-
|
40
|
-
The Tap::Http::Utils#parse_cgi_request (used in parsing redirected requests
|
41
|
-
into a YAML file) is currently untested because I can't figure a way to setup
|
42
|
-
the ENV variables in a standard way. Of course I could set them up myself, but
|
43
|
-
I can't be sure I'm setting up a realistic test environment.
|
44
|
-
|
45
|
-
The capture procedure seems to work in practice, but please report bugs and let
|
46
|
-
me know if you know a way to setup a CGI environment for testing!
|
47
|
-
|
48
38
|
== Installation
|
49
39
|
|
50
40
|
TapHttp is available as a gem on RubyForge[http://rubyforge.org/projects/tap]. Use:
|
@@ -2,54 +2,70 @@ require 'tap/controller'
|
|
2
2
|
require 'tap/http/utils'
|
3
3
|
|
4
4
|
class CaptureController < Tap::Controller
|
5
|
+
include Tap::Http::Utils
|
5
6
|
|
6
7
|
def index
|
7
|
-
|
8
|
+
tutorial
|
8
9
|
end
|
9
10
|
|
11
|
+
# Brings up a tutorial teaching how to capture and resubmit forms.
|
12
|
+
def tutorial
|
13
|
+
render 'tutorial.erb'
|
14
|
+
end
|
15
|
+
|
16
|
+
# Say is the target of the tutorial.
|
10
17
|
def say
|
11
18
|
"<pre>#{request.params['words']}</pre>"
|
12
19
|
end
|
13
20
|
|
14
|
-
# Echos back redirected HTTP requests
|
21
|
+
# Echos back redirected HTTP requests as YAML, suitable for use with the
|
15
22
|
# Tap::Http::Submit task. All HTTP parameters and headers are echoed back
|
16
23
|
# directly, except for the '__original_action' parameter which is used in
|
17
24
|
# conjuction with the 'Referer' header to reconstruct the original url of
|
18
25
|
# the request. The '__original_action' parameter is not echoed.
|
19
|
-
def http_to_yaml
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
-
}
|
26
|
+
def http_to_yaml(keep_content='true')
|
27
|
+
keep_content = keep_content.to_s =~ /true/i
|
28
|
+
config = parse_request(keep_content)
|
29
|
+
|
30
|
+
original_action = config['params'].delete("__original_action").to_s
|
31
|
+
referer = config['headers']['Referer'].to_s
|
32
|
+
config['uri'] = determine_url(original_action, referer)
|
33
|
+
config['headers']['Host'] = URI.parse(config['uri']).host
|
37
34
|
|
38
35
|
response['Content-Type'] = "text/plain"
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
#
|
43
|
-
#{YAML.dump(config)}
|
44
|
-
}
|
36
|
+
response['Content-Disposition'] = "attachment; filename=request.yml;"
|
37
|
+
|
38
|
+
YAML.dump(config)
|
45
39
|
end
|
46
40
|
|
41
|
+
# Echos back redirected HTTP requests formatted as YAML.
|
47
42
|
def echo
|
48
|
-
|
49
|
-
|
50
|
-
|
43
|
+
response['Content-Type'] = "text/plain"
|
44
|
+
YAML.dump(config)
|
45
|
+
end
|
46
|
+
|
47
|
+
protected
|
48
|
+
|
49
|
+
# helper to parse the request into a request hash for
|
50
|
+
# use by a Tap::Http::Submit task
|
51
|
+
def parse_request(keep_content=true) # :nodoc:
|
52
|
+
config = {}
|
53
|
+
parse_rack_request(request, keep_content).each_pair do |key, value|
|
54
|
+
config[key.to_s] = value
|
51
55
|
end
|
52
|
-
|
53
|
-
|
56
|
+
config
|
57
|
+
end
|
58
|
+
|
59
|
+
# helper to determine the url of a redirected action
|
60
|
+
def determine_url(action, referer) # :nodoc:
|
61
|
+
base = File.basename(referer)
|
62
|
+
|
63
|
+
case action
|
64
|
+
when /^https?:/ then action
|
65
|
+
when /\//
|
66
|
+
# only use host of page_url
|
67
|
+
File.join(base, action)
|
68
|
+
else File.join(base, action)
|
69
|
+
end
|
54
70
|
end
|
55
71
|
end
|
data/lib/tap/http/request.rb
CHANGED
@@ -234,8 +234,8 @@ module Tap
|
|
234
234
|
body << case value
|
235
235
|
when Hash
|
236
236
|
hash = headerize_keys(value)
|
237
|
-
filename = hash.delete('Filename')
|
238
|
-
content = File.exists?(filename) ? File.read(filename) : ""
|
237
|
+
filename = hash.delete('Filename')
|
238
|
+
content = hash.delete('Content') || (File.exists?(filename) ? File.read(filename) : "")
|
239
239
|
|
240
240
|
header = "Content-Disposition: form-data; name=\"#{key.to_s}\"; filename=\"#{filename}\"\r\n"
|
241
241
|
hash.each_pair { |key, value| header << "#{key}: #{value}\r\n" }
|
data/lib/tap/http/utils.rb
CHANGED
@@ -22,17 +22,6 @@ module Tap
|
|
22
22
|
# # :params => {},
|
23
23
|
# # }
|
24
24
|
#
|
25
|
-
# If splat_values is specified, single-value headers and parameters
|
26
|
-
# will be hashed as single values. Otherwise, all header and parameter
|
27
|
-
# values will be arrays.
|
28
|
-
#
|
29
|
-
# str = "GET /path?one=a&one=b&two=c HTTP/1.1\n"
|
30
|
-
# req = parse_http_request(str)
|
31
|
-
# req[:params] # => {'one' => ['a', 'b'], 'two' => 'c'}
|
32
|
-
#
|
33
|
-
# req = parse_http_request(str, false)
|
34
|
-
# req[:params] # => {'one' => ['a', 'b'], 'two' => ['c']}
|
35
|
-
#
|
36
25
|
# ==== WEBrick parsing of HTTP format
|
37
26
|
#
|
38
27
|
# WEBrick will parse headers then the body of a request, and currently
|
@@ -84,21 +73,21 @@ module Tap
|
|
84
73
|
# TODO: check if there are other headers to capture from
|
85
74
|
# a multipart/form file. Currently only
|
86
75
|
# 'Filename' and 'Content-Type' are added
|
87
|
-
def parse_http_request(socket,
|
76
|
+
def parse_http_request(socket, keep_content=true)
|
88
77
|
socket = StringIO.new(socket) if socket.kind_of?(String)
|
89
78
|
|
90
79
|
req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
|
91
80
|
req.parse(socket)
|
92
81
|
|
93
|
-
parse_webrick_request(req,
|
82
|
+
parse_webrick_request(req, keep_content)
|
94
83
|
end
|
95
84
|
|
96
85
|
# Parses a WEBrick::HTTPRequest, with the same activity as
|
97
86
|
# parse_http_request.
|
98
|
-
def parse_webrick_request(req,
|
87
|
+
def parse_webrick_request(req, keep_content=true)
|
99
88
|
headers = {}
|
100
89
|
req.header.each_pair do |key, values|
|
101
|
-
headers[headerize(key)] =
|
90
|
+
headers[headerize(key)] = splat(values)
|
102
91
|
end if req.header
|
103
92
|
|
104
93
|
params = {}
|
@@ -112,13 +101,15 @@ module Tap
|
|
112
101
|
values = []
|
113
102
|
value.each_data do |data|
|
114
103
|
values << if data.filename
|
115
|
-
{'Filename' => data.filename, 'Content-Type' => data['Content-Type']}
|
104
|
+
hash = {'Filename' => data.filename, 'Content-Type' => data['Content-Type']}
|
105
|
+
hash['Content'] = data.to_a.join("\n") if keep_content
|
106
|
+
hash
|
116
107
|
else
|
117
108
|
data.to_s
|
118
109
|
end
|
119
110
|
end
|
120
111
|
|
121
|
-
params[key] =
|
112
|
+
params[key] = splat(values)
|
122
113
|
end if req.query
|
123
114
|
|
124
115
|
{ :url => headers['Host'] ? File.join("http://", headers['Host'], req.path_info) : req.path_info,
|
@@ -128,77 +119,57 @@ module Tap
|
|
128
119
|
:params => params}
|
129
120
|
end
|
130
121
|
|
131
|
-
# Parses
|
132
|
-
|
133
|
-
# set in ENV.
|
134
|
-
#
|
135
|
-
def parse_cgi_request(cgi, splat_values=true)
|
122
|
+
# Parses a Rack::Request, with the same activity as parse_http_request.
|
123
|
+
def parse_rack_request(request, keep_content=true)
|
136
124
|
headers = {}
|
137
|
-
|
125
|
+
request.env.each_pair do |key, value|
|
138
126
|
key = case key
|
139
127
|
when "HTTP_VERSION" then next
|
140
128
|
when /^HTTP_(.*)/ then $1
|
141
129
|
when 'CONTENT_TYPE' then key
|
142
130
|
else next
|
143
131
|
end
|
144
|
-
|
145
|
-
headers[headerize(key)] =
|
132
|
+
|
133
|
+
headers[headerize(key)] = value
|
146
134
|
end
|
147
|
-
|
135
|
+
|
148
136
|
params = {}
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
value
|
154
|
-
|
155
|
-
|
156
|
-
else
|
157
|
-
{'Filename' => value.original_filename, 'Content-Type' => value.content_type}
|
137
|
+
request.params.each_pair do |key, value|
|
138
|
+
params[key] = each_member(value) do |obj|
|
139
|
+
if obj.kind_of?(Hash)
|
140
|
+
file = {'Content-Type' => value[:type], 'Filename' => value[:filename]}
|
141
|
+
file['Content'] = value[:tempfile].read if keep_content
|
142
|
+
file
|
143
|
+
else value
|
158
144
|
end
|
159
145
|
end
|
160
|
-
|
161
|
-
params[key] = splat_values ? splat(values) : values
|
162
146
|
end
|
163
|
-
|
164
|
-
{
|
165
|
-
:
|
166
|
-
:
|
147
|
+
|
148
|
+
{
|
149
|
+
:uri => File.join("http://", headers['Host'], request.env['PATH_INFO']),
|
150
|
+
:request_method => request.request_method,
|
151
|
+
:version => request.env['HTTP_VERSION'] =~ /^HTTP\/(.*)$/ ? $1.to_f : request.env['HTTP_VERSION'],
|
167
152
|
:headers => headers,
|
168
|
-
:params => params
|
153
|
+
:params => params
|
154
|
+
}
|
169
155
|
end
|
170
156
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
end
|
181
|
-
end
|
182
|
-
|
183
|
-
# Headerizes an underscored string. The input is be converted to
|
184
|
-
# a string using to_s.
|
185
|
-
#
|
186
|
-
# headerize('SOME_STRING') # => 'Some-String'
|
187
|
-
# headerize('some string') # => 'Some-String'
|
188
|
-
# headerize('Some-String') # => 'Some-String'
|
189
|
-
#
|
190
|
-
def headerize(str)
|
191
|
-
str.to_s.gsub(/\s|-/, "_").split("_").collect do |s|
|
192
|
-
s =~ /^(.)(.*)/
|
193
|
-
$1.upcase + $2.downcase
|
194
|
-
end.join("-")
|
157
|
+
# Yields each member of an input array to the block and collects the
|
158
|
+
# result. If obj is not an array, the value is simply yielded to the
|
159
|
+
# block.
|
160
|
+
def each_member(obj)
|
161
|
+
if obj.kind_of?(Array)
|
162
|
+
obj.collect {|value| yield(value) }
|
163
|
+
else
|
164
|
+
yield(obj)
|
165
|
+
end
|
195
166
|
end
|
196
167
|
|
197
168
|
# Returns the first member of arrays length <= 1, or the array in all
|
198
|
-
# other cases.
|
169
|
+
# other cases. Splat is useful to simplify hashes of http headers
|
199
170
|
# and parameters that may have multiple values, but typically only
|
200
171
|
# have one.
|
201
|
-
#
|
172
|
+
#
|
202
173
|
# splat([]) # => nil
|
203
174
|
# splat([:one]) # => :one
|
204
175
|
# splat([:one, :two]) # => [:one, :two]
|
@@ -212,6 +183,20 @@ module Tap
|
|
212
183
|
else array
|
213
184
|
end
|
214
185
|
end
|
186
|
+
|
187
|
+
# Headerizes an underscored string. The input is be converted to
|
188
|
+
# a string using to_s.
|
189
|
+
#
|
190
|
+
# headerize('SOME_STRING') # => 'Some-String'
|
191
|
+
# headerize('some string') # => 'Some-String'
|
192
|
+
# headerize('Some-String') # => 'Some-String'
|
193
|
+
#
|
194
|
+
def headerize(str)
|
195
|
+
str.to_s.gsub(/\s|-/, "_").split("_").collect do |s|
|
196
|
+
s =~ /^(.)(.*)/
|
197
|
+
$1.upcase + $2.downcase
|
198
|
+
end.join("-")
|
199
|
+
end
|
215
200
|
|
216
201
|
# Inflates (ie unzips) a gzip string, as may be returned by requests
|
217
202
|
# that accept 'gzip' and 'deflate' content encoding.
|
@@ -21,10 +21,20 @@ else you need to capture the parameters of a form):
|
|
21
21
|
|
22
22
|
<p>
|
23
23
|
You should now see some notification that you're redirecting this page.
|
24
|
-
Write what you want to say, hit submit,
|
24
|
+
Write what you want to say, hit submit, and you'll be asked to save a
|
25
|
+
configuration file for the request. Save it, then on the command line
|
26
|
+
execute this:
|
25
27
|
</p>
|
26
28
|
|
27
|
-
<
|
29
|
+
<pre><code> % rap load <config_file> --: submit --: dump --no-audit</code></pre>
|
30
|
+
|
31
|
+
<p>
|
32
|
+
The request will be submitted and you'll get a printout of the response
|
33
|
+
body. To prove everything worked right, click the close link in the
|
34
|
+
redirection bar and resubmit the form.
|
35
|
+
</p>
|
36
|
+
|
37
|
+
<form action='/capture/say'>
|
28
38
|
What you want to say: <input name='words' />
|
29
39
|
<input type="submit" value="submit">
|
30
40
|
</form>
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tap-http
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Simon Chiang
|
@@ -49,7 +49,7 @@ files:
|
|
49
49
|
- lib/tap/http/submit.rb
|
50
50
|
- lib/tap/http/utils.rb
|
51
51
|
- lib/tap/test/http_test.rb
|
52
|
-
- views/capture_controller/
|
52
|
+
- views/capture_controller/tutorial.erb
|
53
53
|
- tap.yml
|
54
54
|
- README
|
55
55
|
- MIT-LICENSE
|