http_request.rb 1.0.7 → 1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Changelog +6 -0
- data/README.rdoc +4 -2
- data/lib/http_request.rb +279 -117
- data/test/test_http_request.rb +266 -0
- data/test/web_server.rb +157 -0
- metadata +8 -4
data/Changelog
CHANGED
data/README.rdoc
CHANGED
@@ -220,13 +220,15 @@ download multiple files from a directory
|
|
220
220
|
</items>'
|
221
221
|
HttpRequest.post(:url => 'http://localhost/xml.php', :parameters = xml)
|
222
222
|
|
223
|
+
== More examples please check the test directory
|
224
|
+
|
223
225
|
== TODO
|
224
226
|
|
225
227
|
bug fixing, testing and testing...
|
226
228
|
|
227
229
|
== LATEST VERSION
|
228
|
-
1.
|
230
|
+
1.1
|
229
231
|
|
230
232
|
== Author
|
231
233
|
|
232
|
-
xianhua.zhou<xianhua.zhou at gmail.com
|
234
|
+
xianhua.zhou<xianhua.zhou at gmail.com>
|
data/lib/http_request.rb
CHANGED
@@ -11,14 +11,13 @@
|
|
11
11
|
#
|
12
12
|
# == Version
|
13
13
|
#
|
14
|
-
# v1.
|
14
|
+
# v1.1
|
15
15
|
#
|
16
|
-
# Last Change:
|
16
|
+
# Last Change: 04 Oct, 2009
|
17
17
|
#
|
18
18
|
# == Author
|
19
19
|
#
|
20
20
|
# xianhua.zhou<xianhua.zhou@gmail.com>
|
21
|
-
# homepage: http://my.cnzxh.net
|
22
21
|
#
|
23
22
|
#
|
24
23
|
require 'cgi'
|
@@ -26,110 +25,85 @@ require 'net/http'
|
|
26
25
|
require 'net/https'
|
27
26
|
require 'net/ftp'
|
28
27
|
require 'singleton'
|
29
|
-
require 'md5'
|
28
|
+
require 'digest/md5'
|
30
29
|
require 'stringio'
|
31
30
|
|
32
31
|
class HttpRequest
|
33
32
|
include Singleton
|
33
|
+
class << self
|
34
|
+
# version
|
35
|
+
VERSION = '1.1'.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
|
34
42
|
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
38
48
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
49
|
+
# update cookies
|
50
|
+
def update_cookies(response)
|
51
|
+
return unless response.header['set-cookie']
|
52
|
+
response.header['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
|
43
62
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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
|
48
74
|
end
|
49
75
|
|
50
|
-
|
51
|
-
|
52
|
-
timeout(timeout) {
|
53
|
-
u = URI(url)
|
54
|
-
s = TCPSocket.new(u.host, u.port)
|
55
|
-
s.close
|
56
|
-
}
|
57
|
-
return true
|
58
|
-
rescue Exception => e
|
59
|
-
return false
|
76
|
+
def data(response, &block)
|
77
|
+
self.class.data(response, &block)
|
60
78
|
end
|
61
79
|
|
62
80
|
# send request by some given parameters
|
63
81
|
def request(method, opt, &block)
|
64
|
-
init_args(method, opt)
|
65
|
-
@options[:method] = method
|
66
|
-
|
67
|
-
# for upload files
|
68
|
-
if @options[:files].is_a?(Array) && 'post'.eql?(method)
|
69
|
-
build_multipart
|
70
|
-
else
|
71
|
-
if @options[:parameters].is_a? Hash
|
72
|
-
@options[:parameters] = @options[:parameters].collect{|k, v|
|
73
|
-
"#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}"
|
74
|
-
}.join('&')
|
75
|
-
end
|
76
|
-
end
|
77
82
|
|
78
|
-
#
|
79
|
-
|
80
|
-
if @options[:proxy_user] && @options[:proxy_pass]
|
81
|
-
Net::HTTP::Proxy(@options[:proxy_addr], @options[:proxy_port], @options[:proxy_user], @options[:proxy_pass]).new(@u.host, @u.port)
|
82
|
-
else
|
83
|
-
Net::HTTP::Proxy(@options[:proxy_addr], @options[:proxy_port]).new(@uri.host, @uri.port)
|
84
|
-
end
|
85
|
-
else
|
86
|
-
Net::HTTP.new(@uri.host, @uri.port)
|
87
|
-
end
|
83
|
+
# parse the @options
|
84
|
+
parse_options(method, opt)
|
88
85
|
|
89
|
-
#
|
90
|
-
|
86
|
+
# parse and merge for the options[:parameters]
|
87
|
+
parse_parameters
|
91
88
|
|
92
|
-
#
|
93
|
-
response =
|
89
|
+
# send http request and get the response
|
90
|
+
response = send_request_and_get_response
|
94
91
|
|
95
|
-
return
|
92
|
+
return data(response, &block) unless @options[:redirect]
|
96
93
|
|
97
94
|
# redirect?
|
98
|
-
|
99
|
-
when Net::HTTPRedirection
|
100
|
-
url = "#{@uri.scheme}://#{@uri.host}#{':' + @uri.port.to_s if @uri.port != 80}"
|
101
|
-
@options[:url] = case response['location']
|
102
|
-
when /^https?:\/\//i
|
103
|
-
response['location']
|
104
|
-
when /^\//
|
105
|
-
url + response['location']
|
106
|
-
when /^(\.\.\/|\.\/)/
|
107
|
-
paths = (File.dirname(@uri.path) + '/' + response['location']).split('/')
|
108
|
-
location = []
|
109
|
-
paths.each {|path|
|
110
|
-
next if path.empty? || path.eql?('.')
|
111
|
-
path == '..' ? location.pop : location.push(path)
|
112
|
-
}
|
113
|
-
url + '/' + location.join('/')
|
114
|
-
else
|
115
|
-
url + File.dirname(@uri.path) + '/' + response['location']
|
116
|
-
end
|
117
|
-
@redirect_times = @redirect_times.succ
|
118
|
-
raise 'too many redirects...' if @redirect_times > @options[:redirect_limits]
|
119
|
-
request('get', @options, &block)
|
120
|
-
else
|
121
|
-
return HttpRequest.data(response, &block)
|
122
|
-
end
|
95
|
+
process_redirection response, &block
|
123
96
|
end
|
124
97
|
|
125
|
-
# catch all
|
98
|
+
# catch all available http requests
|
126
99
|
def self.method_missing(method_name, args, &block)
|
100
|
+
@@__cookies = {}
|
127
101
|
method_name = method_name.to_s.downcase
|
128
102
|
raise NoHttpMethodException, "No such http method can be called: #{method_name}" unless self.http_methods.include?(method_name)
|
129
103
|
self.instance.request(method_name, args, &block)
|
130
104
|
end
|
131
105
|
|
132
|
-
# for ftp
|
106
|
+
# for ftp, no plan to add new features to this method except bug fixing
|
133
107
|
def self.ftp(method, options, &block)
|
134
108
|
options = {:url => options} if options.is_a? String
|
135
109
|
options = {:close => true}.merge(options)
|
@@ -137,12 +111,13 @@ class HttpRequest
|
|
137
111
|
uri = URI(options[:url])
|
138
112
|
guest_name, guest_pass = 'anonymous', "guest@#{uri.host}"
|
139
113
|
unless options[:username]
|
140
|
-
options[:username], options[:password] =
|
114
|
+
options[:username], options[:password] =
|
115
|
+
uri.userinfo ? uri.userinfo.split(':') : [guest_name, guest_pass]
|
141
116
|
end
|
142
117
|
options[:username] = guest_name unless options[:username]
|
143
118
|
options[:password] = guest_pass if options[:password].nil?
|
144
119
|
ftp = Net::FTP.open(uri.host, options[:username], options[:password])
|
145
|
-
return
|
120
|
+
return data(ftp, &block) unless method
|
146
121
|
stat = case method.to_sym
|
147
122
|
when :get_as_string
|
148
123
|
require 'tempfile'
|
@@ -168,19 +143,91 @@ class HttpRequest
|
|
168
143
|
else
|
169
144
|
return ftp
|
170
145
|
end
|
171
|
-
if options[:close]
|
146
|
+
if options[:close] and not block_given?
|
172
147
|
ftp.close
|
173
148
|
stat
|
174
149
|
else
|
175
150
|
ftp.response = stat unless ftp.response
|
176
|
-
|
151
|
+
data(ftp, &block)
|
177
152
|
end
|
178
153
|
end
|
179
154
|
|
180
155
|
private
|
181
156
|
|
157
|
+
def md5(string)
|
158
|
+
Digest::MD5.hexdigest string
|
159
|
+
end
|
160
|
+
|
161
|
+
# get params for the Digest auth
|
162
|
+
# see: http://www.rooftopsolutions.nl/article/223
|
163
|
+
def get_params_for_digest
|
164
|
+
return '' unless @options[:auth_username] and @options[:auth_password]
|
165
|
+
user, passwd = @options[:auth_username], @options[:auth_password]
|
166
|
+
hr = self.class.send @options[:method],
|
167
|
+
@uri.userinfo ?
|
168
|
+
@@__url.sub(/:\/\/(.+?)@/, '://') :
|
169
|
+
@@__url
|
170
|
+
|
171
|
+
params = HttpRequestParams.parse hr['WWW-Authenticate'].split(' ', 2).last
|
172
|
+
method = @options[:method].upcase
|
173
|
+
cnonce = md5(rand.to_s + Time.new.to_s)
|
174
|
+
nc = "00000001"
|
175
|
+
|
176
|
+
data = []
|
177
|
+
data << md5("#{user}:#{params['realm']}:#{passwd}") \
|
178
|
+
<< params['nonce'] \
|
179
|
+
<< ('%08x' % nc) \
|
180
|
+
<< cnonce \
|
181
|
+
<< params['qop'] \
|
182
|
+
<< md5("#{method}:#{@uri.path}")
|
183
|
+
|
184
|
+
params = params.update({
|
185
|
+
:username => user,
|
186
|
+
:nc => nc,
|
187
|
+
:cnonce => cnonce,
|
188
|
+
:uri => @uri.path,
|
189
|
+
:method => method,
|
190
|
+
:response => md5(data.join(":"))
|
191
|
+
})
|
192
|
+
|
193
|
+
headers = []
|
194
|
+
params.each {|k, v| headers << "#{k}=\"#{v}\"" }
|
195
|
+
headers.join(", ")
|
196
|
+
rescue
|
197
|
+
''
|
198
|
+
end
|
199
|
+
|
200
|
+
# for the Http Auth if need
|
201
|
+
def parse_http_auth
|
202
|
+
if @options[:auth] or
|
203
|
+
@uri.userinfo or
|
204
|
+
(@options[:auth_username] and @options[:auth_password])
|
205
|
+
|
206
|
+
if @options[:auth].is_a? Hash
|
207
|
+
@options[:auth_username] = @options[:auth][:username]
|
208
|
+
@options[:auth_password] = @options[:auth][:password]
|
209
|
+
@options[:auth] = @options[:auth][:type]
|
210
|
+
elsif @uri.userinfo and (!@options[:auth_username] or !@options[:auth_password])
|
211
|
+
@options[:auth_username], @options[:auth_password] = @uri.userinfo.split(/:/, 2)
|
212
|
+
end
|
213
|
+
|
214
|
+
if @options[:auth_username] and @options[:auth_password]
|
215
|
+
# Digest Auth
|
216
|
+
if @options[:auth].to_s == 'digest'
|
217
|
+
digest = get_params_for_digest
|
218
|
+
@headers['Authorization'] = "Digest " + digest unless digest.empty?
|
219
|
+
else
|
220
|
+
# Basic Auth
|
221
|
+
@headers['Authorization'] = "Basic " +
|
222
|
+
["#{@options[:auth_username]}:#{@options[:auth_password]}"].pack('m').delete!("\r\n")
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
182
229
|
# initialize for the http request
|
183
|
-
def
|
230
|
+
def parse_options(method, options)
|
184
231
|
options = {:url => options.to_s} if [String, Array].include? options.class
|
185
232
|
@options = {
|
186
233
|
:ssl_port => 443,
|
@@ -188,7 +235,8 @@ class HttpRequest
|
|
188
235
|
:redirect => true,
|
189
236
|
:url => nil,
|
190
237
|
:ajax => false,
|
191
|
-
:xhr => false
|
238
|
+
:xhr => false,
|
239
|
+
:method => method
|
192
240
|
}
|
193
241
|
@options.merge!(options)
|
194
242
|
@@__url = @options[:url]
|
@@ -197,7 +245,7 @@ class HttpRequest
|
|
197
245
|
@headers = {
|
198
246
|
'Host' => @uri.host,
|
199
247
|
'Referer' => @options[:url],
|
200
|
-
'User-Agent' => 'HttpRequest.rb ' +
|
248
|
+
'User-Agent' => 'HttpRequest.rb ' + self.class.version
|
201
249
|
}
|
202
250
|
|
203
251
|
# support gzip
|
@@ -207,8 +255,8 @@ class HttpRequest
|
|
207
255
|
# ajax calls?
|
208
256
|
@headers['X_REQUESTED_WITH'] = 'XMLHttpRequest' if @options[:ajax] or @options[:xhr]
|
209
257
|
|
210
|
-
#
|
211
|
-
|
258
|
+
# Http Authenication
|
259
|
+
parse_http_auth
|
212
260
|
|
213
261
|
# headers
|
214
262
|
@options[:headers].each {|k, v| @headers[k] = v} if @options[:headers].is_a? Hash
|
@@ -230,9 +278,25 @@ class HttpRequest
|
|
230
278
|
@redirect_times = 0 if @options[:redirect]
|
231
279
|
end
|
232
280
|
|
233
|
-
# for
|
281
|
+
# parse parameters for the options[:parameters] and @uri.query
|
282
|
+
def parse_parameters
|
283
|
+
if @options[:parameters].is_a? Hash
|
284
|
+
@options[:parameters] = @options[:parameters].collect{|k, v|
|
285
|
+
"#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}"
|
286
|
+
}.join('&')
|
287
|
+
end
|
288
|
+
@options[:parameters] = '' if @options[:parameters].nil?
|
289
|
+
if not @uri.query.to_s.empty?
|
290
|
+
@options[:parameters] << (@options[:parameters].empty? ? @uri.query : "&#{@uri.query}")
|
291
|
+
end
|
292
|
+
|
293
|
+
# for uploading files
|
294
|
+
build_multipart if @options[:files].is_a?(Array) and 'post'.eql?(@options[:method])
|
295
|
+
end
|
296
|
+
|
297
|
+
# for uploading files
|
234
298
|
def build_multipart
|
235
|
-
boundary =
|
299
|
+
boundary = md5(rand.to_s).to_s[0..5]
|
236
300
|
@headers['Content-type'] = "multipart/form-data, boundary=#{boundary}"
|
237
301
|
multipart = []
|
238
302
|
if @options[:parameters]
|
@@ -262,35 +326,90 @@ class HttpRequest
|
|
262
326
|
@options[:parameters] = multipart
|
263
327
|
end
|
264
328
|
|
329
|
+
# send request and get the response by some options
|
330
|
+
def send_request_and_get_response
|
331
|
+
# for proxy
|
332
|
+
http = if @options[:proxy_addr]
|
333
|
+
if @options[:proxy_user] and @options[:proxy_pass]
|
334
|
+
Net::HTTP::Proxy(
|
335
|
+
@options[:proxy_addr],
|
336
|
+
@options[:proxy_port],
|
337
|
+
@options[:proxy_user],
|
338
|
+
@options[:proxy_pass]
|
339
|
+
).new(@u.host, @u.port)
|
340
|
+
else
|
341
|
+
Net::HTTP::Proxy(
|
342
|
+
@options[:proxy_addr],
|
343
|
+
@options[:proxy_port]
|
344
|
+
).new(@uri.host, @uri.port)
|
345
|
+
end
|
346
|
+
else
|
347
|
+
Net::HTTP.new(@uri.host, @uri.port)
|
348
|
+
end
|
349
|
+
|
350
|
+
# ssl support
|
351
|
+
http.use_ssl = true if @uri.scheme =~ /^https$/i
|
352
|
+
|
353
|
+
# sending request and get response
|
354
|
+
send_request http
|
355
|
+
end
|
356
|
+
|
265
357
|
# send http request
|
266
358
|
def send_request(http)
|
267
|
-
|
268
359
|
# xml data?
|
269
360
|
if @options[:parameters].to_s[0..4].eql?('<?xml') and @options[:method].eql? 'post'
|
270
361
|
@headers['Content-Type'] = 'application/xml'
|
271
362
|
@headers['Content-Length'] = @options[:parameters].size.to_s
|
272
|
-
@headers['Content-MD5'] =
|
273
|
-
else
|
274
|
-
# merge parameters
|
275
|
-
parameters = @options[:parameters].to_s
|
276
|
-
@options[:parameters] = "#{@uri.query}" if @uri.query
|
277
|
-
if parameters
|
278
|
-
if @options[:parameters]
|
279
|
-
@options[:parameters] << "&#{parameters}"
|
280
|
-
else
|
281
|
-
@options[:parameters] = "#{parameters}"
|
282
|
-
end
|
283
|
-
end
|
363
|
+
@headers['Content-MD5'] = md5(@options[:parameters]).to_s
|
284
364
|
end
|
285
365
|
|
286
366
|
# GO !!
|
287
367
|
if @options[:method] =~ /^(get|head|options|delete|move|copy|trace|)$/
|
288
368
|
@options[:parameters] = "?#{@options[:parameters]}" if @options[:parameters]
|
289
|
-
http.method(@options[:method]).call(
|
369
|
+
h = http.method(@options[:method]).call(
|
370
|
+
"#{@uri.path}#{@options[:parameters] unless @options[:parameters].eql?('?')}",
|
371
|
+
@headers
|
372
|
+
)
|
290
373
|
else
|
291
|
-
http.method(@options[:method]).call(@uri.path, @options[:parameters], @headers)
|
374
|
+
h = http.method(@options[:method]).call(@uri.path, @options[:parameters], @headers)
|
292
375
|
end
|
293
376
|
|
377
|
+
self.class.update_cookies h
|
378
|
+
h
|
379
|
+
end
|
380
|
+
|
381
|
+
# process the redirectation if need
|
382
|
+
def process_redirection(response, &block)
|
383
|
+
case response
|
384
|
+
when Net::HTTPRedirection
|
385
|
+
url = "#{@uri.scheme}://#{@uri.host}#{':' + @uri.port.to_s if @uri.port != 80}"
|
386
|
+
@options[:url] = case response['location']
|
387
|
+
when /^https?:\/\//i
|
388
|
+
response['location']
|
389
|
+
when /^\//
|
390
|
+
url + response['location']
|
391
|
+
when /^(\.\.\/|\.\/)/
|
392
|
+
paths = (File.dirname(@uri.path) + '/' + response['location']).split('/')
|
393
|
+
location = []
|
394
|
+
paths.each {|path|
|
395
|
+
next if path.empty? || path.eql?('.')
|
396
|
+
path == '..' ? location.pop : location.push(path)
|
397
|
+
}
|
398
|
+
url + '/' + location.join('/')
|
399
|
+
else
|
400
|
+
url + File.dirname(@uri.path) + '/' + response['location']
|
401
|
+
end
|
402
|
+
@redirect_times = @redirect_times.succ
|
403
|
+
raise 'too many redirects...' if @redirect_times > @options[:redirect_limits]
|
404
|
+
if @options[:cookies].nil?
|
405
|
+
@options[:cookies] = self.class.cookies
|
406
|
+
else
|
407
|
+
@options[:cookies] = @options[:cookies].update self.class.cookies
|
408
|
+
end
|
409
|
+
request('get', @options, &block)
|
410
|
+
else
|
411
|
+
data(response, &block)
|
412
|
+
end
|
294
413
|
end
|
295
414
|
|
296
415
|
end
|
@@ -302,13 +421,7 @@ module Net
|
|
302
421
|
|
303
422
|
# get cookies as a hash
|
304
423
|
def cookies
|
305
|
-
cookies
|
306
|
-
return cookies unless @header['set-cookie']
|
307
|
-
@header['set-cookie'].each {|k|
|
308
|
-
k, v = k.split(';')[0].split('=')
|
309
|
-
cookies[k] = v
|
310
|
-
}
|
311
|
-
cookies
|
424
|
+
HttpRequest.cookies
|
312
425
|
end
|
313
426
|
|
314
427
|
# for gzipped body
|
@@ -342,10 +455,10 @@ module Net
|
|
342
455
|
# code_100? code_101? code_200? code_201? ... code_505?
|
343
456
|
def method_missing(method_name)
|
344
457
|
case method_name.to_s
|
345
|
-
when /^
|
346
|
-
is_a? CODE_CLASS_TO_OBJ[$
|
347
|
-
when /^
|
348
|
-
is_a? CODE_TO_OBJ[$
|
458
|
+
when /^(code|status)_([0-9])xx\?$/
|
459
|
+
not CODE_CLASS_TO_OBJ[$2].nil? and is_a? CODE_CLASS_TO_OBJ[$2]
|
460
|
+
when /^(code|status)_([0-9]+)\?$/
|
461
|
+
not CODE_TO_OBJ[$2].nil? and is_a? CODE_TO_OBJ[$2]
|
349
462
|
else
|
350
463
|
raise NoHttpMethodException, 'Unknown method of response code!'
|
351
464
|
end
|
@@ -365,6 +478,55 @@ class Net::FTP
|
|
365
478
|
end
|
366
479
|
end
|
367
480
|
|
481
|
+
# from Rack, parsing parameters for the Digest auth
|
482
|
+
class HttpRequestParams < Hash
|
483
|
+
def self.parse(str)
|
484
|
+
split_header_value(str).inject(new) do |header, param|
|
485
|
+
k, v = param.split('=', 2)
|
486
|
+
header[k] = dequote(v)
|
487
|
+
header
|
488
|
+
end
|
489
|
+
end
|
490
|
+
|
491
|
+
def self.dequote(str) # From WEBrick::HTTPUtils
|
492
|
+
ret = (/\A"(.*)"\Z/ =~ str) ? $1 : str.dup
|
493
|
+
ret.gsub!(/\\(.)/, "\\1")
|
494
|
+
ret
|
495
|
+
end
|
496
|
+
|
497
|
+
def self.split_header_value(str)
|
498
|
+
str.scan( /(\w+\=(?:"[^\"]+"|[^,]+))/n ).collect{ |v| v[0] }
|
499
|
+
end
|
500
|
+
|
501
|
+
def initialize
|
502
|
+
super
|
503
|
+
|
504
|
+
yield self if block_given?
|
505
|
+
end
|
506
|
+
|
507
|
+
def [](k)
|
508
|
+
super k.to_s
|
509
|
+
end
|
510
|
+
|
511
|
+
def []=(k, v)
|
512
|
+
super k.to_s, v.to_s
|
513
|
+
end
|
514
|
+
|
515
|
+
UNQUOTED = ['qop', 'nc', 'stale']
|
516
|
+
|
517
|
+
def to_s
|
518
|
+
inject([]) do |parts, (k, v)|
|
519
|
+
parts << "#{k}=" + (UNQUOTED.include?(k) ? v.to_s : quote(v))
|
520
|
+
parts
|
521
|
+
end.join(', ')
|
522
|
+
end
|
523
|
+
|
524
|
+
def quote(str) # From WEBrick::HTTPUtils
|
525
|
+
'"' << str.gsub(/[\\\"]/o, "\\\1") << '"'
|
526
|
+
end
|
527
|
+
end
|
528
|
+
|
529
|
+
|
368
530
|
# exception
|
369
531
|
class NoHttpMethodException < Exception; end
|
370
532
|
|
@@ -0,0 +1,266 @@
|
|
1
|
+
require 'test/spec'
|
2
|
+
require File.join(File.dirname(__FILE__), '..', 'lib/http_request.rb')
|
3
|
+
|
4
|
+
URL = 'http://localhost:9527'
|
5
|
+
hr = HttpRequest
|
6
|
+
|
7
|
+
context "some basic http requests" do
|
8
|
+
|
9
|
+
specify "get the first page" do
|
10
|
+
hr.get(URL) do |http|
|
11
|
+
http.body.should.equal 'It Works!'
|
12
|
+
http['content-type'].should.equal 'text/html'
|
13
|
+
end
|
14
|
+
hr.get(URL + '/').body.should.equal 'It Works!'
|
15
|
+
end
|
16
|
+
|
17
|
+
specify "post or get method" do
|
18
|
+
hr.get(URL + "/method/post").body.should.equal 'No'
|
19
|
+
hr.post(URL + "/method/post").body.should.equal 'Yes'
|
20
|
+
|
21
|
+
hr.get(URL + "/method/get").body.should.equal 'Yes'
|
22
|
+
hr.post(URL + "/method/get").body.should.equal 'No'
|
23
|
+
end
|
24
|
+
|
25
|
+
specify "xhr?" do
|
26
|
+
hr.get(:url => URL + "/ajax").body.should.equal 'N'
|
27
|
+
hr.get(:url => URL + "/ajax", :xhr => true).body.should.equal 'Y'
|
28
|
+
hr.get(:url => URL + "/ajax", :ajax => true).body.should.equal 'Y'
|
29
|
+
end
|
30
|
+
|
31
|
+
specify "available http methods" do
|
32
|
+
url = URL + '/get-method-name'
|
33
|
+
hr.get(url).body.should.equal 'GET'
|
34
|
+
hr.post(url).body.should.equal 'POST'
|
35
|
+
hr.put(url).body.should.equal 'PUT'
|
36
|
+
hr.delete(url).body.should.equal 'DELETE'
|
37
|
+
hr.trace(url).body.should.equal 'TRACE'
|
38
|
+
hr.lock(url).body.should.equal 'LOCK'
|
39
|
+
hr.unlock(url).body.should.equal 'UNLOCK'
|
40
|
+
hr.move(url).body.should.equal 'MOVE'
|
41
|
+
hr.copy(url).body.should.equal 'COPY'
|
42
|
+
hr.propfind(url).body.should.equal 'PROPFIND'
|
43
|
+
hr.proppatch(url).body.should.equal 'PROPPATCH'
|
44
|
+
hr.mkcol(url).body.should.equal 'MKCOL'
|
45
|
+
|
46
|
+
hr.options(url).body.should.equal nil
|
47
|
+
hr.options(url)['content-length'].should.equal 'options'.size.to_s
|
48
|
+
|
49
|
+
hr.head(url).body.should.equal nil
|
50
|
+
hr.head(url)['content-length'].should.equal 'head'.size.to_s
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
context "some basic requests with parameter" do
|
56
|
+
|
57
|
+
specify "get method" do
|
58
|
+
hr.get(URL + '/get').body.should.equal({}.inspect)
|
59
|
+
hr.get(URL + '/get?&').body.should.equal({}.inspect)
|
60
|
+
hr.get(URL + '/get?&#').body.should.equal({}.inspect)
|
61
|
+
hr.get(URL + '/get?abc=').body.should.equal({'abc' => ''}.inspect)
|
62
|
+
|
63
|
+
hr.get(URL + '/get?lang=Ruby&version=1.9').body.should.equal({
|
64
|
+
'lang' => 'Ruby', 'version' => '1.9'
|
65
|
+
}.inspect)
|
66
|
+
|
67
|
+
hr.get(:url => URL + '/get', :parameters => 'lang=Ruby&version=1.9').body.should.equal({
|
68
|
+
'lang' => 'Ruby', 'version' => '1.9'
|
69
|
+
}.inspect)
|
70
|
+
|
71
|
+
hr.get(:url => URL + '/get', :parameters => {:lang => 'Ruby', :version => 1.9}).body.should.equal({
|
72
|
+
'lang' => 'Ruby', 'version' => '1.9'
|
73
|
+
}.inspect)
|
74
|
+
|
75
|
+
hr.get(:url => URL + '/get?lang=Ruby', :parameters => {:version => 1.9}).body.should.equal({
|
76
|
+
'lang' => 'Ruby', 'version' => '1.9'
|
77
|
+
}.inspect)
|
78
|
+
|
79
|
+
hr.get(:url => URL + '/get', :parameters => {'lang' => 'Ruby', 'version' => '1.9'}).body.should.equal({
|
80
|
+
'lang' => 'Ruby', 'version' => '1.9'
|
81
|
+
}.inspect)
|
82
|
+
|
83
|
+
hr.get(URL + '/get?ids[]=1&ids[]=2').body.should.equal({
|
84
|
+
'ids' => ['1', '2']
|
85
|
+
}.inspect)
|
86
|
+
|
87
|
+
hr.get(:url => URL + '/get', :parameters => 'ids[]=1&ids[]=2').body.should.equal({
|
88
|
+
'ids' => ['1', '2']
|
89
|
+
}.inspect)
|
90
|
+
|
91
|
+
hr.get(URL + '/get?ids[a]=1&ids[b]=2').body.should.equal({
|
92
|
+
'ids' => {'a' => '1', 'b' => '2'}
|
93
|
+
}.inspect)
|
94
|
+
|
95
|
+
hr.get(:url => URL + '/get', :parameters => 'ids[a]=1&ids[b]=2').body.should.equal({
|
96
|
+
'ids' => {'a' => '1', 'b' => '2'}
|
97
|
+
}.inspect)
|
98
|
+
|
99
|
+
hr.get(:url => URL + '/get', :parameters => {'ids[a]' => 1, 'ids[b]' => 2}).body.should.equal({
|
100
|
+
'ids' => {'a' => '1', 'b' => '2'}
|
101
|
+
}.inspect)
|
102
|
+
end
|
103
|
+
|
104
|
+
specify "post method" do
|
105
|
+
hr.post(URL + '/get').body.should.equal({}.inspect)
|
106
|
+
hr.post(URL + '/get?&').body.should.equal({}.inspect)
|
107
|
+
hr.post(URL + '/get?&#').body.should.equal({}.inspect)
|
108
|
+
hr.post(URL + '/get?abc=').body.should.equal({'abc' => ''}.inspect)
|
109
|
+
|
110
|
+
hr.post(URL + '/get?lang=Ruby&version=1.9').body.should.equal({
|
111
|
+
'lang' => 'Ruby', 'version' => '1.9'
|
112
|
+
}.inspect)
|
113
|
+
|
114
|
+
hr.post(:url => URL + '/get', :parameters => 'lang=Ruby&version=1.9').body.should.equal({
|
115
|
+
'lang' => 'Ruby', 'version' => '1.9'
|
116
|
+
}.inspect)
|
117
|
+
|
118
|
+
hr.post(:url => URL + '/get', :parameters => {:lang => 'Ruby', :version => 1.9}).body.should.equal({
|
119
|
+
'lang' => 'Ruby', 'version' => '1.9'
|
120
|
+
}.inspect)
|
121
|
+
|
122
|
+
hr.post(:url => URL + '/get', :parameters => {'lang' => 'Ruby', 'version' => '1.9'}).body.should.equal({
|
123
|
+
'lang' => 'Ruby', 'version' => '1.9'
|
124
|
+
}.inspect)
|
125
|
+
|
126
|
+
hr.post(URL + '/get?ids[]=1&ids[]=2').body.should.equal({
|
127
|
+
'ids' => ['1', '2']
|
128
|
+
}.inspect)
|
129
|
+
|
130
|
+
hr.post(:url => URL + '/get', :parameters => 'ids[]=1&ids[]=2').body.should.equal({
|
131
|
+
'ids' => ['1', '2']
|
132
|
+
}.inspect)
|
133
|
+
|
134
|
+
hr.post(URL + '/get?ids[a]=1&ids[b]=2').body.should.equal({
|
135
|
+
'ids' => {'a' => '1', 'b' => '2'}
|
136
|
+
}.inspect)
|
137
|
+
|
138
|
+
hr.post(:url => URL + '/get', :parameters => 'ids[a]=1&ids[b]=2').body.should.equal({
|
139
|
+
'ids' => {'a' => '1', 'b' => '2'}
|
140
|
+
}.inspect)
|
141
|
+
|
142
|
+
hr.post(:url => URL + '/get', :parameters => {'ids[a]' => 1, 'ids[b]' => 2}).body.should.equal({
|
143
|
+
'ids' => {'a' => '1', 'b' => '2'}
|
144
|
+
}.inspect)
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
context "http auth" do
|
150
|
+
specify "Basic Auth" do
|
151
|
+
hr.get("http://zhou:password@localhost:9527/auth/basic").body.should.equal "success!"
|
152
|
+
hr.get("http://localhost:9527/auth/basic").body.should.equal ""
|
153
|
+
|
154
|
+
hr.get(
|
155
|
+
:url => "http://zhou:password@localhost:9527/auth/basic",
|
156
|
+
:auth => :basic
|
157
|
+
).body.should.equal "success!"
|
158
|
+
|
159
|
+
hr.get(
|
160
|
+
:url => "http://localhost:9527/auth/basic",
|
161
|
+
:auth_username => 'zhou',
|
162
|
+
:auth_password => 'password',
|
163
|
+
:auth => :basic
|
164
|
+
).body.should.equal "success!"
|
165
|
+
|
166
|
+
hr.get(
|
167
|
+
:url => "http://localhost:9527/auth/basic",
|
168
|
+
:auth => {
|
169
|
+
:password => 'password',
|
170
|
+
:username => 'zhou',
|
171
|
+
:type => :basic
|
172
|
+
}
|
173
|
+
).body.should.equal "success!"
|
174
|
+
|
175
|
+
hr.get(
|
176
|
+
:url => "http://localhost:9527/auth/basic",
|
177
|
+
:auth => {
|
178
|
+
:password => 'password',
|
179
|
+
:username => 'zhou'
|
180
|
+
}
|
181
|
+
).body.should.equal "success!"
|
182
|
+
end
|
183
|
+
|
184
|
+
specify "Digest Auth" do
|
185
|
+
hr.get(
|
186
|
+
:url => "http://zhou:password@localhost:9527/auth/digest",
|
187
|
+
:auth => :digest
|
188
|
+
).body.should.equal "success!"
|
189
|
+
|
190
|
+
hr.get(
|
191
|
+
:url => "http://localhost:9527/auth/digest",
|
192
|
+
:auth_username => 'zhou',
|
193
|
+
:auth_password => 'password',
|
194
|
+
:auth => :digest
|
195
|
+
).body.should.equal "success!"
|
196
|
+
|
197
|
+
hr.get(
|
198
|
+
:url => "http://localhost:9527/auth/digest",
|
199
|
+
:auth => {
|
200
|
+
:password => 'password',
|
201
|
+
:username => 'zhou',
|
202
|
+
:type => :digest
|
203
|
+
}
|
204
|
+
).body.should.equal "success!"
|
205
|
+
end
|
206
|
+
|
207
|
+
end
|
208
|
+
|
209
|
+
context 'Session && Cookie' do
|
210
|
+
specify "Session" do
|
211
|
+
h = hr.get(URL + "/session")
|
212
|
+
h.body.should.equal "1"
|
213
|
+
|
214
|
+
h = hr.get(:url => URL + "/session", :cookies => h.cookies)
|
215
|
+
h.body.should.equal "2"
|
216
|
+
|
217
|
+
h = hr.get(:url => URL + "/session", :cookies => h.cookies)
|
218
|
+
h.body.should.equal "3"
|
219
|
+
|
220
|
+
h1 = hr.get(URL + "/session")
|
221
|
+
h1.body.should.equal "1"
|
222
|
+
|
223
|
+
h1 = hr.get(:url => URL + "/session", :cookies => h1.cookies)
|
224
|
+
h1.body.should.equal "2"
|
225
|
+
|
226
|
+
h = hr.get(URL + "/session/1")
|
227
|
+
h.body.should.equal "/session/1:/session/2"
|
228
|
+
|
229
|
+
h = hr.get(:url => URL + "/session/1", :redirect => false)
|
230
|
+
h.code_3xx?.should.equal true
|
231
|
+
end
|
232
|
+
|
233
|
+
specify "Cookie" do
|
234
|
+
h = hr.get(URL + "/cookie")
|
235
|
+
h.cookies['name'].should.equal 'zhou'
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
context 'upload file' do
|
240
|
+
specify 'upload file' do
|
241
|
+
files = [{:file_name => 'hi.txt', :field_name => 'file', :file_content => 'hi'}]
|
242
|
+
h = hr.post(:url => URL + '/upload_file', :files => files)
|
243
|
+
h.body.should.equal 'hi.txt - hi'
|
244
|
+
end
|
245
|
+
|
246
|
+
specify 'upload file with parameters' do
|
247
|
+
files = [{:file_name => 'hi.txt', :field_name => 'file', :file_content => 'hi'}]
|
248
|
+
h = hr.post(:url => URL + '/upload_file', :files => files, :parameters => {:name => 'Ruby'})
|
249
|
+
h.body.should.equal 'hi.txt - hi' + {'name' => 'Ruby'}.inspect
|
250
|
+
end
|
251
|
+
|
252
|
+
specify 'upload file with parameters and query string' do
|
253
|
+
files = [{:file_name => 'hi.txt', :field_name => 'file', :file_content => 'hi'}]
|
254
|
+
h = hr.post(:url => URL + '/upload_file?version=1.9', :files => files, :parameters => {:name => 'Ruby'})
|
255
|
+
h.body.should.equal 'hi.txt - hi' + {'name' => 'Ruby', 'version' => '1.9'}.inspect
|
256
|
+
end
|
257
|
+
|
258
|
+
specify 'upload 2 files' do
|
259
|
+
files = [
|
260
|
+
{:file_name => 'hi.txt', :field_name => 'file', :file_content => 'hi'},
|
261
|
+
{:file_name => 'ih.txt', :field_name => 'elif', :file_content => 'ih'}
|
262
|
+
]
|
263
|
+
h = hr.post(:url => URL + '/upload_file2', :files => files)
|
264
|
+
h.body.should.equal 'hi.txt - hi, ih.txt - ih'
|
265
|
+
end
|
266
|
+
end
|
data/test/web_server.rb
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'rubygems'
|
3
|
+
require 'rack'
|
4
|
+
include Rack
|
5
|
+
|
6
|
+
builder = Builder.new do
|
7
|
+
|
8
|
+
map '/' do
|
9
|
+
run lambda {|env|
|
10
|
+
[200, {'Content-Type' => 'text/html'}, 'It Works!']
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
map '/get' do
|
15
|
+
run lambda {|env|
|
16
|
+
[200, {'Content-Type' => 'text/html'}, Request.new(env).params.inspect]
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
map '/post' do
|
21
|
+
run lambda {|env|
|
22
|
+
[200, {'Content-Type' => 'text/html'}, Request.new(env).params.inspect]
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
map '/ajax' do
|
27
|
+
run lambda {|env|
|
28
|
+
[200, {'Content-Type' => 'text/html'}, Request.new(env).xhr? ? 'Y' : 'N']
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
map '/method/post' do
|
33
|
+
run lambda {|env|
|
34
|
+
body = Request.new(env).post? ? 'Yes' : 'No'
|
35
|
+
[200, {'Content-Type' => 'text/html'}, body]
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
map '/method/get' do
|
40
|
+
run lambda {|env|
|
41
|
+
body = Request.new(env).get? ? 'Yes' : 'No'
|
42
|
+
[200, {'Content-Type' => 'text/html'}, body]
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
map '/get-method-name' do
|
47
|
+
run lambda {|env|
|
48
|
+
[200, {'Content-Type' => 'text/html'}, Request.new(env).request_method]
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
map '/auth/basic' do
|
53
|
+
auths = {:username => 'zhou', :password => 'password'}
|
54
|
+
|
55
|
+
app = lambda {|env|
|
56
|
+
headers = {
|
57
|
+
'Content-Type' => 'text/html'
|
58
|
+
}
|
59
|
+
[200, headers, 'success!']
|
60
|
+
}
|
61
|
+
|
62
|
+
app = Auth::Basic.new(app) {|user, pass|
|
63
|
+
auths[:username] == user and auths[:password] == pass
|
64
|
+
}
|
65
|
+
app.realm = 'HttpRequest'
|
66
|
+
run app
|
67
|
+
end
|
68
|
+
|
69
|
+
map '/auth/digest' do
|
70
|
+
auths = {:username => 'zhou', :password => 'password'}
|
71
|
+
realm = 'HttpRequest'
|
72
|
+
|
73
|
+
app = lambda {|env|
|
74
|
+
headers = {
|
75
|
+
'Content-Type' => 'text/html'
|
76
|
+
}
|
77
|
+
[200, headers, 'success!']
|
78
|
+
}
|
79
|
+
|
80
|
+
app = Auth::Digest::MD5.new(app) {|user|
|
81
|
+
user == auths[:username] ?
|
82
|
+
Digest::MD5.hexdigest("#{auths[:username]}:#{realm}:#{auths[:password]}") : nil
|
83
|
+
}
|
84
|
+
app.realm = realm
|
85
|
+
app.opaque = 'have a nice day!'
|
86
|
+
app.passwords_hashed = true
|
87
|
+
run app
|
88
|
+
end
|
89
|
+
|
90
|
+
map '/session' do
|
91
|
+
app = lambda {|env|
|
92
|
+
env['rack.session']['counter'] ||= 0
|
93
|
+
env['rack.session']['counter'] += 1
|
94
|
+
[200, {'Content-Type' => 'text/html'}, "#{env['rack.session']['counter']}"]
|
95
|
+
}
|
96
|
+
run Rack::Session::Cookie.new(app)
|
97
|
+
end
|
98
|
+
|
99
|
+
map '/session/1' do
|
100
|
+
app = lambda {|env|
|
101
|
+
env['rack.session']['page1'] = '/session/1'
|
102
|
+
response = Response.new
|
103
|
+
response.redirect '/session/2', 301
|
104
|
+
response.finish
|
105
|
+
}
|
106
|
+
run Rack::Session::Cookie.new(app)
|
107
|
+
end
|
108
|
+
|
109
|
+
map '/session/2' do
|
110
|
+
app = lambda {|env|
|
111
|
+
env['rack.session']['page2'] = '/session/2'
|
112
|
+
[200,
|
113
|
+
{'Content-Type' => 'text/html'},
|
114
|
+
"#{env['rack.session']['page1']}:#{env['rack.session']['page2']}"
|
115
|
+
]
|
116
|
+
}
|
117
|
+
run Rack::Session::Cookie.new(app)
|
118
|
+
end
|
119
|
+
|
120
|
+
map '/cookie' do
|
121
|
+
run lambda {|env|
|
122
|
+
response = Response.new
|
123
|
+
response.set_cookie 'name', 'zhou'
|
124
|
+
response.finish
|
125
|
+
}
|
126
|
+
end
|
127
|
+
|
128
|
+
map '/upload_file' do
|
129
|
+
run lambda {|env|
|
130
|
+
request = Request.new env
|
131
|
+
data = ''
|
132
|
+
if request.params['file']
|
133
|
+
file = request.params['file']
|
134
|
+
data << file[:filename] + ' - ' + file[:tempfile].read
|
135
|
+
end
|
136
|
+
params = request.params
|
137
|
+
params.delete 'file'
|
138
|
+
data << params.inspect if params.size > 0
|
139
|
+
[200, {'Content-Type' => 'text/html'}, data]
|
140
|
+
}
|
141
|
+
end
|
142
|
+
|
143
|
+
map '/upload_file2' do
|
144
|
+
run lambda {|env|
|
145
|
+
request = Request.new env
|
146
|
+
data = ''
|
147
|
+
file = request.params['file']
|
148
|
+
data << file[:filename] + ' - ' + file[:tempfile].read
|
149
|
+
file = request.params['elif']
|
150
|
+
data << ", " + file[:filename] + ' - ' + file[:tempfile].read
|
151
|
+
[200, {'Content-Type' => 'text/html'}, data]
|
152
|
+
}
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
156
|
+
|
157
|
+
Handler::Mongrel.run builder, :Port => 9527
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: http_request.rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: "1.1"
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- xianhua.zhou
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-10-04 00:00:00 +08:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -25,8 +25,12 @@ files:
|
|
25
25
|
- Changelog
|
26
26
|
- README.rdoc
|
27
27
|
- lib/http_request.rb
|
28
|
+
- test/test_http_request.rb
|
29
|
+
- test/web_server.rb
|
28
30
|
has_rdoc: true
|
29
31
|
homepage: http://my.cnzxh.net
|
32
|
+
licenses: []
|
33
|
+
|
30
34
|
post_install_message:
|
31
35
|
rdoc_options: []
|
32
36
|
|
@@ -47,9 +51,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
47
51
|
requirements: []
|
48
52
|
|
49
53
|
rubyforge_project: http_request.rb
|
50
|
-
rubygems_version: 1.3.
|
54
|
+
rubygems_version: 1.3.5
|
51
55
|
signing_key:
|
52
|
-
specification_version:
|
56
|
+
specification_version: 3
|
53
57
|
summary: http_request.rb is a small, lightweight, powerful HttpRequest class based on the 'net/http' and 'net/ftp' libraries
|
54
58
|
test_files: []
|
55
59
|
|