httpclient 2.1.5 → 2.8.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +85 -0
  3. data/bin/httpclient +77 -0
  4. data/bin/jsonclient +85 -0
  5. data/lib/hexdump.rb +50 -0
  6. data/lib/http-access2.rb +6 -4
  7. data/lib/httpclient/auth.rb +575 -173
  8. data/lib/httpclient/cacert.pem +3952 -0
  9. data/lib/httpclient/cacert1024.pem +3866 -0
  10. data/lib/httpclient/connection.rb +6 -2
  11. data/lib/httpclient/cookie.rb +162 -504
  12. data/lib/httpclient/http.rb +334 -119
  13. data/lib/httpclient/include_client.rb +85 -0
  14. data/lib/httpclient/jruby_ssl_socket.rb +588 -0
  15. data/lib/httpclient/session.rb +385 -288
  16. data/lib/httpclient/ssl_config.rb +195 -155
  17. data/lib/httpclient/ssl_socket.rb +150 -0
  18. data/lib/httpclient/timeout.rb +14 -10
  19. data/lib/httpclient/util.rb +142 -6
  20. data/lib/httpclient/version.rb +3 -0
  21. data/lib/httpclient/webagent-cookie.rb +459 -0
  22. data/lib/httpclient.rb +509 -202
  23. data/lib/jsonclient.rb +63 -0
  24. data/lib/oauthclient.rb +111 -0
  25. data/sample/async.rb +8 -0
  26. data/sample/auth.rb +11 -0
  27. data/sample/cookie.rb +18 -0
  28. data/sample/dav.rb +103 -0
  29. data/sample/howto.rb +49 -0
  30. data/sample/jsonclient.rb +67 -0
  31. data/sample/oauth_buzz.rb +57 -0
  32. data/sample/oauth_friendfeed.rb +59 -0
  33. data/sample/oauth_twitter.rb +61 -0
  34. data/sample/ssl/0cert.pem +22 -0
  35. data/sample/ssl/0key.pem +30 -0
  36. data/sample/ssl/1000cert.pem +19 -0
  37. data/sample/ssl/1000key.pem +18 -0
  38. data/sample/ssl/htdocs/index.html +10 -0
  39. data/sample/ssl/ssl_client.rb +22 -0
  40. data/sample/ssl/webrick_httpsd.rb +29 -0
  41. data/sample/stream.rb +21 -0
  42. data/sample/thread.rb +27 -0
  43. data/sample/wcat.rb +21 -0
  44. data/test/ca-chain.pem +44 -0
  45. data/test/ca.cert +23 -0
  46. data/test/client-pass.key +18 -0
  47. data/test/client.cert +19 -0
  48. data/test/client.key +15 -0
  49. data/test/helper.rb +131 -0
  50. data/test/htdigest +1 -0
  51. data/test/htpasswd +2 -0
  52. data/test/jruby_ssl_socket/test_pemutils.rb +32 -0
  53. data/test/runner.rb +2 -0
  54. data/test/server.cert +19 -0
  55. data/test/server.key +15 -0
  56. data/test/sslsvr.rb +65 -0
  57. data/test/subca.cert +21 -0
  58. data/test/test_auth.rb +492 -0
  59. data/test/test_cookie.rb +309 -0
  60. data/test/test_hexdump.rb +14 -0
  61. data/test/test_http-access2.rb +508 -0
  62. data/test/test_httpclient.rb +2145 -0
  63. data/test/test_include_client.rb +52 -0
  64. data/test/test_jsonclient.rb +80 -0
  65. data/test/test_ssl.rb +559 -0
  66. data/test/test_webagent-cookie.rb +465 -0
  67. metadata +85 -44
  68. data/lib/httpclient/auth.rb.orig +0 -513
  69. data/lib/httpclient/cacert.p7s +0 -1579
  70. data/lib/httpclient.rb.orig +0 -1020
  71. data/lib/tags +0 -908
@@ -1,1020 +0,0 @@
1
- # HTTPClient - HTTP client library.
2
- # Copyright (C) 2000-2009 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
3
- #
4
- # This program is copyrighted free software by NAKAMURA, Hiroshi. You can
5
- # redistribute it and/or modify it under the same terms of Ruby's license;
6
- # either the dual license version in 2003, or any later version.
7
-
8
-
9
- require 'uri'
10
- require 'stringio'
11
- require 'digest/sha1'
12
-
13
- # Extra library
14
- require 'httpclient/util'
15
- require 'httpclient/ssl_config'
16
- require 'httpclient/connection'
17
- require 'httpclient/session'
18
- require 'httpclient/http'
19
- require 'httpclient/auth'
20
- require 'httpclient/cookie'
21
-
22
-
23
- # The HTTPClient class provides several methods for accessing Web resources
24
- # via HTTP.
25
- #
26
- # HTTPClient instance is designed to be MT-safe. You can call a HTTPClient
27
- # instance from several threads without synchronization after setting up an
28
- # instance.
29
- #
30
- # clnt = HTTPClient.new
31
- # clnt.set_cookie_store('/home/nahi/cookie.dat')
32
- # urls.each do |url|
33
- # Thread.new(url) do |u|
34
- # p clnt.head(u).status
35
- # end
36
- # end
37
- #
38
- # == How to use
39
- #
40
- # At first, how to create your client. See initialize for more detail.
41
- #
42
- # 1. Create simple client.
43
- #
44
- # clnt = HTTPClient.new
45
- #
46
- # 2. Accessing resources through HTTP proxy. You can use environment
47
- # variable 'http_proxy' or 'HTTP_PROXY' instead.
48
- #
49
- # clnt = HTTPClient.new('http://myproxy:8080')
50
- #
51
- # === How to retrieve web resources
52
- #
53
- # See get_content.
54
- #
55
- # 1. Get content of specified URL. It returns a String of whole result.
56
- #
57
- # puts clnt.get_content('http://dev.ctor.org/')
58
- #
59
- # 2. Get content as chunks of String. It yields chunks of String.
60
- #
61
- # clnt.get_content('http://dev.ctor.org/') do |chunk|
62
- # puts chunk
63
- # end
64
- #
65
- # === Invoking other HTTP methods
66
- #
67
- # See head, get, post, put, delete, options, propfind, proppatch and trace.
68
- # It returns a HTTP::Message instance as a response.
69
- #
70
- # 1. Do HEAD request.
71
- #
72
- # res = clnt.head(uri)
73
- # p res.header['Last-Modified'][0]
74
- #
75
- # 2. Do GET request with query.
76
- #
77
- # query = { 'keyword' => 'ruby', 'lang' => 'en' }
78
- # res = clnt.get(uri, query)
79
- # p res.status
80
- # p res.contenttype
81
- # p res.header['X-Custom']
82
- # puts res.content
83
- #
84
- # === How to POST
85
- #
86
- # See post.
87
- #
88
- # 1. Do POST a form data.
89
- #
90
- # body = { 'keyword' => 'ruby', 'lang' => 'en' }
91
- # res = clnt.post(uri, body)
92
- #
93
- # 2. Do multipart file upload with POST. No need to set extra header by
94
- # yourself from httpclient/2.1.4.
95
- #
96
- # File.open('/tmp/post_data') do |file|
97
- # body = { 'upload' => file, 'user' => 'nahi' }
98
- # res = clnt.post(uri, body)
99
- # end
100
- #
101
- # === Accessing via SSL
102
- #
103
- # Ruby needs to be compiled with OpenSSL.
104
- #
105
- # 1. Get content of specified URL via SSL.
106
- # Just pass an URL which starts with 'https://'.
107
- #
108
- # https_url = 'https://www.rsa.com'
109
- # clnt.get_content(https_url)
110
- #
111
- # 2. Getting peer certificate from response.
112
- #
113
- # res = clnt.get(https_url)
114
- # p res.peer_cert #=> returns OpenSSL::X509::Certificate
115
- #
116
- # 3. Configuring OpenSSL options. See HTTPClient::SSLConfig for more details.
117
- #
118
- # user_cert_file = 'cert.pem'
119
- # user_key_file = 'privkey.pem'
120
- # clnt.ssl_config.set_client_cert_file(user_cert_file, user_key_file)
121
- # clnt.get_content(https_url)
122
- #
123
- # === Handling Cookies
124
- #
125
- # 1. Using volatile Cookies. Nothing to do. HTTPClient handles Cookies.
126
- #
127
- # clnt = HTTPClient.new
128
- # clnt.get_content(url1) # receives Cookies.
129
- # clnt.get_content(url2) # sends Cookies if needed.
130
- #
131
- # 2. Saving non volatile Cookies to a specified file. Need to set a file at
132
- # first and invoke save method at last.
133
- #
134
- # clnt = HTTPClient.new
135
- # clnt.set_cookie_store('/home/nahi/cookie.dat')
136
- # clnt.get_content(url)
137
- # ...
138
- # clnt.save_cookie_store
139
- #
140
- # 3. Disabling Cookies.
141
- #
142
- # clnt = HTTPClient.new
143
- # clnt.cookie_manager = nil
144
- #
145
- # === Configuring authentication credentials
146
- #
147
- # 1. Authentication with Web server. Supports BasicAuth, DigestAuth, and
148
- # Negotiate/NTLM (requires ruby/ntlm module).
149
- #
150
- # clnt = HTTPClient.new
151
- # domain = 'http://dev.ctor.org/http-access2/'
152
- # user = 'user'
153
- # password = 'user'
154
- # clnt.set_auth(domain, user, password)
155
- # p clnt.get_content('http://dev.ctor.org/http-access2/login').status
156
- #
157
- # 2. Authentication with Proxy server. Supports BasicAuth and NTLM
158
- # (requires win32/sspi)
159
- #
160
- # clnt = HTTPClient.new(proxy)
161
- # user = 'proxy'
162
- # password = 'proxy'
163
- # clnt.set_proxy_auth(user, password)
164
- # p clnt.get_content(url)
165
- #
166
- # === Invoking HTTP methods with custom header
167
- #
168
- # Pass a Hash or an Array for extheader argument.
169
- #
170
- # extheader = { 'Accept' => '*/*' }
171
- # clnt.get_content(uri, query, extheader)
172
- #
173
- # extheader = [['Accept', 'image/jpeg'], ['Accept', 'image/png']]
174
- # clnt.get_content(uri, query, extheader)
175
- #
176
- # === Invoking HTTP methods asynchronously
177
- #
178
- # See head_async, get_async, post_async, put_async, delete_async,
179
- # options_async, propfind_async, proppatch_async, and trace_async.
180
- # It immediately returns a HTTPClient::Connection instance as a returning value.
181
- #
182
- # connection = clnt.post_async(url, body)
183
- # print 'posting.'
184
- # while true
185
- # break if connection.finished?
186
- # print '.'
187
- # sleep 1
188
- # end
189
- # puts '.'
190
- # res = connection.pop
191
- # p res.status
192
- # p res.content.read # res.content is an IO for the res of async method.
193
- #
194
- # === Shortcut methods
195
- #
196
- # You can invoke get_content, get, etc. without creating HTTPClient instance.
197
- #
198
- # ruby -rhttpclient -e 'puts HTTPClient.get_content(ARGV.shift)' http://dev.ctor.org/
199
- # ruby -rhttpclient -e 'p HTTPClient.head(ARGV.shift).header["last-modified"]' http://dev.ctor.org/
200
- #
201
- class HTTPClient
202
- VERSION = '2.1.4'
203
- RUBY_VERSION_STRING = "ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]"
204
- /: (\S+) (\S+)/ =~ %q$Id: httpclient.rb 274 2009-05-13 13:54:08Z nahi $
205
- LIB_NAME = "(#{$1}/#{$2}, #{RUBY_VERSION_STRING})"
206
-
207
- include Util
208
-
209
- # Raised for indicating running environment configuration error for example
210
- # accessing via SSL under the ruby which is not compiled with OpenSSL.
211
- class ConfigurationError < StandardError
212
- end
213
-
214
- # Raised for indicating HTTP response error.
215
- class BadResponseError < RuntimeError
216
- # HTTP::Message:: a response
217
- attr_reader :res
218
-
219
- def initialize(msg, res = nil) # :nodoc:
220
- super(msg)
221
- @res = res
222
- end
223
- end
224
-
225
- # Raised for indicating a timeout error.
226
- class TimeoutError < RuntimeError
227
- end
228
-
229
- # Raised for indicating a connection timeout error.
230
- # You can configure connection timeout via HTTPClient#connect_timeout=.
231
- class ConnectTimeoutError < TimeoutError
232
- end
233
-
234
- # Raised for indicating a request sending timeout error.
235
- # You can configure request sending timeout via HTTPClient#send_timeout=.
236
- class SendTimeoutError < TimeoutError
237
- end
238
-
239
- # Raised for indicating a response receiving timeout error.
240
- # You can configure response receiving timeout via
241
- # HTTPClient#receive_timeout=.
242
- class ReceiveTimeoutError < TimeoutError
243
- end
244
-
245
- # Deprecated. just for backward compatibility
246
- class Session
247
- BadResponse = ::HTTPClient::BadResponseError
248
- end
249
-
250
- class << self
251
- %w(get_content post_content head get post put delete options propfind proppatch trace).each do |name|
252
- eval <<-EOD
253
- def #{name}(*arg, &block)
254
- clnt = new
255
- begin
256
- clnt.#{name}(*arg, &block)
257
- ensure
258
- clnt.reset_all
259
- end
260
- end
261
- EOD
262
- end
263
-
264
- private
265
-
266
- def attr_proxy(symbol, assignable = false)
267
- name = symbol.to_s
268
- define_method(name) {
269
- @session_manager.__send__(name)
270
- }
271
- if assignable
272
- aname = name + '='
273
- define_method(aname) { |rhs|
274
- reset_all
275
- @session_manager.__send__(aname, rhs)
276
- }
277
- end
278
- end
279
- end
280
-
281
- # HTTPClient::SSLConfig:: SSL configurator.
282
- attr_reader :ssl_config
283
- # WebAgent::CookieManager:: Cookies configurator.
284
- attr_accessor :cookie_manager
285
- # An array of response HTTP message body String which is used for loop-back
286
- # test. See test/* to see how to use it. If you want to do loop-back test
287
- # of HTTP header, use test_loopback_http_response instead.
288
- attr_reader :test_loopback_response
289
- # An array of request filter which can trap HTTP request/response.
290
- # See HTTPClient::WWWAuth to see how to use it.
291
- attr_reader :request_filter
292
- # HTTPClient::ProxyAuth:: Proxy authentication handler.
293
- attr_reader :proxy_auth
294
- # HTTPClient::WWWAuth:: WWW authentication handler.
295
- attr_reader :www_auth
296
- # How many times get_content and post_content follows HTTP redirect.
297
- # 10 by default.
298
- attr_accessor :follow_redirect_count
299
-
300
- # Set HTTP version as a String:: 'HTTP/1.0' or 'HTTP/1.1'
301
- attr_proxy(:protocol_version, true)
302
- # Connect timeout in sec.
303
- attr_proxy(:connect_timeout, true)
304
- # Request sending timeout in sec.
305
- attr_proxy(:send_timeout, true)
306
- # Response receiving timeout in sec.
307
- attr_proxy(:receive_timeout, true)
308
- # Negotiation retry count for authentication. 5 by default.
309
- attr_proxy(:protocol_retry_count, true)
310
- # if your ruby is older than 2005-09-06, do not set socket_sync = false to
311
- # avoid an SSL socket blocking bug in openssl/buffering.rb.
312
- attr_proxy(:socket_sync, true)
313
- # User-Agent header in HTTP request.
314
- attr_proxy(:agent_name, true)
315
- # From header in HTTP request.
316
- attr_proxy(:from, true)
317
- # An array of response HTTP String (not a HTTP message body) which is used
318
- # for loopback test. See test/* to see how to use it.
319
- attr_proxy(:test_loopback_http_response)
320
-
321
- # Default extheader for PROPFIND request.
322
- PROPFIND_DEFAULT_EXTHEADER = { 'Depth' => '0' }
323
-
324
- # Creates a HTTPClient instance which manages sessions, cookies, etc.
325
- #
326
- # HTTPClient.new takes 3 optional arguments for proxy url string,
327
- # User-Agent String and From header String. User-Agent and From are embedded
328
- # in HTTP request Header if given. No User-Agent and From header added
329
- # without setting it explicitly.
330
- #
331
- # proxy = 'http://myproxy:8080'
332
- # agent_name = 'MyAgent/0.1'
333
- # from = 'from@example.com'
334
- # HTTPClient.new(proxy, agent_name, from)
335
- #
336
- # You can use a keyword argument style Hash. Keys are :proxy, :agent_name
337
- # and :from.
338
- #
339
- # HTTPClient.new(:agent_name = 'MyAgent/0.1')
340
- def initialize(*args)
341
- proxy, agent_name, from = keyword_argument(args, :proxy, :agent_name, :from)
342
- @proxy = nil # assigned later.
343
- @no_proxy = nil
344
- @www_auth = WWWAuth.new
345
- @proxy_auth = ProxyAuth.new
346
- @request_filter = [@proxy_auth, @www_auth]
347
- @debug_dev = nil
348
- @redirect_uri_callback = method(:default_redirect_uri_callback)
349
- @test_loopback_response = []
350
- @session_manager = SessionManager.new(self)
351
- @session_manager.agent_name = agent_name
352
- @session_manager.from = from
353
- @session_manager.ssl_config = @ssl_config = SSLConfig.new(self)
354
- @cookie_manager = WebAgent::CookieManager.new
355
- @follow_redirect_count = 10
356
- load_environment
357
- self.proxy = proxy if proxy
358
- end
359
-
360
- # Returns debug device if exists. See debug_dev=.
361
- def debug_dev
362
- @debug_dev
363
- end
364
-
365
- # Sets debug device. Once debug device is set, all HTTP requests and
366
- # responses are dumped to given device. dev must respond to << for dump.
367
- #
368
- # Calling this method resets all existing sessions.
369
- def debug_dev=(dev)
370
- @debug_dev = dev
371
- reset_all
372
- @session_manager.debug_dev = dev
373
- end
374
-
375
- # Returns URI object of HTTP proxy if exists.
376
- def proxy
377
- @proxy
378
- end
379
-
380
- # Sets HTTP proxy used for HTTP connection. Given proxy can be an URI,
381
- # a String or nil. You can set user/password for proxy authentication like
382
- # HTTPClient#proxy = 'http://user:passwd@myproxy:8080'
383
- #
384
- # You can use environment variable 'http_proxy' or 'HTTP_PROXY' for it.
385
- # You need to use 'cgi_http_proxy' or 'CGI_HTTP_PROXY' instead if you run
386
- # HTTPClient from CGI environment from security reason. (HTTPClient checks
387
- # 'REQUEST_METHOD' environment variable whether it's CGI or not)
388
- #
389
- # Calling this method resets all existing sessions.
390
- def proxy=(proxy)
391
- if proxy.nil?
392
- @proxy = nil
393
- @proxy_auth.reset_challenge
394
- else
395
- @proxy = urify(proxy)
396
- if @proxy.scheme == nil or @proxy.scheme.downcase != 'http' or
397
- @proxy.host == nil or @proxy.port == nil
398
- raise ArgumentError.new("unsupported proxy #{proxy}")
399
- end
400
- @proxy_auth.reset_challenge
401
- if @proxy.user || @proxy.password
402
- @proxy_auth.set_auth(@proxy.user, @proxy.password)
403
- end
404
- end
405
- reset_all
406
- @session_manager.proxy = @proxy
407
- @proxy
408
- end
409
-
410
- # Returns NO_PROXY setting String if given.
411
- def no_proxy
412
- @no_proxy
413
- end
414
-
415
- # Sets NO_PROXY setting String. no_proxy must be a comma separated String.
416
- # Each entry must be 'host' or 'host:port' such as;
417
- # HTTPClient#no_proxy = 'example.com,example.co.jp:443'
418
- #
419
- # 'localhost' is treated as a no_proxy site regardless of explicitly listed.
420
- # HTTPClient checks given URI objects before accessing it.
421
- # 'host' is tail string match. No IP-addr conversion.
422
- #
423
- # You can use environment variable 'no_proxy' or 'NO_PROXY' for it.
424
- #
425
- # Calling this method resets all existing sessions.
426
- def no_proxy=(no_proxy)
427
- @no_proxy = no_proxy
428
- reset_all
429
- end
430
-
431
- # Sets credential for Web server authentication.
432
- # domain:: a String or an URI to specify where HTTPClient should use this
433
- # credential. If you set uri to nil, HTTPClient uses this credential
434
- # wherever a server requires it.
435
- # user:: username String.
436
- # passwd:: password String.
437
- #
438
- # You can set multiple credentials for each uri.
439
- #
440
- # clnt.set_auth('http://www.example.com/foo/', 'foo_user', 'passwd')
441
- # clnt.set_auth('http://www.example.com/bar/', 'bar_user', 'passwd')
442
- #
443
- # Calling this method resets all existing sessions.
444
- def set_auth(domain, user, passwd)
445
- uri = urify(domain)
446
- @www_auth.set_auth(uri, user, passwd)
447
- reset_all
448
- end
449
-
450
- # Deprecated. Use set_auth instead.
451
- def set_basic_auth(domain, user, passwd)
452
- uri = urify(domain)
453
- @www_auth.basic_auth.set(uri, user, passwd)
454
- reset_all
455
- end
456
-
457
- # Sets credential for Proxy authentication.
458
- # user:: username String.
459
- # passwd:: password String.
460
- #
461
- # Calling this method resets all existing sessions.
462
- def set_proxy_auth(user, passwd)
463
- @proxy_auth.set_auth(user, passwd)
464
- reset_all
465
- end
466
-
467
- # Sets the filename where non-volatile Cookies be saved by calling
468
- # save_cookie_store.
469
- # This method tries to load and managing Cookies from the specified file.
470
- #
471
- # Calling this method resets all existing sessions.
472
- def set_cookie_store(filename)
473
- @cookie_manager.cookies_file = filename
474
- @cookie_manager.load_cookies if filename
475
- reset_all
476
- end
477
-
478
- # Try to save Cookies to the file specified in set_cookie_store. Unexpected
479
- # error will be raised if you don't call set_cookie_store first.
480
- # (interface mismatch between WebAgent::CookieManager implementation)
481
- def save_cookie_store
482
- @cookie_manager.save_cookies
483
- end
484
-
485
- # Sets callback proc when HTTP redirect status is returned for get_content
486
- # and post_content. default_redirect_uri_callback is used by default.
487
- #
488
- # If you need strict implementation which does not allow relative URI
489
- # redirection, set strict_redirect_uri_callback instead.
490
- #
491
- # clnt.redirect_uri_callback = clnt.method(:strict_redirect_uri_callback)
492
- #
493
- def redirect_uri_callback=(redirect_uri_callback)
494
- @redirect_uri_callback = redirect_uri_callback
495
- end
496
-
497
- # Retrieves a web resource.
498
- #
499
- # uri:: a String or an URI object which represents an URL of web resource.
500
- # query:: a Hash or an Array of query part of URL.
501
- # e.g. { "a" => "b" } => 'http://host/part?a=b'.
502
- # Give an array to pass multiple value like
503
- # [["a", "b"], ["a", "c"]] => 'http://host/part?a=b&a=c'.
504
- # extheader:: a Hash or an Array of extra headers. e.g.
505
- # { 'Accept' => '*/*' } or
506
- # [['Accept', 'image/jpeg'], ['Accept', 'image/png']].
507
- # &block:: Give a block to get chunked message-body of response like
508
- # get_content(uri) { |chunked_body| ... }.
509
- # Size of each chunk may not be the same.
510
- #
511
- # get_content follows HTTP redirect status (see HTTP::Status.redirect?)
512
- # internally and try to retrieve content from redirected URL. See
513
- # redirect_uri_callback= how HTTP redirection is handled.
514
- #
515
- # If you need to get full HTTP response including HTTP status and headers,
516
- # use get method. get returns HTTP::Message as a response and you need to
517
- # follow HTTP redirect by yourself if you need.
518
- def get_content(uri, query = nil, extheader = {}, &block)
519
- follow_redirect(:get, uri, query, nil, extheader, &block).content
520
- end
521
-
522
- # Posts a content.
523
- #
524
- # uri:: a String or an URI object which represents an URL of web resource.
525
- # body:: a Hash or an Array of body part.
526
- # e.g. { "a" => "b" } => 'a=b'.
527
- # Give an array to pass multiple value like
528
- # [["a", "b"], ["a", "c"]] => 'a=b&a=c'.
529
- # When you pass a File as a value, it will be posted as a
530
- # multipart/form-data. e.g. { 'upload' => file }
531
- # extheader:: a Hash or an Array of extra headers. e.g.
532
- # { 'Accept' => '*/*' } or
533
- # [['Accept', 'image/jpeg'], ['Accept', 'image/png']].
534
- # &block:: Give a block to get chunked message-body of response like
535
- # post_content(uri) { |chunked_body| ... }.
536
- # Size of each chunk may not be the same.
537
- #
538
- # post_content follows HTTP redirect status (see HTTP::Status.redirect?)
539
- # internally and try to post the content to redirected URL. See
540
- # redirect_uri_callback= how HTTP redirection is handled.
541
- #
542
- # If you need to get full HTTP response including HTTP status and headers,
543
- # use post method.
544
- def post_content(uri, body = nil, extheader = {}, &block)
545
- follow_redirect(:post, uri, nil, body, extheader, &block).content
546
- end
547
-
548
- # A method for redirect uri callback. How to use:
549
- # clnt.redirect_uri_callback = clnt.method(:strict_redirect_uri_callback)
550
- # This callback does not allow relative redirect such as
551
- # Location: ../foo/
552
- # in HTTP header. (raises BadResponseError instead)
553
- def strict_redirect_uri_callback(uri, res)
554
- newuri = URI.parse(res.header['location'][0])
555
- if https?(uri) && !https?(newuri)
556
- raise BadResponseError.new("redirecting to non-https resource")
557
- end
558
- unless newuri.is_a?(URI::HTTP)
559
- raise BadResponseError.new("unexpected location: #{newuri}", res)
560
- end
561
- puts "redirect to: #{newuri}" if $DEBUG
562
- newuri
563
- end
564
-
565
- # A default method for redirect uri callback. This method is used by
566
- # HTTPClient instance by default.
567
- # This callback allows relative redirect such as
568
- # Location: ../foo/
569
- # in HTTP header.
570
- def default_redirect_uri_callback(uri, res)
571
- newuri = URI.parse(res.header['location'][0])
572
- if https?(uri) && !https?(newuri)
573
- raise BadResponseError.new("redirecting to non-https resource")
574
- end
575
- unless newuri.is_a?(URI::HTTP)
576
- newuri = uri + newuri
577
- STDERR.puts("could be a relative URI in location header which is not recommended")
578
- STDERR.puts("'The field value consists of a single absolute URI' in HTTP spec")
579
- end
580
- puts "redirect to: #{newuri}" if $DEBUG
581
- newuri
582
- end
583
-
584
- # Sends HEAD request to the specified URL. See request for arguments.
585
- def head(uri, query = nil, extheader = {})
586
- request(:head, uri, query, nil, extheader)
587
- end
588
-
589
- # Sends GET request to the specified URL. See request for arguments.
590
- def get(uri, query = nil, extheader = {}, &block)
591
- request(:get, uri, query, nil, extheader, &block)
592
- end
593
-
594
- # Sends POST request to the specified URL. See request for arguments.
595
- def post(uri, body = nil, extheader = {}, &block)
596
- request(:post, uri, nil, body, extheader, &block)
597
- end
598
-
599
- # Sends PUT request to the specified URL. See request for arguments.
600
- def put(uri, body = nil, extheader = {}, &block)
601
- request(:put, uri, nil, body, extheader, &block)
602
- end
603
-
604
- # Sends DELETE request to the specified URL. See request for arguments.
605
- def delete(uri, extheader = {}, &block)
606
- request(:delete, uri, nil, nil, extheader, &block)
607
- end
608
-
609
- # Sends OPTIONS request to the specified URL. See request for arguments.
610
- def options(uri, extheader = {}, &block)
611
- request(:options, uri, nil, nil, extheader, &block)
612
- end
613
-
614
- # Sends PROPFIND request to the specified URL. See request for arguments.
615
- def propfind(uri, extheader = PROPFIND_DEFAULT_EXTHEADER, &block)
616
- request(:propfind, uri, nil, nil, extheader, &block)
617
- end
618
-
619
- # Sends PROPPATCH request to the specified URL. See request for arguments.
620
- def proppatch(uri, body = nil, extheader = {}, &block)
621
- request(:proppatch, uri, nil, body, extheader, &block)
622
- end
623
-
624
- # Sends TRACE request to the specified URL. See request for arguments.
625
- def trace(uri, query = nil, body = nil, extheader = {}, &block)
626
- request('TRACE', uri, query, body, extheader, &block)
627
- end
628
-
629
- # Sends a request to the specified URL.
630
- #
631
- # method:: HTTP method to be sent. method.to_s.upcase is used.
632
- # uri:: a String or an URI object which represents an URL of web resource.
633
- # query:: a Hash or an Array of query part of URL.
634
- # e.g. { "a" => "b" } => 'http://host/part?a=b'
635
- # Give an array to pass multiple value like
636
- # [["a", "b"], ["a", "c"]] => 'http://host/part?a=b&a=c'
637
- # body:: a Hash or an Array of body part.
638
- # e.g. { "a" => "b" } => 'a=b'.
639
- # Give an array to pass multiple value like
640
- # [["a", "b"], ["a", "c"]] => 'a=b&a=c'.
641
- # When the given method is 'POST' and the given body contains a file
642
- # as a value, it will be posted as a multipart/form-data.
643
- # e.g. { 'upload' => file }
644
- # See HTTP::Message.file? for actual condition of 'a file'.
645
- # extheader:: a Hash or an Array of extra headers. e.g.
646
- # { 'Accept' => '*/*' } or
647
- # [['Accept', 'image/jpeg'], ['Accept', 'image/png']].
648
- # &block:: Give a block to get chunked message-body of response like
649
- # get(uri) { |chunked_body| ... }.
650
- # Size of each chunk may not be the same.
651
- #
652
- # You can also pass a String as a body. HTTPClient just sends a String as
653
- # a HTTP request message body.
654
- #
655
- # When you pass an IO as a body, HTTPClient sends it as a HTTP request with
656
- # chunked encoding (Transfer-Encoding: chunked in HTTP header). Bear in mind
657
- # that some server application does not support chunked request. At least
658
- # cgi.rb does not support it.
659
- def request(method, uri, query = nil, body = nil, extheader = {}, &block)
660
- uri = urify(uri)
661
- proxy = no_proxy?(uri) ? nil : @proxy
662
- req = create_request(method, uri, query, body, extheader)
663
- if block
664
- filtered_block = proc { |res, str|
665
- block.call(str)
666
- }
667
- end
668
- do_request(req, proxy, &filtered_block)
669
- end
670
-
671
- # Sends HEAD request in async style. See request_async for arguments.
672
- # It immediately returns a HTTPClient::Connection instance as a result.
673
- def head_async(uri, query = nil, extheader = {})
674
- request_async(:head, uri, query, nil, extheader)
675
- end
676
-
677
- # Sends GET request in async style. See request_async for arguments.
678
- # It immediately returns a HTTPClient::Connection instance as a result.
679
- def get_async(uri, query = nil, extheader = {})
680
- request_async(:get, uri, query, nil, extheader)
681
- end
682
-
683
- # Sends POST request in async style. See request_async for arguments.
684
- # It immediately returns a HTTPClient::Connection instance as a result.
685
- def post_async(uri, body = nil, extheader = {})
686
- request_async(:post, uri, nil, body, extheader)
687
- end
688
-
689
- # Sends PUT request in async style. See request_async for arguments.
690
- # It immediately returns a HTTPClient::Connection instance as a result.
691
- def put_async(uri, body = nil, extheader = {})
692
- request_async(:put, uri, nil, body, extheader)
693
- end
694
-
695
- # Sends DELETE request in async style. See request_async for arguments.
696
- # It immediately returns a HTTPClient::Connection instance as a result.
697
- def delete_async(uri, extheader = {})
698
- request_async(:delete, uri, nil, nil, extheader)
699
- end
700
-
701
- # Sends OPTIONS request in async style. See request_async for arguments.
702
- # It immediately returns a HTTPClient::Connection instance as a result.
703
- def options_async(uri, extheader = {})
704
- request_async(:options, uri, nil, nil, extheader)
705
- end
706
-
707
- # Sends PROPFIND request in async style. See request_async for arguments.
708
- # It immediately returns a HTTPClient::Connection instance as a result.
709
- def propfind_async(uri, extheader = PROPFIND_DEFAULT_EXTHEADER)
710
- request_async(:propfind, uri, nil, nil, extheader)
711
- end
712
-
713
- # Sends PROPPATCH request in async style. See request_async for arguments.
714
- # It immediately returns a HTTPClient::Connection instance as a result.
715
- def proppatch_async(uri, body = nil, extheader = {})
716
- request_async(:proppatch, uri, nil, body, extheader)
717
- end
718
-
719
- # Sends TRACE request in async style. See request_async for arguments.
720
- # It immediately returns a HTTPClient::Connection instance as a result.
721
- def trace_async(uri, query = nil, body = nil, extheader = {})
722
- request_async(:trace, uri, query, body, extheader)
723
- end
724
-
725
- # Sends a request in async style. request method creates new Thread for
726
- # HTTP connection and returns a HTTPClient::Connection instance immediately.
727
- #
728
- # Arguments definition is the same as request.
729
- def request_async(method, uri, query = nil, body = nil, extheader = {})
730
- uri = urify(uri)
731
- proxy = no_proxy?(uri) ? nil : @proxy
732
- req = create_request(method, uri, query, body, extheader)
733
- do_request_async(req, proxy)
734
- end
735
-
736
- # Resets internal session for the given URL. Keep-alive connection for the
737
- # site (host-port pair) is disconnected if exists.
738
- def reset(uri)
739
- uri = urify(uri)
740
- @session_manager.reset(uri)
741
- end
742
-
743
- # Resets all of internal sessions. Keep-alive connections are disconnected.
744
- def reset_all
745
- @session_manager.reset_all
746
- end
747
-
748
- private
749
-
750
- class RetryableResponse < StandardError # :nodoc:
751
- end
752
-
753
- class KeepAliveDisconnected < StandardError # :nodoc:
754
- end
755
-
756
- def do_request(req, proxy, &block)
757
- conn = Connection.new
758
- res = nil
759
- retry_count = @session_manager.protocol_retry_count
760
- while retry_count > 0
761
- begin
762
- protect_keep_alive_disconnected do
763
- do_get_block(req, proxy, conn, &block)
764
- end
765
- res = conn.pop
766
- break
767
- rescue RetryableResponse
768
- res = conn.pop
769
- retry_count -= 1
770
- end
771
- end
772
- res
773
- end
774
-
775
- def do_request_async(req, proxy)
776
- conn = Connection.new
777
- t = Thread.new(conn) { |tconn|
778
- retry_count = @session_manager.protocol_retry_count
779
- while retry_count > 0
780
- begin
781
- protect_keep_alive_disconnected do
782
- do_get_stream(req, proxy, tconn)
783
- end
784
- break
785
- rescue RetryableResponse
786
- retry_count -= 1
787
- end
788
- end
789
- }
790
- conn.async_thread = t
791
- conn
792
- end
793
-
794
- def load_environment
795
- # http_proxy
796
- if getenv('REQUEST_METHOD')
797
- # HTTP_PROXY conflicts with the environment variable usage in CGI where
798
- # HTTP_* is used for HTTP header information. Unlike open-uri, we
799
- # simply ignore http_proxy in CGI env and use cgi_http_proxy instead.
800
- self.proxy = getenv('cgi_http_proxy')
801
- else
802
- self.proxy = getenv('http_proxy')
803
- end
804
- # no_proxy
805
- self.no_proxy = getenv('no_proxy')
806
- end
807
-
808
- def getenv(name)
809
- ENV[name.downcase] || ENV[name.upcase]
810
- end
811
-
812
- def follow_redirect(method, uri, query, body, extheader, &block)
813
- uri = urify(uri)
814
- if block
815
- filtered_block = proc { |r, str|
816
- block.call(str) if HTTP::Status.successful?(r.status)
817
- }
818
- end
819
- if HTTP::Message.file?(body)
820
- pos = body.pos rescue nil
821
- end
822
- retry_number = 0
823
- while retry_number < @follow_redirect_count
824
- body.pos = pos if pos
825
- req = create_request(method, uri, query, body, extheader)
826
- proxy = no_proxy?(req.header.request_uri) ? nil : @proxy
827
- res = do_request(req, proxy, &filtered_block)
828
- if HTTP::Status.successful?(res.status)
829
- return res
830
- elsif HTTP::Status.redirect?(res.status)
831
- uri = urify(@redirect_uri_callback.call(req.header.request_uri, res))
832
- retry_number += 1
833
- else
834
- raise BadResponseError.new("unexpected response: #{res.header.inspect}", res)
835
- end
836
- end
837
- raise BadResponseError.new("retry count exceeded", res)
838
- end
839
-
840
- def protect_keep_alive_disconnected
841
- begin
842
- yield
843
- rescue KeepAliveDisconnected
844
- yield
845
- end
846
- end
847
-
848
- def create_request(method, uri, query, body, extheader)
849
- method = method.to_s.upcase
850
- if extheader.is_a?(Hash)
851
- extheader = extheader.to_a
852
- else
853
- extheader = extheader.dup
854
- end
855
- boundary = nil
856
- if body
857
- dummy, content_type = extheader.find { |key, value|
858
- key.downcase == 'content-type'
859
- }
860
- if content_type
861
- if /\Amultipart/ =~ content_type
862
- if content_type =~ /boundary=(.+)\z/
863
- boundary = $1
864
- else
865
- boundary = create_boundary
866
- content_type = "#{content_type}; boundary=#{boundary}"
867
- extheader = override_header(extheader, 'Content-Type', content_type)
868
- end
869
- end
870
- elsif method == 'POST'
871
- if file_in_form_data?(body)
872
- boundary = create_boundary
873
- content_type = "multipart/form-data; boundary=#{boundary}"
874
- else
875
- content_type = 'application/x-www-form-urlencoded'
876
- end
877
- extheader << ['Content-Type', content_type]
878
- end
879
- end
880
- req = HTTP::Message.new_request(method, uri, query, body, boundary)
881
- extheader.each do |key, value|
882
- req.header.add(key, value)
883
- end
884
- if @cookie_manager && cookie = @cookie_manager.find(uri)
885
- req.header.add('Cookie', cookie)
886
- end
887
- req
888
- end
889
-
890
- def create_boundary
891
- Digest::SHA1.hexdigest(Time.now.to_s)
892
- end
893
-
894
- def file_in_form_data?(body)
895
- HTTP::Message.multiparam_query?(body) &&
896
- body.any? { |k, v| HTTP::Message.file?(v) }
897
- end
898
-
899
- def override_header(extheader, key, value)
900
- result = []
901
- extheader.each do |k, v|
902
- if k.downcase == key.downcase
903
- result << [key, value]
904
- else
905
- result << [k, v]
906
- end
907
- end
908
- result
909
- end
910
-
911
- NO_PROXY_HOSTS = ['localhost']
912
-
913
- def no_proxy?(uri)
914
- if !@proxy or NO_PROXY_HOSTS.include?(uri.host)
915
- return true
916
- end
917
- unless @no_proxy
918
- return false
919
- end
920
- @no_proxy.scan(/([^:,]+)(?::(\d+))?/) do |host, port|
921
- if /(\A|\.)#{Regexp.quote(host)}\z/i =~ uri.host &&
922
- (!port || uri.port == port.to_i)
923
- return true
924
- end
925
- end
926
- false
927
- end
928
-
929
- def https?(uri)
930
- uri.scheme.downcase == 'https'
931
- end
932
-
933
- # !! CAUTION !!
934
- # Method 'do_get*' runs under MT conditon. Be careful to change.
935
- def do_get_block(req, proxy, conn, &block)
936
- @request_filter.each do |filter|
937
- filter.filter_request(req)
938
- end
939
- if str = @test_loopback_response.shift
940
- dump_dummy_request_response(req.body.dump, str) if @debug_dev
941
- conn.push(HTTP::Message.new_response(str))
942
- return
943
- end
944
- content = block ? nil : ''
945
- res = HTTP::Message.new_response(content)
946
- @debug_dev << "= Request\n\n" if @debug_dev
947
- sess = @session_manager.query(req, proxy)
948
- res.peer_cert = sess.ssl_peer_cert
949
- @debug_dev << "\n\n= Response\n\n" if @debug_dev
950
- do_get_header(req, res, sess)
951
- conn.push(res)
952
- sess.get_body do |part|
953
- if block
954
- block.call(res, part)
955
- else
956
- content << part
957
- end
958
- end
959
- @session_manager.keep(sess) unless sess.closed?
960
- commands = @request_filter.collect { |filter|
961
- filter.filter_response(req, res)
962
- }
963
- if commands.find { |command| command == :retry }
964
- raise RetryableResponse.new
965
- end
966
- end
967
-
968
- def do_get_stream(req, proxy, conn)
969
- @request_filter.each do |filter|
970
- filter.filter_request(req)
971
- end
972
- if str = @test_loopback_response.shift
973
- dump_dummy_request_response(req.body.dump, str) if @debug_dev
974
- conn.push(HTTP::Message.new_response(StringIO.new(str)))
975
- return
976
- end
977
- piper, pipew = IO.pipe
978
- res = HTTP::Message.new_response(piper)
979
- @debug_dev << "= Request\n\n" if @debug_dev
980
- sess = @session_manager.query(req, proxy)
981
- res.peer_cert = sess.ssl_peer_cert
982
- @debug_dev << "\n\n= Response\n\n" if @debug_dev
983
- do_get_header(req, res, sess)
984
- conn.push(res)
985
- sess.get_body do |part|
986
- pipew.syswrite(part)
987
- end
988
- pipew.close
989
- @session_manager.keep(sess) unless sess.closed?
990
- commands = @request_filter.collect { |filter|
991
- filter.filter_response(req, res)
992
- }
993
- # ignore commands (not retryable in async mode)
994
- end
995
-
996
- def do_get_header(req, res, sess)
997
- res.version, res.status, res.reason, headers = sess.get_header
998
- headers.each do |key, value|
999
- res.header.add(key, value)
1000
- end
1001
- if @cookie_manager
1002
- res.header['set-cookie'].each do |cookie|
1003
- @cookie_manager.parse(cookie, req.header.request_uri)
1004
- end
1005
-
1006
- # Set the cookie header in the current request so cookies are present in the event we have to retry the
1007
- # request, such as when responding to an authorization challenge.
1008
- if (cookie = @cookie_manager.find(req.header.request_uri)) && req.header['Cookie'].empty?
1009
- req.header.add('Cookie', cookie)
1010
- end
1011
- end
1012
- end
1013
-
1014
- def dump_dummy_request_response(req, res)
1015
- @debug_dev << "= Dummy Request\n\n"
1016
- @debug_dev << req
1017
- @debug_dev << "\n\n= Dummy Response\n\n"
1018
- @debug_dev << res
1019
- end
1020
- end