tap-http 0.3.2 → 0.4.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 +4 -4
- data/README +17 -17
- data/lib/tap/http/capture.rb +176 -0
- data/lib/tap/http/get.rb +3 -9
- data/lib/tap/http/submit.rb +84 -9
- data/lib/tap/http/utils.rb +1 -11
- data/lib/tap/test/http_test.rb +6 -4
- data/tap-http.gemspec +41 -0
- data/views/tap/http/capture/footer.erb +4 -0
- data/views/tap/http/capture/header.erb +5 -0
- data/views/tap/http/capture/http.erb +31 -0
- data/views/tap/http/capture/index.erb +11 -0
- data/views/tap/http/capture/redirect.css +42 -0
- data/views/tap/http/capture/redirect.js +177 -0
- data/views/{capture_controller → tap/http/capture}/tutorial.erb +17 -4
- metadata +16 -10
- data/controllers/capture_controller.rb +0 -82
- data/lib/tap/http/request.rb +0 -263
data/History
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
== 0.
|
1
|
+
== 0.4.0 / 2009-03-17
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
Significant rewrite using an updated version of TapServer
|
4
|
+
and Mechanize. TapHttp now serves its own redirection
|
5
|
+
commands via TapUbiquity.
|
6
6
|
|
7
7
|
== 0.3.1 / 2009-02-19
|
8
8
|
|
data/README
CHANGED
@@ -4,11 +4,11 @@ A task library for submitting http requests using {Tap}[http://tap.rubyforge.org
|
|
4
4
|
|
5
5
|
== Description
|
6
6
|
|
7
|
-
TapHttp provides modules to
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
TapHttp provides modules to capture and resubmit HTTP requests. TapHttp is
|
8
|
+
designed to work with a
|
9
|
+
{Ubiquity}[http://labs.mozilla.com/2008/08/introducing-ubiquity/] command
|
10
|
+
called redirect-http; together they allow the capture and resubmission of web
|
11
|
+
forms.
|
12
12
|
|
13
13
|
* Lighthouse[http://bahuvrihi.lighthouseapp.com/projects/9908-tap-task-application/tickets]
|
14
14
|
* Github[http://github.com/bahuvrihi/tap-http/tree/master]
|
@@ -16,28 +16,28 @@ they allow the capture and resubmission of web forms.
|
|
16
16
|
|
17
17
|
=== Usage
|
18
18
|
|
19
|
-
TapHttp submits
|
20
|
-
|
21
|
-
|
19
|
+
TapHttp submits HTTP requests using
|
20
|
+
Mechanize[http://mechanize.rubyforge.org/mechanize/], via the
|
21
|
+
Tap::Http::Submit task. Headers and parameters may be specified, but only a
|
22
|
+
uri is required.
|
22
23
|
|
23
|
-
|
24
|
-
|
25
|
-
res = Request.get('http://www.google.com/search')
|
26
|
-
res.body[0,80] # => "<!doctype html><head><title>tap-http - Google Search</title><style>body{backgrou"
|
24
|
+
Tap::Http::Submit.new.process(:uri => 'http://www.google.com')[0, 80]
|
25
|
+
# => "<html><head><meta http-equiv=\"content-type\" content=\"text/html; charset=ISO-8859"
|
27
26
|
|
28
27
|
=== Submitting Web Forms
|
29
28
|
|
30
|
-
|
31
|
-
of tools.
|
32
|
-
|
29
|
+
HTTP requests from web forms may be captured and resubmitted using a
|
30
|
+
combination of tools. To learn how, install tap-http and start a tap server
|
31
|
+
from the command line:
|
33
32
|
|
34
33
|
% tap server
|
35
34
|
|
36
|
-
Now open a browser and work through the {tutorial}[http://localhost:8080/capture].
|
35
|
+
Now open a browser and work through the {tutorial}[http://localhost:8080/capture/tutorial].
|
37
36
|
|
38
37
|
== Installation
|
39
38
|
|
40
|
-
TapHttp is available as a gem on RubyForge[http://rubyforge.org/projects/tap].
|
39
|
+
TapHttp is available as a gem on RubyForge[http://rubyforge.org/projects/tap].
|
40
|
+
Use:
|
41
41
|
|
42
42
|
% gem install tap-http
|
43
43
|
|
@@ -0,0 +1,176 @@
|
|
1
|
+
require 'tap/controller'
|
2
|
+
require 'tap/http/utils'
|
3
|
+
require 'tap/ubiquity/utils'
|
4
|
+
|
5
|
+
module Tap
|
6
|
+
module Http
|
7
|
+
# ::controller
|
8
|
+
# ::ubiquity
|
9
|
+
class Capture < Tap::Controller
|
10
|
+
include Tap::Http::Utils
|
11
|
+
include Tap::Ubiquity::Utils
|
12
|
+
|
13
|
+
PREFIX = '__redirect_http_'
|
14
|
+
REDIRECT_PARAMETER = '__redirect_http_original_action'
|
15
|
+
|
16
|
+
# Brings up the tutorial.
|
17
|
+
def index
|
18
|
+
render 'index.erb', :locals => {:captures => persistence.index }
|
19
|
+
end
|
20
|
+
|
21
|
+
def create(id)
|
22
|
+
persistence.create(id) do |io|
|
23
|
+
io << YAML.dump([parse_request])
|
24
|
+
end
|
25
|
+
download(id)
|
26
|
+
end
|
27
|
+
|
28
|
+
def show(id)
|
29
|
+
response['Content-Type'] = "text/plain"
|
30
|
+
persistence.read(id)
|
31
|
+
end
|
32
|
+
|
33
|
+
def download(id)
|
34
|
+
path = persistence.path(id)
|
35
|
+
filename = id
|
36
|
+
filename += ".yml" if File.extname(id) == ""
|
37
|
+
|
38
|
+
response['Content-Type'] = "text/plain"
|
39
|
+
response['Content-Disposition'] = "attachment; filename=#{filename};"
|
40
|
+
persistence.read(id)
|
41
|
+
end
|
42
|
+
|
43
|
+
def update(id="request")
|
44
|
+
path = persistence.path(id)
|
45
|
+
requests = File.exists?(path) ? YAML.load_file(path) : []
|
46
|
+
requests << parse_request
|
47
|
+
|
48
|
+
persistence.update(id) do |io|
|
49
|
+
io << YAML.dump(requests)
|
50
|
+
end
|
51
|
+
download(id)
|
52
|
+
end
|
53
|
+
|
54
|
+
def destroy(id)
|
55
|
+
persistence.destroy(id)
|
56
|
+
index
|
57
|
+
end
|
58
|
+
|
59
|
+
# Brings up a tutorial teaching how to capture and resubmit forms.
|
60
|
+
def tutorial
|
61
|
+
serve js_injection(:redirect_http) do |link|
|
62
|
+
content = render 'tutorial.erb'
|
63
|
+
content + link
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Say is the target of the tutorial.
|
68
|
+
def say
|
69
|
+
"<pre>#{request.params['words']}</pre>"
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns the redirection script.
|
73
|
+
def redirect_http
|
74
|
+
style = render 'redirect.css'
|
75
|
+
script = render 'redirect.js', :locals => {
|
76
|
+
:redirect_parameter => REDIRECT_PARAMETER,
|
77
|
+
:redirect_action => uri(:update),
|
78
|
+
:header => render('header.erb'),
|
79
|
+
:footer => render('footer.erb')
|
80
|
+
}
|
81
|
+
|
82
|
+
if request.get?
|
83
|
+
response['Content-Type'] = 'text/plain'
|
84
|
+
%Q{
|
85
|
+
<style type="text/css">
|
86
|
+
#{style.strip}
|
87
|
+
</style>
|
88
|
+
<script type="text/javascript">
|
89
|
+
#{script.strip}
|
90
|
+
</script>}
|
91
|
+
else
|
92
|
+
response['Content-Type'] = 'text/javascript'
|
93
|
+
%Q{
|
94
|
+
var style = document.createElement("style");
|
95
|
+
style.type = "text/css";
|
96
|
+
style.innerHTML = #{style.to_json};
|
97
|
+
document.body.appendChild(style);
|
98
|
+
|
99
|
+
var script = document.createElement("script");
|
100
|
+
script.type = "text/javascript";
|
101
|
+
script.innerHTML = #{script.to_json};
|
102
|
+
document.body.appendChild(script);
|
103
|
+
}
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Parses HTTP request
|
108
|
+
def http
|
109
|
+
if request.get?
|
110
|
+
render 'http.erb'
|
111
|
+
else
|
112
|
+
keep_content = request.params['keep_content'] == "true"
|
113
|
+
|
114
|
+
hash = {}
|
115
|
+
parse_http_request(request.params['http'], keep_content).each_pair do |key, value|
|
116
|
+
hash[key.to_s] = value
|
117
|
+
end
|
118
|
+
|
119
|
+
# remove extranous data
|
120
|
+
hash.delete('headers')
|
121
|
+
hash.delete('version')
|
122
|
+
|
123
|
+
response['Content-Type'] = "text/plain"
|
124
|
+
YAML.dump(hash)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
protected
|
129
|
+
|
130
|
+
def prefix
|
131
|
+
PREFIX
|
132
|
+
end
|
133
|
+
|
134
|
+
def capture_overloaded_parameters # :nodoc:
|
135
|
+
# perform the actions of Rack::Request::POST, but capture
|
136
|
+
# overloaded parameter names
|
137
|
+
env = request.env
|
138
|
+
env["rack.request.form_input"] = env["rack.input"]
|
139
|
+
unless env["rack.request.form_hash"] = Tap::Http::Utils.parse_multipart(env)
|
140
|
+
env["rack.request.form_vars"] = env["rack.input"].read
|
141
|
+
env["rack.request.form_hash"] = Rack::Utils.parse_query(env["rack.request.form_vars"])
|
142
|
+
env["rack.input"].rewind if env["rack.input"].respond_to?(:rewind)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# helper to parse the request into a request hash for
|
147
|
+
# use by a Tap::Http::Submit task
|
148
|
+
def parse_request(keep_content=true) # :nodoc:
|
149
|
+
capture_overloaded_parameters
|
150
|
+
|
151
|
+
hash = {}
|
152
|
+
parse_rack_request(request, keep_content).each_pair do |key, value|
|
153
|
+
hash[key.to_s] = value
|
154
|
+
end
|
155
|
+
|
156
|
+
action = hash['params'].delete(REDIRECT_PARAMETER)
|
157
|
+
hash['uri'] = if action =~ /^http/
|
158
|
+
# action is an href already
|
159
|
+
action
|
160
|
+
else
|
161
|
+
# make action relative to Referer
|
162
|
+
uri = URI.parse(hash['headers']['Referer'].to_s)
|
163
|
+
uri.path = action
|
164
|
+
uri.to_s
|
165
|
+
end
|
166
|
+
|
167
|
+
# remove extranous data
|
168
|
+
hash.delete('headers')
|
169
|
+
hash.delete('version')
|
170
|
+
|
171
|
+
hash
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
data/lib/tap/http/get.rb
CHANGED
@@ -1,19 +1,13 @@
|
|
1
|
-
require 'tap/http/
|
1
|
+
require 'tap/http/submit'
|
2
2
|
|
3
3
|
module Tap
|
4
4
|
module Http
|
5
5
|
# Tap::Http::Get::manifest gets the uri
|
6
6
|
# Submits an Http request to the specified uri and returns the message body.
|
7
|
-
class Get <
|
8
|
-
include Request
|
9
|
-
|
7
|
+
class Get < Submit
|
10
8
|
def process(uri)
|
11
|
-
|
12
|
-
res = Request.get(uri)
|
13
|
-
log(nil, res.message)
|
14
|
-
res.body
|
9
|
+
super(:uri => uri)
|
15
10
|
end
|
16
|
-
|
17
11
|
end
|
18
12
|
end
|
19
13
|
end
|
data/lib/tap/http/submit.rb
CHANGED
@@ -1,20 +1,63 @@
|
|
1
|
-
require '
|
1
|
+
require 'mechanize'
|
2
2
|
|
3
3
|
module Tap
|
4
4
|
module Http
|
5
5
|
# Tap::Http::Submit::manifest submits a captured http request
|
6
6
|
class Submit < Tap::Task
|
7
|
+
Form = WWW::Mechanize::Form
|
7
8
|
|
8
|
-
|
9
|
-
|
9
|
+
# mechanize agent configurations
|
10
|
+
nest :mechanize do
|
11
|
+
config :ca_file, nil
|
12
|
+
config :cert, nil
|
13
|
+
config :conditional_requests, true
|
14
|
+
config :follow_meta_refresh, false
|
15
|
+
config :keep_alive, true
|
16
|
+
config :keep_alive_time, 300
|
17
|
+
config :key, nil
|
18
|
+
config :open_timeout, nil
|
19
|
+
config :pass, nil
|
20
|
+
config :read_timeout, nil
|
21
|
+
config :redirect_ok, true
|
22
|
+
config :redirection_limit, 20
|
23
|
+
config :user_agent, "WWW-Mechanize/0.9.2 (http://rubyforge.org/projects/mechanize/)"
|
24
|
+
config :verify_callback, nil
|
25
|
+
config :watch_for_set, nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def process(*requests)
|
29
|
+
# initialize mechanize agent
|
30
|
+
agent = WWW::Mechanize.new
|
31
|
+
agent.log = app.logger unless app.quiet
|
32
|
+
|
33
|
+
# reconfigures agent with mechanize config
|
34
|
+
mechanize.config.dup.bind(agent)
|
10
35
|
|
11
|
-
|
12
|
-
|
36
|
+
agent.pre_connect_hooks << lambda do |options|
|
37
|
+
connection = options[:connection]
|
38
|
+
unless connection.started?
|
39
|
+
log :mechanize, "#{connection.address}#{connection.use_ssl? ? ' (ssl)' : ''}"
|
40
|
+
end
|
41
|
+
end
|
13
42
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
43
|
+
# handle requests
|
44
|
+
requests.inject(nil) do |last_page, request|
|
45
|
+
request = symbolize(request)
|
46
|
+
|
47
|
+
# note get handles nil request methods (ie '') and has to
|
48
|
+
# reassign uri to url to make mechanize happy
|
49
|
+
page = case request[:request_method].to_s
|
50
|
+
when /get/i, '' then agent.get(request.merge(:url => request[:uri]))
|
51
|
+
when /post/i then agent.submit(prepare_form(request), nil)
|
52
|
+
when /head/i then agent.head(nil, nil, request)
|
53
|
+
when /put/i then agent.put(nil, nil, request)
|
54
|
+
when /delete/i then agent.delete(nil, nil, request)
|
55
|
+
else raise "unsupported request method: #{request.inspect}"
|
56
|
+
end
|
57
|
+
|
58
|
+
# process page results if desired
|
59
|
+
block_given? ? yield(page) : page.content
|
60
|
+
end
|
18
61
|
end
|
19
62
|
|
20
63
|
protected
|
@@ -26,6 +69,38 @@ module Tap
|
|
26
69
|
options
|
27
70
|
end
|
28
71
|
end
|
72
|
+
|
73
|
+
# A subclass of Hash used exclusively as the node for a form.
|
74
|
+
# Evidently a search method is necessary.
|
75
|
+
class FormNode < Hash # :nodoc:
|
76
|
+
def search(*args); []; end
|
77
|
+
end
|
78
|
+
|
79
|
+
# prepares a Mechanize::Form for the request. this method is patterned
|
80
|
+
# after what happens in Mechanize#post.
|
81
|
+
def prepare_form(request) # :nodoc:
|
82
|
+
node = FormNode.new
|
83
|
+
node['method'] = request[:request_method]
|
84
|
+
node['action'] = request[:uri]
|
85
|
+
node['enctype'] = request[:headers] ? request[:headers]['Content-Type'] : nil
|
86
|
+
|
87
|
+
form = Form.new(node)
|
88
|
+
request[:params].each_pair do |key, value|
|
89
|
+
case value
|
90
|
+
when IO
|
91
|
+
form.enctype = 'multipart/form-data'
|
92
|
+
upload = Form::FileUpload.new(key.to_s, ::File.basename(value.path))
|
93
|
+
upload.file_data = value.read
|
94
|
+
form.file_uploads << upload
|
95
|
+
when Array
|
96
|
+
fields = value.collect {|val| Form::Field.new(key.to_s, val)}
|
97
|
+
form.fields.concat fields
|
98
|
+
else
|
99
|
+
form.fields << Form::Field.new(key.to_s, value)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
form
|
103
|
+
end
|
29
104
|
end
|
30
105
|
end
|
31
106
|
end
|
data/lib/tap/http/utils.rb
CHANGED
@@ -113,7 +113,7 @@ module Tap
|
|
113
113
|
params[key] = splat(values)
|
114
114
|
end if req.query
|
115
115
|
|
116
|
-
{ :
|
116
|
+
{ :uri => headers['Host'] ? File.join("http://", headers['Host'], req.path_info) : req.path_info,
|
117
117
|
:request_method => req.request_method,
|
118
118
|
:version => req.http_version.to_s,
|
119
119
|
:headers => headers,
|
@@ -199,16 +199,6 @@ module Tap
|
|
199
199
|
$1.upcase + $2.downcase
|
200
200
|
end.join("-")
|
201
201
|
end
|
202
|
-
|
203
|
-
# Inflates (ie unzips) a gzip string, as may be returned by requests
|
204
|
-
# that accept 'gzip' and 'deflate' content encoding.
|
205
|
-
#
|
206
|
-
#--
|
207
|
-
# Utils.inflate(res.body) if res['content-encoding'] == 'gzip'
|
208
|
-
#
|
209
|
-
def inflate(str)
|
210
|
-
Zlib::GzipReader.new( StringIO.new( str ) ).read
|
211
|
-
end
|
212
202
|
|
213
203
|
EOL = Rack::Utils::Multipart::EOL
|
214
204
|
# Lifted from Rack::Utils::Multipart, and modified to collect
|
data/lib/tap/test/http_test.rb
CHANGED
@@ -14,12 +14,14 @@ module Tap
|
|
14
14
|
module HttpTest
|
15
15
|
|
16
16
|
class MockServer
|
17
|
-
def initialize(
|
18
|
-
@
|
17
|
+
def initialize(status=200, headers={}, &block)
|
18
|
+
@status = status
|
19
|
+
@headers = headers
|
20
|
+
@block = block
|
19
21
|
end
|
20
|
-
|
22
|
+
|
21
23
|
def call(env)
|
22
|
-
@
|
24
|
+
[@status, @headers, @block.call(env)]
|
23
25
|
end
|
24
26
|
end
|
25
27
|
|
data/tap-http.gemspec
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "tap-http"
|
3
|
+
s.version = "0.4.0"
|
4
|
+
s.author = "Simon Chiang"
|
5
|
+
s.email = "simon.a.chiang@gmail.com"
|
6
|
+
s.homepage = "http://tap.rubyforge.org/tap-http"
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.summary = "A task library for submitting http requests using Tap."
|
9
|
+
s.require_path = "lib"
|
10
|
+
s.rubyforge_project = "tap"
|
11
|
+
s.add_dependency("tap-ubiquity", "= 0.2.0")
|
12
|
+
s.add_dependency("mechanize", ">= 0.9.2")
|
13
|
+
s.has_rdoc = true
|
14
|
+
s.rdoc_options.concat %W{--main README -S -N --title Tap\sHttp}
|
15
|
+
|
16
|
+
# list extra rdoc files like README here.
|
17
|
+
s.extra_rdoc_files = %W{
|
18
|
+
README
|
19
|
+
MIT-LICENSE
|
20
|
+
History
|
21
|
+
}
|
22
|
+
|
23
|
+
# list the files you want to include here. you can
|
24
|
+
# check this manifest using 'rake :print_manifest'
|
25
|
+
s.files = %W{
|
26
|
+
lib/tap/http/capture.rb
|
27
|
+
lib/tap/http/get.rb
|
28
|
+
lib/tap/http/submit.rb
|
29
|
+
lib/tap/http/utils.rb
|
30
|
+
lib/tap/test/http_test.rb
|
31
|
+
tap-http.gemspec
|
32
|
+
tap.yml
|
33
|
+
views/tap/http/capture/footer.erb
|
34
|
+
views/tap/http/capture/header.erb
|
35
|
+
views/tap/http/capture/index.erb
|
36
|
+
views/tap/http/capture/http.erb
|
37
|
+
views/tap/http/capture/redirect.css
|
38
|
+
views/tap/http/capture/redirect.js
|
39
|
+
views/tap/http/capture/tutorial.erb
|
40
|
+
}
|
41
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
<h1>Parse HTTP Parameters</h1>
|
2
|
+
|
3
|
+
<p>Enter an HTTP request, like the ones you can capture using the
|
4
|
+
<a href='https://addons.mozilla.org/en-US/firefox/addon/3829'>LiveHTTPHeaders</a> addon for
|
5
|
+
<a href='http://www.mozilla.com/en-US/firefox/'>Firefox</a>.
|
6
|
+
</p>
|
7
|
+
|
8
|
+
<form action='<%= uri(:http) %>' method='post'>
|
9
|
+
<textarea rows='20' cols='60' name='http'></textarea>
|
10
|
+
<br/>
|
11
|
+
<input type='checkbox' name='keep_content' value='true' checked='true'> Keep File Content
|
12
|
+
<input type='submit' value='Parse'>
|
13
|
+
</form>
|
14
|
+
|
15
|
+
<p>Note the request must be properly formated. For example:</p>
|
16
|
+
|
17
|
+
<pre>
|
18
|
+
GET / HTTP/1.1
|
19
|
+
Host: tap.rubyforge.org
|
20
|
+
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.12) Gecko/20080201 Firefox/2.0.0.12
|
21
|
+
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
|
22
|
+
Accept-Language: en-us,en;q=0.5
|
23
|
+
Accept-Encoding: gzip,deflate
|
24
|
+
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
|
25
|
+
Keep-Alive: 300
|
26
|
+
Connection: keep-alive
|
27
|
+
</pre>
|
28
|
+
|
29
|
+
<p>Note that POST requests require an empty line between the headers and body.
|
30
|
+
Without it you get a WEBrick::HTTPStatus::BadRequest error.</p>
|
31
|
+
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<p>Captures:</p>
|
2
|
+
<ul>
|
3
|
+
<% if captures.empty? %>
|
4
|
+
<li>(none)</li>
|
5
|
+
<% end %>
|
6
|
+
<% captures.each do |capture| %>
|
7
|
+
<li><a href="<%= uri("show/#{capture}") %>"><%= capture %></a> (<a href="<%= uri("destroy/#{capture}") %>">x</a>)</li>
|
8
|
+
<% end %>
|
9
|
+
</ul>
|
10
|
+
|
11
|
+
<p>See the <a href="<%= uri(:tutorial) %>">Tutorial</a> for help.</p>
|
@@ -0,0 +1,42 @@
|
|
1
|
+
div.<%= prefix %>notification {
|
2
|
+
position:relative;
|
3
|
+
text-align:center;
|
4
|
+
background-color:#EFE3BC;
|
5
|
+
border: thin solid white;
|
6
|
+
font-family: arial;
|
7
|
+
font-size: 16;
|
8
|
+
}
|
9
|
+
|
10
|
+
div#<%= prefix %>header {
|
11
|
+
position:relative;
|
12
|
+
top:0;
|
13
|
+
left:0;
|
14
|
+
right:0;
|
15
|
+
}
|
16
|
+
|
17
|
+
div#<%= prefix %>footer {
|
18
|
+
position:relative;
|
19
|
+
bottom:0;
|
20
|
+
left:0;
|
21
|
+
right:0;
|
22
|
+
}
|
23
|
+
|
24
|
+
div.<%= prefix %>message {
|
25
|
+
display:inline;
|
26
|
+
font-weight:bold;
|
27
|
+
}
|
28
|
+
|
29
|
+
div.<%= prefix %>revert {
|
30
|
+
display:inline;
|
31
|
+
position:absolute;
|
32
|
+
right:0;
|
33
|
+
font-size:0.8em;
|
34
|
+
font-weight:normal;
|
35
|
+
text-decoration:none;
|
36
|
+
}
|
37
|
+
|
38
|
+
div.<%= prefix %>controls {
|
39
|
+
display:inline;
|
40
|
+
position:absolute;
|
41
|
+
left:0;
|
42
|
+
}
|
@@ -0,0 +1,177 @@
|
|
1
|
+
var RedirectHttp = {
|
2
|
+
|
3
|
+
active: function() {
|
4
|
+
var header = document.getElementById("<%= prefix %>header");
|
5
|
+
if(header) return true;
|
6
|
+
else return false;
|
7
|
+
},
|
8
|
+
|
9
|
+
follow_request: function() {
|
10
|
+
var checkbox = document.getElementById("<%= prefix %>continue");
|
11
|
+
if(checkbox && checkbox.checked) return true;
|
12
|
+
else return false;
|
13
|
+
},
|
14
|
+
|
15
|
+
redirect_uri: function(tag) {
|
16
|
+
var id = document.getElementById("<%= prefix %>id");
|
17
|
+
return '<%= redirect_action %>/' + escape(id.value);
|
18
|
+
},
|
19
|
+
|
20
|
+
redirect_form: function(form) {
|
21
|
+
if(RedirectHttp.active()) {
|
22
|
+
form.setAttribute('action', RedirectHttp.redirect_uri());
|
23
|
+
form.submit();
|
24
|
+
|
25
|
+
if(RedirectHttp.follow_request()) {
|
26
|
+
this.revert();
|
27
|
+
form.submit();
|
28
|
+
}
|
29
|
+
|
30
|
+
return false;
|
31
|
+
}
|
32
|
+
|
33
|
+
return true;
|
34
|
+
},
|
35
|
+
|
36
|
+
redirect_anchor: function(anchor) {
|
37
|
+
if(RedirectHttp.active()) {
|
38
|
+
var original_href = anchor.getAttribute('<%= prefix %>original_href');
|
39
|
+
location.href = RedirectHttp.redirect_uri() + '?<%= redirect_parameter %>=' + escape(original_href);
|
40
|
+
|
41
|
+
if(RedirectHttp.follow_request()) {
|
42
|
+
this.revert();
|
43
|
+
location.href = anchor.href;
|
44
|
+
}
|
45
|
+
|
46
|
+
return false;
|
47
|
+
}
|
48
|
+
|
49
|
+
return true;
|
50
|
+
},
|
51
|
+
|
52
|
+
// Redirects forms and links to '<%= redirect_action %>'
|
53
|
+
redirect: function() {
|
54
|
+
// Revert if forms have already been redirected
|
55
|
+
if(RedirectHttp.active()) {
|
56
|
+
RedirectHttp.revert();
|
57
|
+
return false;
|
58
|
+
}
|
59
|
+
|
60
|
+
try {
|
61
|
+
|
62
|
+
// redirect forms to the redirect_action
|
63
|
+
var forms = document.getElementsByTagName('form');
|
64
|
+
for(i=0; i<forms.length; i++) {
|
65
|
+
var form = forms[i];
|
66
|
+
|
67
|
+
// store the original action
|
68
|
+
var input = document.createElement("input");
|
69
|
+
input.setAttribute("id", '<%= redirect_parameter %>_' + i);
|
70
|
+
input.setAttribute("name", '<%= redirect_parameter %>');
|
71
|
+
input.setAttribute("type", 'hidden');
|
72
|
+
input.setAttribute("value", form.getAttribute('action'));
|
73
|
+
|
74
|
+
form.setAttribute('<%= prefix %>index', i);
|
75
|
+
form.appendChild(input);
|
76
|
+
|
77
|
+
// redirect onSubmit
|
78
|
+
var current_on_submit = '';
|
79
|
+
if(form.hasAttribute("onSubmit")) current_on_submit = form.getAttribute("onSubmit");
|
80
|
+
var on_submit = "return RedirectHttp.redirect_form(this);";
|
81
|
+
|
82
|
+
form.setAttribute("onSubmit", current_on_submit + ";" + on_submit);
|
83
|
+
form.setAttribute("<%= prefix %>original_callback", current_on_submit);
|
84
|
+
}
|
85
|
+
|
86
|
+
// redirect anchors to the redirect_action
|
87
|
+
var anchors = document.getElementsByTagName('a');
|
88
|
+
for(i=0; i<anchors.length; i++) {
|
89
|
+
var anchor = anchors[i];
|
90
|
+
|
91
|
+
// store the original href
|
92
|
+
anchor.setAttribute("<%= prefix %>original_href", anchor.href);
|
93
|
+
|
94
|
+
// redirect onClick
|
95
|
+
var current_on_click = '';
|
96
|
+
if(anchor.hasAttribute("onClick")) current_on_click = anchor.getAttribute("onClick");
|
97
|
+
var on_click = "return RedirectHttp.redirect_anchor(this);";
|
98
|
+
|
99
|
+
anchor.setAttribute("onClick", current_on_click + ";" + on_click);
|
100
|
+
anchor.setAttribute("<%= prefix %>original_callback", current_on_click);
|
101
|
+
}
|
102
|
+
|
103
|
+
// position and show notifications
|
104
|
+
var header = document.createElement("div");
|
105
|
+
header.setAttribute("id", "<%= prefix %>header");
|
106
|
+
header.setAttribute("class", "<%= prefix %>notification");
|
107
|
+
header.innerHTML = <%= header.to_json %>;
|
108
|
+
document.body.insertBefore(header, document.body.firstChild);
|
109
|
+
|
110
|
+
var footer = document.createElement("div");
|
111
|
+
footer.setAttribute("id", "<%= prefix %>footer");
|
112
|
+
footer.setAttribute("class", "<%= prefix %>notification");
|
113
|
+
footer.innerHTML = <%= footer.to_json %>;
|
114
|
+
document.body.appendChild(footer);
|
115
|
+
|
116
|
+
} catch(ex) {
|
117
|
+
alert_error(ex);
|
118
|
+
}
|
119
|
+
},
|
120
|
+
|
121
|
+
// Reverts redirection.
|
122
|
+
revert: function() {
|
123
|
+
try {
|
124
|
+
|
125
|
+
// redirect forms back to original actions
|
126
|
+
var forms = document.getElementsByTagName('form');
|
127
|
+
for(i=0; i<forms.length; i++) {
|
128
|
+
var form = forms[i];
|
129
|
+
|
130
|
+
// check in case the form is not redirected
|
131
|
+
if(form.hasAttribute('<%= prefix %>index')) {
|
132
|
+
var index = form.getAttribute('<%= prefix %>index');
|
133
|
+
var input = document.getElementById("<%= redirect_parameter %>_" + index);
|
134
|
+
|
135
|
+
form.setAttribute('action', input.value);
|
136
|
+
form.removeAttribute('<%= prefix %>index');
|
137
|
+
form.removeChild(input);
|
138
|
+
|
139
|
+
form.setAttribute('onSubmit', form.getAttribute('<%= prefix %>original_callback'));
|
140
|
+
form.removeAttribute('<%= prefix %>original_callback');
|
141
|
+
}
|
142
|
+
}
|
143
|
+
|
144
|
+
// redirect anchors back to original hrefs
|
145
|
+
var anchors = document.getElementsByTagName('a');
|
146
|
+
for(i=0; i<anchors.length; i++) {
|
147
|
+
var anchor = anchors[i];
|
148
|
+
|
149
|
+
// check in case the anchor is not redirected
|
150
|
+
if(anchor.hasAttribute('<%= prefix %>original_action')) {
|
151
|
+
anchor.setAttribute('href', anchor.getAttribute('<%= prefix %>original_href'));
|
152
|
+
anchor.removeAttribute('<%= prefix %>original_href');
|
153
|
+
|
154
|
+
anchor.setAttribute('onClick', anchor.getAttribute('<%= prefix %>original_callback'));
|
155
|
+
anchor.removeAttribute('<%= prefix %>original_callback');
|
156
|
+
}
|
157
|
+
}
|
158
|
+
|
159
|
+
// remove notifications
|
160
|
+
var header = document.getElementById("<%= prefix %>header");
|
161
|
+
if(header) document.body.removeChild(header);
|
162
|
+
|
163
|
+
var footer = document.getElementById("<%= prefix %>footer");
|
164
|
+
if(footer) document.body.removeChild(footer);
|
165
|
+
|
166
|
+
} catch(ex) {
|
167
|
+
alert_error(ex);
|
168
|
+
}
|
169
|
+
},
|
170
|
+
|
171
|
+
// Alerts of errors due to RedirectHttp
|
172
|
+
alert_error: function(ex) {
|
173
|
+
alert("Redirect HTTP Error: " + ex.message + "\n\n The webpage may have been corrupted; be sure to refresh.");
|
174
|
+
}
|
175
|
+
};
|
176
|
+
|
177
|
+
RedirectHttp.redirect();
|
@@ -6,7 +6,7 @@ resubmission at a later date. First, get the environment right:
|
|
6
6
|
<ul>
|
7
7
|
<li>Be sure you're viewing this in <a href="http://www.mozilla.com/en-US/firefox/">Firefox</a></li>
|
8
8
|
<li>Install <a href="http://labs.mozilla.com/2008/08/introducing-ubiquity/">Ubiquity</a></li>
|
9
|
-
<li>Subscribe to the
|
9
|
+
<li>Subscribe to the redirect-http command on this page</li>
|
10
10
|
</ul>
|
11
11
|
|
12
12
|
<p>
|
@@ -16,7 +16,7 @@ else you need to capture the parameters of a form):
|
|
16
16
|
|
17
17
|
<ul>
|
18
18
|
<li>Bring up Ubiquity in Firefox by pressing 'option+space'</li>
|
19
|
-
<li>Enter the command:
|
19
|
+
<li>Enter the command: redirect_http</li>
|
20
20
|
</ul>
|
21
21
|
|
22
22
|
<p>
|
@@ -34,7 +34,20 @@ body. To prove everything worked right, click the close link in the
|
|
34
34
|
redirection bar and resubmit the form.
|
35
35
|
</p>
|
36
36
|
|
37
|
+
<a href="/capture/say?words=link+was+clicked">Link</a>
|
37
38
|
<form action='/capture/say'>
|
38
|
-
|
39
|
-
<input type="submit" value="submit"
|
39
|
+
Form: <input name='words' value="form was submitted"/>
|
40
|
+
<input type="submit" value="submit" />
|
41
|
+
</form>
|
42
|
+
|
43
|
+
<p>
|
44
|
+
Note that redirection occurs via the on-click and on-submit callbacks for
|
45
|
+
links and forms, respectively. The existing behavior is preserved, as can
|
46
|
+
be demonstrated below:
|
47
|
+
</p>
|
48
|
+
|
49
|
+
<a href="/capture/say?words=link+was+clicked" onclick='alert("Link was clicked!")'>Link with OnClick</a>
|
50
|
+
<form action='/capture/say' onsubmit='alert("Form was submitted!")'>
|
51
|
+
Form with OnSubmit: <input name='words' value="form was submitted"/>
|
52
|
+
<input type="submit" value="submit" />
|
40
53
|
</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.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Simon Chiang
|
@@ -9,28 +9,28 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-03-17 00:00:00 -06:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
|
-
name: tap
|
16
|
+
name: tap-ubiquity
|
17
17
|
type: :runtime
|
18
18
|
version_requirement:
|
19
19
|
version_requirements: !ruby/object:Gem::Requirement
|
20
20
|
requirements:
|
21
|
-
- - "
|
21
|
+
- - "="
|
22
22
|
- !ruby/object:Gem::Version
|
23
|
-
version: 0.
|
23
|
+
version: 0.2.0
|
24
24
|
version:
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
|
-
name:
|
26
|
+
name: mechanize
|
27
27
|
type: :runtime
|
28
28
|
version_requirement:
|
29
29
|
version_requirements: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 0.9.
|
33
|
+
version: 0.9.2
|
34
34
|
version:
|
35
35
|
description:
|
36
36
|
email: simon.a.chiang@gmail.com
|
@@ -43,14 +43,20 @@ extra_rdoc_files:
|
|
43
43
|
- MIT-LICENSE
|
44
44
|
- History
|
45
45
|
files:
|
46
|
-
-
|
46
|
+
- lib/tap/http/capture.rb
|
47
47
|
- lib/tap/http/get.rb
|
48
|
-
- lib/tap/http/request.rb
|
49
48
|
- lib/tap/http/submit.rb
|
50
49
|
- lib/tap/http/utils.rb
|
51
50
|
- lib/tap/test/http_test.rb
|
52
|
-
-
|
51
|
+
- tap-http.gemspec
|
53
52
|
- tap.yml
|
53
|
+
- views/tap/http/capture/footer.erb
|
54
|
+
- views/tap/http/capture/header.erb
|
55
|
+
- views/tap/http/capture/index.erb
|
56
|
+
- views/tap/http/capture/http.erb
|
57
|
+
- views/tap/http/capture/redirect.css
|
58
|
+
- views/tap/http/capture/redirect.js
|
59
|
+
- views/tap/http/capture/tutorial.erb
|
54
60
|
- README
|
55
61
|
- MIT-LICENSE
|
56
62
|
- History
|
@@ -1,82 +0,0 @@
|
|
1
|
-
require 'tap/controller'
|
2
|
-
require 'tap/http/utils'
|
3
|
-
|
4
|
-
class CaptureController < Tap::Controller
|
5
|
-
include Tap::Http::Utils
|
6
|
-
|
7
|
-
def index
|
8
|
-
tutorial
|
9
|
-
end
|
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.
|
17
|
-
def say
|
18
|
-
"<pre>#{request.params['words']}</pre>"
|
19
|
-
end
|
20
|
-
|
21
|
-
# Echos back redirected HTTP requests as YAML, suitable for use with the
|
22
|
-
# Tap::Http::Submit task. All HTTP parameters and headers are echoed back
|
23
|
-
# directly, except for the '__original_action' parameter which is used in
|
24
|
-
# conjuction with the 'Referer' header to reconstruct the original url of
|
25
|
-
# the request. The '__original_action' parameter is not echoed.
|
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
|
34
|
-
|
35
|
-
response['Content-Type'] = "text/plain"
|
36
|
-
response['Content-Disposition'] = "attachment; filename=request.yml;"
|
37
|
-
|
38
|
-
YAML.dump(config)
|
39
|
-
end
|
40
|
-
|
41
|
-
# Echos back redirected HTTP requests formatted as YAML.
|
42
|
-
def echo
|
43
|
-
response['Content-Type'] = "text/plain"
|
44
|
-
YAML.dump(parse_request)
|
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
|
-
|
53
|
-
# perform the actions of Rack::Request::POST, but capturing
|
54
|
-
# overloaded parameter names
|
55
|
-
env = request.env
|
56
|
-
env["rack.request.form_input"] = env["rack.input"]
|
57
|
-
unless env["rack.request.form_hash"] = Tap::Http::Utils.parse_multipart(env)
|
58
|
-
env["rack.request.form_vars"] = env["rack.input"].read
|
59
|
-
env["rack.request.form_hash"] = Rack::Utils.parse_query(env["rack.request.form_vars"])
|
60
|
-
env["rack.input"].rewind if env["rack.input"].respond_to?(:rewind)
|
61
|
-
end
|
62
|
-
|
63
|
-
config = {}
|
64
|
-
parse_rack_request(request, keep_content).each_pair do |key, value|
|
65
|
-
config[key.to_s] = value
|
66
|
-
end
|
67
|
-
config
|
68
|
-
end
|
69
|
-
|
70
|
-
# helper to determine the url of a redirected action
|
71
|
-
def determine_url(action, referer) # :nodoc:
|
72
|
-
base = File.basename(referer)
|
73
|
-
|
74
|
-
case action
|
75
|
-
when /^https?:/ then action
|
76
|
-
when /\//
|
77
|
-
# only use host of page_url
|
78
|
-
File.join(base, action)
|
79
|
-
else File.join(base, action)
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
data/lib/tap/http/request.rb
DELETED
@@ -1,263 +0,0 @@
|
|
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 = hash.delete('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
|