tap-http 0.3.2 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|