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 CHANGED
@@ -1,3 +1,9 @@
1
+ v1.1
2
+ * added some testing with test/spec
3
+ * fixed some bugs such as uploading files, combine parameters and query string etc.
4
+ * supported the HTTP Digest Authentication
5
+ * refactoring code...
6
+
1
7
  v1.0.7
2
8
  * bug fixing for the ':ajax' and ':xhr' directive
3
9
 
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.0.6
230
+ 1.1
229
231
 
230
232
  == Author
231
233
 
232
- xianhua.zhou<xianhua.zhou at gmail.com>, homepage: http://my.cnzxh.net
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.0.6
14
+ # v1.1
15
15
  #
16
- # Last Change: 7 July, 2009
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
- # version
36
- VERSION = '1.0.7'.freeze
37
- def self.version;VERSION;end
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
- # avaiabled http methods
40
- def self.http_methods
41
- %w{get head post put proppatch lock unlock options propfind delete move copy mkcol trace}
42
- end
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
- # return data with or without block
45
- def self.data(response, &block)
46
- response.url = @@__url if defined? @@__url
47
- block_given? ? block.call(response) : response
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
- # check the http resource whether or not available
51
- def self.available?(url, timeout = 5)
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
- # for proxy
79
- http = if @options[:proxy_addr]
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
- # ssl support
90
- http.use_ssl = true if @uri.scheme =~ /^https$/i
86
+ # parse and merge for the options[:parameters]
87
+ parse_parameters
91
88
 
92
- # sending request and get response
93
- response = send_request http
89
+ # send http request and get the response
90
+ response = send_request_and_get_response
94
91
 
95
- return HttpRequest.data(response, &block) unless @options[:redirect]
92
+ return data(response, &block) unless @options[:redirect]
96
93
 
97
94
  # redirect?
98
- case response
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 of http requests
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] = uri.userinfo ? uri.userinfo.split(':') : [guest_name, guest_pass]
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 self.data(ftp, &block) unless method
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] && !block_given?
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
- self.data(ftp, &block)
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 init_args(method, options)
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 ' + VERSION
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
- # Basic Authenication
211
- @headers['Authorization'] = "Basic " + [@uri.userinfo].pack('m').delete!("\r\n") if @uri.userinfo
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 upload files by post method
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 = MD5.md5(rand.to_s).to_s[0..5]
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'] = MD5.md5(@options[:parameters]).to_s
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("#{@uri.path}#{@options[:parameters] unless @options[:parameters].eql?('?')}", @headers)
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 /^code_([0-9])xx\?$/
346
- is_a? CODE_CLASS_TO_OBJ[$1]
347
- when /^code_([0-9]+)\?$/
348
- is_a? CODE_TO_OBJ[$1]
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
@@ -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.0.7
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-08-24 00:00:00 +08:00
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.1
54
+ rubygems_version: 1.3.5
51
55
  signing_key:
52
- specification_version: 2
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