http_request.rb 1.0.7 → 1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|