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 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