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 CHANGED
@@ -1,3 +1,9 @@
1
+ == 0.3.1 / 2009-02-19
2
+
3
+ Improved handling of multipart/form data.
4
+
5
+ * http_to_yaml returns config file as an attachment.
6
+
1
7
  == 0.3.0 / 2009-02-19
2
8
 
3
9
  Rework of cgi scripts a tap controllers. Nearly complete
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
- render 'index.erb'
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 as YAML, suitable for use with the
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
- 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
- }
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
- %Q{# Save as a configuration file. Resubmit using:
40
- #
41
- # % rap load <config_file> --: submit --: dump --no-audit
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
- headers = {}
49
- request.env.each_pair do |key, value|
50
- headers[key] = value unless key =~ /^(rack|tap)/
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
- "<pre>#{headers.to_yaml}#{request.params.to_yaml}</pre>"
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
@@ -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" }
@@ -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, splat_values=true)
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, splat_values)
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, splat_values=true)
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)] = splat_values ? splat(values) : values
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] = splat_values ? splat(values) : values
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 the input CGI into a hash that may be resubmitted by Dispatch.
132
- # To work properly, the standard CGI environmental variables must be
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
- ENV.each_pair do |key, values|
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)] = splat_values ? splat(values) : values
132
+
133
+ headers[headerize(key)] = value
146
134
  end
147
-
135
+
148
136
  params = {}
149
- cgi.params.each_pair do |key, values|
150
- values = values.collect do |value|
151
- case
152
- when !value.respond_to?(:read)
153
- value
154
- when value.original_filename.empty?
155
- value.read
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
- { :url => File.join("http://", headers['Host'], ENV['PATH_INFO']),
165
- :request_method => ENV['REQUEST_METHOD'],
166
- :version => ENV['HTTP_VERSION'] =~ /^HTTP\/(.*)$/ ? $1.to_f : ENV['HTTP_VERSION'],
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
- def determine_url(action, referer)
172
- base = File.basename(referer)
173
-
174
- case action
175
- when /^https?:/ then action
176
- when /\//
177
- # only use host of page_url
178
- File.join(base, action)
179
- else File.join(base, action)
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. Splat is useful to simplify hashes of http headers
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, then follow the instructions.
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
- <form action='say'>
29
+ <pre><code> % rap load &lt;config_file&gt; --: 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.0
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/index.erb
52
+ - views/capture_controller/tutorial.erb
53
53
  - tap.yml
54
54
  - README
55
55
  - MIT-LICENSE