http_request.rb 1.1.8 → 1.1.9
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/Changelog +3 -0
- data/README.rdoc +1 -1
- data/lib/http_request.rb +522 -522
- metadata +2 -2
data/Changelog
CHANGED
data/README.rdoc
CHANGED
data/lib/http_request.rb
CHANGED
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
#
|
|
12
12
|
# == Version
|
|
13
13
|
#
|
|
14
|
-
# v1.1.
|
|
14
|
+
# v1.1.9
|
|
15
15
|
#
|
|
16
|
-
# Last Change:
|
|
16
|
+
# Last Change: 13 Sep, 2010
|
|
17
17
|
#
|
|
18
18
|
# == Author
|
|
19
19
|
#
|
|
@@ -29,514 +29,514 @@ require 'digest/md5'
|
|
|
29
29
|
require 'stringio'
|
|
30
30
|
|
|
31
31
|
class HttpRequest
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
32
|
+
include Singleton
|
|
33
|
+
class << self
|
|
34
|
+
# version
|
|
35
|
+
VERSION = '1.1.9'.freeze
|
|
36
|
+
def version;VERSION;end
|
|
37
|
+
|
|
38
|
+
# avaiabled http methods
|
|
39
|
+
def http_methods
|
|
40
|
+
%w{get head post put proppatch lock unlock options propfind delete move copy mkcol trace}
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# return data with or without block
|
|
44
|
+
def data(response, &block)
|
|
45
|
+
response.url = @@__url if defined? @@__url
|
|
46
|
+
block_given? ? block.call(response) : response
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# update cookies
|
|
50
|
+
def update_cookies(response)
|
|
51
|
+
return unless response.header['set-cookie']
|
|
52
|
+
response.get_fields('set-cookie').each {|k|
|
|
53
|
+
k, v = k.split(';')[0].split('=')
|
|
54
|
+
@@__cookies[k] = v
|
|
55
|
+
}
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# return cookies
|
|
59
|
+
def cookies
|
|
60
|
+
@@__cookies
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# check the http resource whether or not available
|
|
64
|
+
def available?(url, timeout = 5)
|
|
65
|
+
timeout(timeout) {
|
|
66
|
+
u = URI(url)
|
|
67
|
+
s = TCPSocket.new(u.host, u.port)
|
|
68
|
+
s.close
|
|
69
|
+
}
|
|
70
|
+
return true
|
|
71
|
+
rescue Exception => e
|
|
72
|
+
return false
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def data(response, &block)
|
|
77
|
+
self.class.data(response, &block)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# send request by some given parameters
|
|
81
|
+
def request(method, options, &block)
|
|
82
|
+
|
|
83
|
+
# parse the @options
|
|
84
|
+
parse_options(method, options)
|
|
85
|
+
|
|
86
|
+
# parse and merge for the options[:parameters]
|
|
87
|
+
parse_parameters
|
|
88
|
+
|
|
89
|
+
# send http request and get the response
|
|
90
|
+
response = send_request_and_get_response
|
|
91
|
+
|
|
92
|
+
return data(response, &block) unless @options[:redirect]
|
|
93
|
+
|
|
94
|
+
# redirect?
|
|
95
|
+
process_redirection response, &block
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# catch all available http requests
|
|
99
|
+
def self.method_missing(method_name, *options, &block)
|
|
100
|
+
options = if options.size.eql? 2
|
|
101
|
+
options.last.merge({:url => options.first})
|
|
102
|
+
else
|
|
103
|
+
options.first
|
|
104
|
+
end
|
|
105
|
+
@@redirect_times = 0
|
|
106
|
+
# we need to retrieve the cookies from last http response before reset cookies if it's a Net::HTTPResponse
|
|
107
|
+
options[:cookies] = options[:cookies].cookies if options[:cookies].is_a? Net::HTTPResponse
|
|
108
|
+
@@__cookies = {}
|
|
109
|
+
method_name = method_name.to_s.downcase
|
|
110
|
+
raise NoHttpMethodException, "No such http method can be called: #{method_name}" unless self.http_methods.include?(method_name)
|
|
111
|
+
self.instance.request(method_name, options, &block)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# for ftp, no plan to add new features to this method except bug fixing
|
|
115
|
+
def self.ftp(method, options, &block)
|
|
116
|
+
options = {:url => options} if options.is_a? String
|
|
117
|
+
options = {:close => true}.merge(options)
|
|
118
|
+
@@__url = options[:url] = "ftp://#{options[:url]}" unless options[:url] =~ /^ftp:\/\//
|
|
119
|
+
uri = URI(options[:url])
|
|
120
|
+
guest_name, guest_pass = 'anonymous', "guest@#{uri.host}"
|
|
121
|
+
unless options[:username]
|
|
122
|
+
options[:username], options[:password] =
|
|
123
|
+
uri.userinfo ? uri.userinfo.split(':') : [guest_name, guest_pass]
|
|
124
|
+
end
|
|
125
|
+
options[:username] = guest_name unless options[:username]
|
|
126
|
+
options[:password] = guest_pass if options[:password].nil?
|
|
127
|
+
ftp = Net::FTP.open(uri.host, options[:username], options[:password])
|
|
128
|
+
return data(ftp, &block) unless method
|
|
129
|
+
stat = case method.to_sym
|
|
130
|
+
when :get_as_string
|
|
131
|
+
require 'tempfile'
|
|
132
|
+
tmp = Tempfile.new('http_request_ftp')
|
|
133
|
+
ftp.getbinaryfile(uri.path, tmp.path)
|
|
134
|
+
ftp.response = tmp.read
|
|
135
|
+
tmp.close
|
|
136
|
+
unless block_given?
|
|
137
|
+
ftp.close
|
|
138
|
+
return ftp.response
|
|
139
|
+
end
|
|
140
|
+
when :get
|
|
141
|
+
options[:to] = File.basename(uri.path) unless options[:to]
|
|
142
|
+
ftp.getbinaryfile(uri.path, options[:to])
|
|
143
|
+
when :put
|
|
144
|
+
ftp.putbinaryfile(options[:from], uri.path)
|
|
145
|
+
when :mkdir, :rmdir, :delete, :size, :mtime, :list, :nlst
|
|
146
|
+
ftp.method(method).call(uri.path)
|
|
147
|
+
when :rename
|
|
148
|
+
ftp.rename(uri.path, options[:to]) if options[:to]
|
|
149
|
+
when :status
|
|
150
|
+
ftp.status
|
|
151
|
+
else
|
|
152
|
+
return ftp
|
|
153
|
+
end
|
|
154
|
+
if options[:close] and not block_given?
|
|
155
|
+
ftp.close
|
|
156
|
+
stat
|
|
157
|
+
else
|
|
158
|
+
ftp.response = stat unless ftp.response
|
|
159
|
+
data(ftp, &block)
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
private
|
|
164
|
+
|
|
165
|
+
def md5(string)
|
|
166
|
+
Digest::MD5.hexdigest string
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# get params for the Digest auth
|
|
170
|
+
# see: http://www.rooftopsolutions.nl/article/223
|
|
171
|
+
def get_params_for_digest
|
|
172
|
+
return '' unless @options[:auth_username] and @options[:auth_password]
|
|
173
|
+
user, passwd = @options[:auth_username], @options[:auth_password]
|
|
174
|
+
hr = self.class.send @options[:method],
|
|
175
|
+
@uri.userinfo ?
|
|
176
|
+
@@__url.sub(/:\/\/(.+?)@/, '://') :
|
|
177
|
+
@@__url
|
|
178
|
+
|
|
179
|
+
params = HttpRequestParams.parse hr['WWW-Authenticate'].split(' ', 2).last
|
|
180
|
+
method = @options[:method].upcase
|
|
181
|
+
cnonce = md5(rand.to_s + Time.new.to_s)
|
|
182
|
+
nc = "00000001"
|
|
183
|
+
|
|
184
|
+
data = []
|
|
185
|
+
data << md5("#{user}:#{params['realm']}:#{passwd}") \
|
|
186
|
+
<< params['nonce'] \
|
|
187
|
+
<< ('%08x' % nc) \
|
|
188
|
+
<< cnonce \
|
|
189
|
+
<< params['qop'] \
|
|
190
|
+
<< md5("#{method}:#{@uri.path}")
|
|
191
|
+
|
|
192
|
+
params = params.update({
|
|
193
|
+
:username => user,
|
|
194
|
+
:nc => nc,
|
|
195
|
+
:cnonce => cnonce,
|
|
196
|
+
:uri => @uri.path,
|
|
197
|
+
:method => method,
|
|
198
|
+
:response => md5(data.join(":"))
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
headers = []
|
|
202
|
+
params.each {|k, v| headers << "#{k}=\"#{v}\"" }
|
|
203
|
+
headers.join(", ")
|
|
204
|
+
rescue
|
|
205
|
+
''
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# for the Http Auth if need
|
|
209
|
+
def parse_http_auth
|
|
210
|
+
if @options[:auth] or
|
|
211
|
+
@uri.userinfo or
|
|
212
|
+
(@options[:auth_username] and @options[:auth_password])
|
|
213
|
+
|
|
214
|
+
if @options[:auth].is_a? Hash
|
|
215
|
+
@options[:auth_username] = @options[:auth][:username]
|
|
216
|
+
@options[:auth_password] = @options[:auth][:password]
|
|
217
|
+
@options[:auth] = @options[:auth][:type]
|
|
218
|
+
elsif @uri.userinfo and (!@options[:auth_username] or !@options[:auth_password])
|
|
219
|
+
@options[:auth_username], @options[:auth_password] = @uri.userinfo.split(/:/, 2)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
if @options[:auth_username] and @options[:auth_password]
|
|
223
|
+
# Digest Auth
|
|
224
|
+
if @options[:auth].to_s == 'digest'
|
|
225
|
+
digest = get_params_for_digest
|
|
226
|
+
@headers['Authorization'] = "Digest " + digest unless digest.empty?
|
|
227
|
+
else
|
|
228
|
+
# Basic Auth
|
|
229
|
+
@headers['Authorization'] = "Basic " +
|
|
230
|
+
["#{@options[:auth_username]}:#{@options[:auth_password]}"].pack('m').delete!("\r\n")
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# initialize for the http request
|
|
238
|
+
def parse_options(method, options)
|
|
239
|
+
options = {:url => options.to_s} if [String, Array].include? options.class
|
|
240
|
+
@options = {
|
|
241
|
+
:ssl_port => 443,
|
|
242
|
+
:redirect_limits => 5,
|
|
243
|
+
:redirect => true,
|
|
244
|
+
:url => nil,
|
|
245
|
+
:ajax => false,
|
|
246
|
+
:xhr => false,
|
|
247
|
+
:method => method
|
|
248
|
+
}
|
|
249
|
+
@options.merge!(options)
|
|
250
|
+
@@__url = @options[:url]
|
|
251
|
+
@uri = URI(@options[:url])
|
|
252
|
+
@uri.path = '/' if @uri.path.empty?
|
|
253
|
+
@headers = {
|
|
254
|
+
'Host' => @uri.host,
|
|
255
|
+
'Referer' => @options[:url],
|
|
256
|
+
'User-Agent' => 'HttpRequest.rb ' + self.class.version
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
# support gzip
|
|
260
|
+
begin; require 'zlib'; rescue LoadError; end
|
|
261
|
+
@headers['Accept-Encoding'] = 'gzip,deflate' if defined? ::Zlib
|
|
262
|
+
|
|
263
|
+
# ajax calls?
|
|
264
|
+
@headers['X-Requested-With'] = 'XMLHttpRequest' if @options[:ajax] or @options[:xhr]
|
|
265
|
+
|
|
266
|
+
# Http Authenication
|
|
267
|
+
parse_http_auth
|
|
268
|
+
|
|
269
|
+
# headers
|
|
270
|
+
@options[:headers].each {|k, v| @headers[k] = v} if @options[:headers].is_a? Hash
|
|
271
|
+
|
|
272
|
+
# add cookies if have
|
|
273
|
+
if @options[:cookies]
|
|
274
|
+
if @options[:cookies].is_a? Hash
|
|
275
|
+
cookies = []
|
|
276
|
+
@options[:cookies].each {|k, v|
|
|
277
|
+
cookies << "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}"
|
|
278
|
+
}
|
|
279
|
+
cookies = cookies.join('; ')
|
|
280
|
+
else
|
|
281
|
+
cookies = @options[:cookies].to_s
|
|
282
|
+
end
|
|
283
|
+
@headers['Cookie'] = cookies unless cookies.empty?
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# parse parameters for the options[:parameters] and @uri.query
|
|
288
|
+
def parse_parameters
|
|
289
|
+
if @options[:parameters].is_a?(Hash)
|
|
290
|
+
@options[:parameters] = @options[:parameters].collect{|k, v|
|
|
291
|
+
"#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}"
|
|
292
|
+
}.join('&')
|
|
293
|
+
end
|
|
294
|
+
@options[:parameters] = '' if @options[:parameters].nil?
|
|
295
|
+
if not @uri.query.to_s.empty?
|
|
296
|
+
@options[:parameters] << (@options[:parameters].empty? ? @uri.query : "&#{@uri.query}")
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# for uploading files
|
|
300
|
+
build_multipart if @options[:files] and 'post'.eql?(@options[:method])
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# for uploading files
|
|
304
|
+
def build_multipart
|
|
305
|
+
boundary = md5(rand.to_s).to_s[0..5]
|
|
306
|
+
@headers['Content-type'] = "multipart/form-data, boundary=#{boundary}"
|
|
307
|
+
multipart = []
|
|
308
|
+
if @options[:parameters].is_a?(String)
|
|
309
|
+
@options[:parameters] = CGI.parse(@options[:parameters])
|
|
310
|
+
if @options[:parameters].is_a? Hash
|
|
311
|
+
@options[:parameters].each {|k, v|
|
|
312
|
+
multipart << "--#{boundary}"
|
|
313
|
+
multipart << "Content-disposition: form-data; name=\"#{k}\""
|
|
314
|
+
multipart << "\r\n#{v.first}"
|
|
315
|
+
}
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
318
|
@options[:files] = [@options[:files]] if @options[:files].is_a?(Hash)
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
319
|
+
@options[:files].each_with_index {|f, index|
|
|
320
|
+
f[:field_name] ||= "files[]"
|
|
321
|
+
f[:file_name] ||= "#{boundary}_#{index}"
|
|
322
|
+
f[:transfer_encoding] ||= "binary"
|
|
323
|
+
f[:content_type] ||= 'application/octet-stream'
|
|
324
|
+
multipart << "--#{boundary}"
|
|
325
|
+
multipart << "Content-disposition: form-data; name=\"#{f[:field_name]}\"; filename=\"#{f[:file_name]}\""
|
|
326
|
+
multipart << "Content-type: #{f[:content_type]}"
|
|
327
|
+
multipart << "Content-Transfer-Encoding: #{f[:transfer_encoding]}"
|
|
328
|
+
multipart << "\r\n#{f[:file_content]}"
|
|
329
|
+
}
|
|
330
|
+
multipart << "--#{boundary}--"
|
|
331
|
+
multipart = multipart.join("\r\n")
|
|
332
|
+
@headers['Content-length'] = "#{multipart.size}"
|
|
333
|
+
@options[:parameters] = multipart
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
# send request and get the response by some options
|
|
337
|
+
def send_request_and_get_response
|
|
338
|
+
# for proxy
|
|
339
|
+
http = if @options[:proxy_addr]
|
|
340
|
+
if @options[:proxy_user] and @options[:proxy_pass]
|
|
341
|
+
Net::HTTP::Proxy(
|
|
342
|
+
@options[:proxy_addr],
|
|
343
|
+
@options[:proxy_port],
|
|
344
|
+
@options[:proxy_user],
|
|
345
|
+
@options[:proxy_pass]
|
|
346
|
+
).new(@u.host, @u.port)
|
|
347
|
+
else
|
|
348
|
+
Net::HTTP::Proxy(
|
|
349
|
+
@options[:proxy_addr],
|
|
350
|
+
@options[:proxy_port]
|
|
351
|
+
).new(@uri.host, @uri.port)
|
|
352
|
+
end
|
|
353
|
+
else
|
|
354
|
+
Net::HTTP.new(@uri.host, @uri.port)
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
# ssl support
|
|
358
|
+
http.use_ssl = true if @uri.scheme =~ /^https$/i
|
|
359
|
+
|
|
360
|
+
# sending request and get response
|
|
361
|
+
send_request http
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
# send http request
|
|
365
|
+
def send_request(http)
|
|
366
|
+
# xml data?
|
|
367
|
+
if @options[:parameters].to_s[0..4].eql?('<?xml') and @options[:method].eql? 'post'
|
|
368
|
+
@headers['Content-Type'] = 'application/xml'
|
|
369
|
+
@headers['Content-Length'] = @options[:parameters].size.to_s
|
|
370
|
+
@headers['Content-MD5'] = md5(@options[:parameters]).to_s
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
# GO !!
|
|
374
|
+
if @options[:method] =~ /^(get|head|options|delete|move|copy|trace|)$/
|
|
375
|
+
@options[:parameters] = "?#{@options[:parameters]}" if @options[:parameters]
|
|
376
|
+
path = if @options[:parameters] =~ /^\?+$/
|
|
377
|
+
@uri.path
|
|
378
|
+
else
|
|
379
|
+
@uri.path + @options[:parameters]
|
|
380
|
+
end
|
|
381
|
+
h = http.method(@options[:method]).call(path, @headers)
|
|
382
|
+
else
|
|
383
|
+
h = http.method(@options[:method]).call(@uri.path, @options[:parameters], @headers)
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
self.class.update_cookies h
|
|
387
|
+
h
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
# process the redirectation if need
|
|
391
|
+
def process_redirection(response, &block)
|
|
392
|
+
case response
|
|
393
|
+
when Net::HTTPRedirection
|
|
394
|
+
url = "#{@uri.scheme}://#{@uri.host}#{':' + @uri.port.to_s if @uri.port != 80}"
|
|
395
|
+
last_url = @options[:url]
|
|
396
|
+
@options[:url] = case response['location']
|
|
397
|
+
when /^https?:\/\//i
|
|
398
|
+
response['location']
|
|
399
|
+
when /^\//
|
|
400
|
+
url + response['location']
|
|
401
|
+
when /^(\.\.\/|\.\/)/
|
|
402
|
+
paths = (File.dirname(@uri.path) + '/' + response['location']).split('/')
|
|
403
|
+
location = []
|
|
404
|
+
paths.each {|path|
|
|
405
|
+
next if path.empty? || path.eql?('.')
|
|
406
|
+
path == '..' ? location.pop : location.push(path)
|
|
407
|
+
}
|
|
408
|
+
url + '/' + location.join('/')
|
|
409
|
+
else
|
|
410
|
+
url + File.dirname(@uri.path) + '/' + response['location']
|
|
411
|
+
end
|
|
412
|
+
return data(response, &block) if @@redirect_times > 2 and @options[:url].eql? last_url
|
|
413
|
+
@@redirect_times += 1
|
|
414
|
+
raise 'too many redirects...' if @@redirect_times > @options[:redirect_limits]
|
|
415
|
+
if @options[:cookies].nil?
|
|
416
|
+
@options[:cookies] = self.class.cookies
|
|
417
|
+
else
|
|
418
|
+
@options[:cookies] = @options[:cookies].update self.class.cookies
|
|
419
|
+
end
|
|
420
|
+
@options.delete :parameters
|
|
421
|
+
@options.delete :method
|
|
422
|
+
request('get', @options, &block)
|
|
423
|
+
else
|
|
424
|
+
data(response, &block)
|
|
425
|
+
end
|
|
426
|
+
end
|
|
427
427
|
|
|
428
428
|
end
|
|
429
429
|
|
|
430
430
|
module Net
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
431
|
+
class HTTPResponse
|
|
432
|
+
|
|
433
|
+
attr_accessor :url
|
|
434
|
+
|
|
435
|
+
# get cookies as a hash
|
|
436
|
+
def cookies
|
|
437
|
+
HttpRequest.cookies
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
# for gzipped body
|
|
441
|
+
def body
|
|
442
|
+
bd = read_body()
|
|
443
|
+
return bd unless bd
|
|
444
|
+
if (self['content-encoding'] == 'gzip') and defined?(::Zlib)
|
|
445
|
+
::Zlib::GzipReader.new(StringIO.new(bd)).read
|
|
446
|
+
else
|
|
447
|
+
bd
|
|
448
|
+
end
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
# body
|
|
452
|
+
def raw_body
|
|
453
|
+
read_body()
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
# detect the response code
|
|
457
|
+
#
|
|
458
|
+
# Example:
|
|
459
|
+
# puts HttpRequest.get('http://www.example.com').code_200?
|
|
460
|
+
# puts HttpRequest.get('http://www.example.com').code_2xx?
|
|
461
|
+
# HttpRequest.get('http://www.example.com/404.html') {|http|
|
|
462
|
+
# puts "IS 4xx" if http.code_4xx?
|
|
463
|
+
# puts "IS 404" if http.code_404?
|
|
464
|
+
# }
|
|
465
|
+
#
|
|
466
|
+
# supported methods
|
|
467
|
+
# code_1xx? code_2xx? code_3xx? code_4xx? code_5xx?
|
|
468
|
+
# code_100? code_101? code_200? code_201? ... code_505?
|
|
469
|
+
def method_missing(method_name)
|
|
470
|
+
case method_name.to_s
|
|
471
|
+
when /^(code|status)_([0-9])xx\?$/
|
|
472
|
+
not CODE_CLASS_TO_OBJ[$2].nil? and is_a? CODE_CLASS_TO_OBJ[$2]
|
|
473
|
+
when /^(code|status)_([0-9]+)\?$/
|
|
474
|
+
not CODE_TO_OBJ[$2].nil? and is_a? CODE_TO_OBJ[$2]
|
|
475
|
+
else
|
|
476
|
+
raise NoHttpMethodException, 'Unknown method of response code!'
|
|
477
|
+
end
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
end
|
|
481
481
|
end
|
|
482
482
|
|
|
483
483
|
# for ftp response
|
|
484
484
|
class Net::FTP
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
485
|
+
def response=(response)
|
|
486
|
+
@_response = response
|
|
487
|
+
end
|
|
488
488
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
489
|
+
def response
|
|
490
|
+
@_response
|
|
491
|
+
end
|
|
492
492
|
end
|
|
493
493
|
|
|
494
494
|
# from Rack, parsing parameters for the Digest auth
|
|
495
495
|
class HttpRequestParams < Hash
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
496
|
+
def self.parse(str)
|
|
497
|
+
split_header_value(str).inject(new) do |header, param|
|
|
498
|
+
k, v = param.split('=', 2)
|
|
499
|
+
header[k] = dequote(v)
|
|
500
|
+
header
|
|
501
|
+
end
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
def self.dequote(str) # From WEBrick::HTTPUtils
|
|
505
|
+
ret = (/\A"(.*)"\Z/ =~ str) ? $1 : str.dup
|
|
506
|
+
ret.gsub!(/\\(.)/, "\\1")
|
|
507
|
+
ret
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
def self.split_header_value(str)
|
|
511
|
+
str.scan( /(\w+\=(?:"[^\"]+"|[^,]+))/n ).collect{ |v| v[0] }
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
def initialize
|
|
515
|
+
super
|
|
516
|
+
|
|
517
|
+
yield self if block_given?
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
def [](k)
|
|
521
|
+
super k.to_s
|
|
522
|
+
end
|
|
523
|
+
|
|
524
|
+
def []=(k, v)
|
|
525
|
+
super k.to_s, v.to_s
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
UNQUOTED = ['qop', 'nc', 'stale']
|
|
529
|
+
|
|
530
|
+
def to_s
|
|
531
|
+
inject([]) do |parts, (k, v)|
|
|
532
|
+
parts << "#{k}=" + (UNQUOTED.include?(k) ? v.to_s : quote(v))
|
|
533
|
+
parts
|
|
534
|
+
end.join(', ')
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
def quote(str) # From WEBrick::HTTPUtils
|
|
538
|
+
'"' << str.gsub(/[\\\"]/o, "\\\1") << '"'
|
|
539
|
+
end
|
|
540
540
|
end
|
|
541
541
|
|
|
542
542
|
|
|
@@ -545,31 +545,31 @@ class NoHttpMethodException < Exception; end
|
|
|
545
545
|
|
|
546
546
|
# for command line
|
|
547
547
|
if __FILE__.eql? $0
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
548
|
+
method, url, params = ARGV
|
|
549
|
+
exit unless method
|
|
550
|
+
source_method = method
|
|
551
|
+
method = method.split('_')[0] if method.include? '_'
|
|
552
|
+
|
|
553
|
+
# fix path of the url
|
|
554
|
+
url = "http://#{url}" unless url =~ /^(https?:\/\/)/i
|
|
555
|
+
|
|
556
|
+
params = if params
|
|
557
|
+
"{:url => '#{url}', :parameters => '" + params + "'}"
|
|
558
|
+
else
|
|
559
|
+
"'#{url}'"
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
if HttpRequest.http_methods.include?(method) && url
|
|
563
|
+
http = eval("HttpRequest.#{method}(#{params})")
|
|
564
|
+
case source_method
|
|
565
|
+
when /_only_header$/
|
|
566
|
+
http.each{|k,v| puts "#{k}: #{v}"}
|
|
567
|
+
when /_with_header$/
|
|
568
|
+
http.each{|k,v| puts "#{k}: #{v}"}
|
|
569
|
+
print http.body unless http.body.to_s.empty?
|
|
570
|
+
else
|
|
571
|
+
print http.body unless http.body.to_s.empty?
|
|
572
|
+
end
|
|
573
|
+
end
|
|
574
574
|
|
|
575
575
|
end
|