rs-httpclient 3.0.0.beta1

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.
Files changed (78) 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/cookie.rb +1 -0
  7. data/lib/http-access2/http.rb +1 -0
  8. data/lib/http-access2.rb +55 -0
  9. data/lib/httpclient/auth.rb +924 -0
  10. data/lib/httpclient/cacert.pem +3252 -0
  11. data/lib/httpclient/cacert1024.pem +3866 -0
  12. data/lib/httpclient/connection.rb +88 -0
  13. data/lib/httpclient/cookie.rb +220 -0
  14. data/lib/httpclient/http.rb +1082 -0
  15. data/lib/httpclient/include_client.rb +85 -0
  16. data/lib/httpclient/jruby_ssl_socket.rb +594 -0
  17. data/lib/httpclient/session.rb +960 -0
  18. data/lib/httpclient/ssl_config.rb +452 -0
  19. data/lib/httpclient/ssl_socket.rb +150 -0
  20. data/lib/httpclient/timeout.rb +140 -0
  21. data/lib/httpclient/util.rb +222 -0
  22. data/lib/httpclient/version.rb +3 -0
  23. data/lib/httpclient/webagent-cookie.rb +459 -0
  24. data/lib/httpclient.rb +1331 -0
  25. data/lib/jsonclient.rb +66 -0
  26. data/lib/oauthclient.rb +111 -0
  27. data/sample/async.rb +8 -0
  28. data/sample/auth.rb +11 -0
  29. data/sample/cookie.rb +18 -0
  30. data/sample/dav.rb +103 -0
  31. data/sample/generate_test_keys.rb +99 -0
  32. data/sample/howto.rb +49 -0
  33. data/sample/jsonclient.rb +67 -0
  34. data/sample/oauth_buzz.rb +57 -0
  35. data/sample/oauth_friendfeed.rb +59 -0
  36. data/sample/oauth_twitter.rb +61 -0
  37. data/sample/ssl/0cert.pem +22 -0
  38. data/sample/ssl/0key.pem +30 -0
  39. data/sample/ssl/1000cert.pem +19 -0
  40. data/sample/ssl/1000key.pem +18 -0
  41. data/sample/ssl/htdocs/index.html +10 -0
  42. data/sample/ssl/ssl_client.rb +22 -0
  43. data/sample/ssl/webrick_httpsd.rb +29 -0
  44. data/sample/stream.rb +21 -0
  45. data/sample/thread.rb +27 -0
  46. data/sample/wcat.rb +21 -0
  47. data/test/ca-chain.pem +40 -0
  48. data/test/ca.cert +20 -0
  49. data/test/ca.key +27 -0
  50. data/test/ca.srl +1 -0
  51. data/test/client-pass.key +30 -0
  52. data/test/client.cert +20 -0
  53. data/test/client.key +27 -0
  54. data/test/fixtures/verify.alt.cert +20 -0
  55. data/test/fixtures/verify.foo.cert +20 -0
  56. data/test/fixtures/verify.key +27 -0
  57. data/test/fixtures/verify.localhost.cert +20 -0
  58. data/test/helper.rb +129 -0
  59. data/test/htdigest +1 -0
  60. data/test/htpasswd +2 -0
  61. data/test/jruby_ssl_socket/test_pemutils.rb +32 -0
  62. data/test/runner.rb +2 -0
  63. data/test/server.cert +20 -0
  64. data/test/server.key +27 -0
  65. data/test/sslsvr.rb +65 -0
  66. data/test/subca.cert +20 -0
  67. data/test/subca.key +27 -0
  68. data/test/subca.srl +1 -0
  69. data/test/test_auth.rb +496 -0
  70. data/test/test_cookie.rb +309 -0
  71. data/test/test_hexdump.rb +13 -0
  72. data/test/test_http-access2.rb +516 -0
  73. data/test/test_httpclient.rb +2144 -0
  74. data/test/test_include_client.rb +52 -0
  75. data/test/test_jsonclient.rb +98 -0
  76. data/test/test_ssl.rb +522 -0
  77. data/test/test_webagent-cookie.rb +465 -0
  78. metadata +130 -0
data/lib/httpclient.rb ADDED
@@ -0,0 +1,1331 @@
1
+ # HTTPClient - HTTP client library.
2
+ # Copyright (C) 2000-2015 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 'stringio'
10
+ require 'digest/sha1'
11
+
12
+ # Extra library
13
+ require 'httpclient/version'
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
+ # :main:HTTPClient
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 and get_content.
54
+ #
55
+ # 1. Get content of specified URL. It returns HTTP::Message object and
56
+ # calling 'body' method of it returns a content String.
57
+ #
58
+ # puts clnt.get('http://dev.ctor.org/').body
59
+ #
60
+ # 2. For getting content directly, use get_content. It follows redirect
61
+ # response and returns a String of whole result.
62
+ #
63
+ # puts clnt.get_content('http://dev.ctor.org/')
64
+ #
65
+ # 3. You can pass :follow_redirect option to follow redirect response in get.
66
+ #
67
+ # puts clnt.get('http://dev.ctor.org/', :follow_redirect => true)
68
+ #
69
+ # 4. Get content as chunks of String. It yields chunks of String.
70
+ #
71
+ # clnt.get_content('http://dev.ctor.org/') do |chunk|
72
+ # puts chunk
73
+ # end
74
+ #
75
+ # === Invoking other HTTP methods
76
+ #
77
+ # See head, get, post, put, delete, options, propfind, proppatch and trace.
78
+ # It returns a HTTP::Message instance as a response.
79
+ #
80
+ # 1. Do HEAD request.
81
+ #
82
+ # res = clnt.head(uri)
83
+ # p res.header['Last-Modified'][0]
84
+ #
85
+ # 2. Do GET request with query.
86
+ #
87
+ # query = { 'keyword' => 'ruby', 'lang' => 'en' }
88
+ # res = clnt.get(uri, query)
89
+ # p res.status
90
+ # p res.contenttype
91
+ # p res.header['X-Custom']
92
+ # puts res.body
93
+ #
94
+ # You can also use keyword argument style.
95
+ #
96
+ # res = clnt.get(uri, :query => { :keyword => 'ruby', :lang => 'en' })
97
+ #
98
+ # === How to POST
99
+ #
100
+ # See post.
101
+ #
102
+ # 1. Do POST a form data.
103
+ #
104
+ # body = { 'keyword' => 'ruby', 'lang' => 'en' }
105
+ # res = clnt.post(uri, body)
106
+ #
107
+ # Keyword argument style.
108
+ #
109
+ # res = clnt.post(uri, :body => ...)
110
+ #
111
+ # 2. Do multipart file upload with POST. No need to set extra header by
112
+ # yourself from httpclient/2.1.4.
113
+ #
114
+ # File.open('/tmp/post_data') do |file|
115
+ # body = { 'upload' => file, 'user' => 'nahi' }
116
+ # res = clnt.post(uri, body)
117
+ # end
118
+ #
119
+ # 3. Do multipart with custom body.
120
+ #
121
+ # File.open('/tmp/post_data') do |file|
122
+ # body = [{ 'Content-Type' => 'application/atom+xml; charset=UTF-8',
123
+ # :content => '<entry>...</entry>' },
124
+ # { 'Content-Type' => 'video/mp4',
125
+ # 'Content-Transfer-Encoding' => 'binary',
126
+ # :content => file }]
127
+ # res = clnt.post(uri, body)
128
+ # end
129
+ #
130
+ # === Accessing via SSL
131
+ #
132
+ # Ruby needs to be compiled with OpenSSL.
133
+ #
134
+ # 1. Get content of specified URL via SSL.
135
+ # Just pass an URL which starts with 'https://'.
136
+ #
137
+ # https_url = 'https://www.rsa.com'
138
+ # clnt.get(https_url)
139
+ #
140
+ # 2. Getting peer certificate from response.
141
+ #
142
+ # res = clnt.get(https_url)
143
+ # p res.peer_cert #=> returns OpenSSL::X509::Certificate
144
+ #
145
+ # 3. Configuring OpenSSL options. See HTTPClient::SSLConfig for more details.
146
+ #
147
+ # user_cert_file = 'cert.pem'
148
+ # user_key_file = 'privkey.pem'
149
+ # clnt.ssl_config.set_client_cert_file(user_cert_file, user_key_file)
150
+ # clnt.get(https_url)
151
+ #
152
+ # 4. Revocation check. On JRuby you can set following options to let
153
+ # HTTPClient to perform revocation check with CRL and OCSP:
154
+ #
155
+ # -J-Dcom.sun.security.enableCRLDP=true -J-Dcom.sun.net.ssl.checkRevocation=true
156
+ # ex. jruby -J-Dcom.sun.security.enableCRLDP=true -J-Dcom.sun.net.ssl.checkRevocation=true app.rb
157
+ # Revoked cert example: https://test-sspev.verisign.com:2443/test-SSPEV-revoked-verisign.html
158
+ #
159
+ # On other platform you can download CRL by yourself and set it via
160
+ # SSLConfig#add_crl.
161
+ #
162
+ # === Handling Cookies
163
+ #
164
+ # 1. Using volatile Cookies. Nothing to do. HTTPClient handles Cookies.
165
+ #
166
+ # clnt = HTTPClient.new
167
+ # res = clnt.get(url1) # receives Cookies.
168
+ # res = clnt.get(url2) # sends Cookies if needed.
169
+ # p res.cookies
170
+ #
171
+ # 2. Saving non volatile Cookies to a specified file. Need to set a file at
172
+ # first and invoke save method at last.
173
+ #
174
+ # clnt = HTTPClient.new
175
+ # clnt.set_cookie_store('/home/nahi/cookie.dat')
176
+ # clnt.get(url)
177
+ # ...
178
+ # clnt.save_cookie_store
179
+ #
180
+ # 3. Disabling Cookies.
181
+ #
182
+ # clnt = HTTPClient.new
183
+ # clnt.cookie_manager = nil
184
+ #
185
+ # === Configuring authentication credentials
186
+ #
187
+ # 1. Authentication with Web server. Supports BasicAuth, DigestAuth, and
188
+ # Negotiate/NTLM (requires ruby/ntlm module).
189
+ #
190
+ # clnt = HTTPClient.new
191
+ # domain = 'http://dev.ctor.org/http-access2/'
192
+ # user = 'user'
193
+ # password = 'user'
194
+ # clnt.set_auth(domain, user, password)
195
+ # p clnt.get('http://dev.ctor.org/http-access2/login').status
196
+ #
197
+ # 2. Authentication with Proxy server. Supports BasicAuth and NTLM
198
+ # (requires win32/sspi)
199
+ #
200
+ # clnt = HTTPClient.new(proxy)
201
+ # user = 'proxy'
202
+ # password = 'proxy'
203
+ # clnt.set_proxy_auth(user, password)
204
+ # p clnt.get(url)
205
+ #
206
+ # === Invoking HTTP methods with custom header
207
+ #
208
+ # Pass a Hash or an Array for header argument.
209
+ #
210
+ # header = { 'Accept' => 'text/html' }
211
+ # clnt.get(uri, query, header)
212
+ #
213
+ # header = [['Accept', 'image/jpeg'], ['Accept', 'image/png']]
214
+ # clnt.get_content(uri, query, header)
215
+ #
216
+ # === Invoking HTTP methods asynchronously
217
+ #
218
+ # See head_async, get_async, post_async, put_async, delete_async,
219
+ # options_async, propfind_async, proppatch_async, and trace_async.
220
+ # It immediately returns a HTTPClient::Connection instance as a returning value.
221
+ #
222
+ # connection = clnt.post_async(url, body)
223
+ # print 'posting.'
224
+ # while true
225
+ # break if connection.finished?
226
+ # print '.'
227
+ # sleep 1
228
+ # end
229
+ # puts '.'
230
+ # res = connection.pop
231
+ # p res.status
232
+ # p res.body.read # res.body is an IO for the res of async method.
233
+ #
234
+ # === Shortcut methods
235
+ #
236
+ # You can invoke get_content, get, etc. without creating HTTPClient instance.
237
+ #
238
+ # ruby -rhttpclient -e 'puts HTTPClient.get_content(ARGV.shift)' http://dev.ctor.org/
239
+ # ruby -rhttpclient -e 'p HTTPClient.head(ARGV.shift).header["last-modified"]' http://dev.ctor.org/
240
+ #
241
+ class HTTPClient
242
+ RUBY_VERSION_STRING = "ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE})"
243
+ LIB_NAME = "(#{VERSION}, #{RUBY_VERSION_STRING})"
244
+
245
+ include Util
246
+
247
+ # Raised for indicating running environment configuration error for example
248
+ # accessing via SSL under the ruby which is not compiled with OpenSSL.
249
+ class ConfigurationError < StandardError
250
+ end
251
+
252
+ # Raised for indicating HTTP response error.
253
+ class BadResponseError < RuntimeError
254
+ # HTTP::Message:: a response
255
+ attr_reader :res
256
+
257
+ def initialize(msg, res = nil) # :nodoc:
258
+ super(msg)
259
+ @res = res
260
+ end
261
+ end
262
+
263
+ # Raised for indicating a timeout error.
264
+ class TimeoutError < RuntimeError
265
+ end
266
+
267
+ # Raised for indicating a connection timeout error.
268
+ # You can configure connection timeout via HTTPClient#connect_timeout=.
269
+ class ConnectTimeoutError < TimeoutError
270
+ end
271
+
272
+ # Raised for indicating a request sending timeout error.
273
+ # You can configure request sending timeout via HTTPClient#send_timeout=.
274
+ class SendTimeoutError < TimeoutError
275
+ end
276
+
277
+ # Raised for indicating a response receiving timeout error.
278
+ # You can configure response receiving timeout via
279
+ # HTTPClient#receive_timeout=.
280
+ class ReceiveTimeoutError < TimeoutError
281
+ end
282
+
283
+ # Deprecated. just for backward compatibility
284
+ class Session
285
+ BadResponse = ::HTTPClient::BadResponseError
286
+ end
287
+
288
+ class << self
289
+ %w(get_content post_content head get post put delete options propfind proppatch trace).each do |name|
290
+ eval <<-EOD
291
+ def #{name}(*arg, &block)
292
+ clnt = new
293
+ begin
294
+ clnt.#{name}(*arg, &block)
295
+ ensure
296
+ clnt.reset_all
297
+ end
298
+ end
299
+ EOD
300
+ end
301
+
302
+ private
303
+
304
+ def attr_proxy(symbol, assignable = false)
305
+ name = symbol.to_s
306
+ define_method(name) {
307
+ @session_manager.__send__(name)
308
+ }
309
+ if assignable
310
+ aname = name + '='
311
+ define_method(aname) { |rhs|
312
+ @session_manager.__send__(aname, rhs)
313
+ }
314
+ end
315
+ end
316
+ end
317
+
318
+ # HTTPClient::SSLConfig:: SSL configurator.
319
+ attr_reader :ssl_config
320
+ # HTTPClient::CookieManager:: Cookies configurator.
321
+ attr_accessor :cookie_manager
322
+ # An array of response HTTP message body String which is used for loop-back
323
+ # test. See test/* to see how to use it. If you want to do loop-back test
324
+ # of HTTP header, use test_loopback_http_response instead.
325
+ attr_reader :test_loopback_response
326
+ # An array of request filter which can trap HTTP request/response.
327
+ # See HTTPClient::WWWAuth to see how to use it.
328
+ attr_reader :request_filter
329
+ # HTTPClient::ProxyAuth:: Proxy authentication handler.
330
+ attr_reader :proxy_auth
331
+ # HTTPClient::WWWAuth:: WWW authentication handler.
332
+ attr_reader :www_auth
333
+ # How many times get_content and post_content follows HTTP redirect.
334
+ # 10 by default.
335
+ attr_accessor :follow_redirect_count
336
+ # Base url of resources.
337
+ attr_accessor :base_url
338
+ # Default request header.
339
+ attr_accessor :default_header
340
+
341
+ # Set HTTP version as a String:: 'HTTP/1.0' or 'HTTP/1.1'
342
+ attr_proxy(:protocol_version, true)
343
+ # Connect timeout in sec.
344
+ attr_proxy(:connect_timeout, true)
345
+ # Request sending timeout in sec.
346
+ attr_proxy(:send_timeout, true)
347
+ # Response receiving timeout in sec.
348
+ attr_proxy(:receive_timeout, true)
349
+ # Reuse the same connection within this timeout in sec. from last used.
350
+ attr_proxy(:keep_alive_timeout, true)
351
+ # Size of reading block for non-chunked response.
352
+ attr_proxy(:read_block_size, true)
353
+ # Negotiation retry count for authentication. 5 by default.
354
+ attr_proxy(:protocol_retry_count, true)
355
+ # if your ruby is older than 2005-09-06, do not set socket_sync = false to
356
+ # avoid an SSL socket blocking bug in openssl/buffering.rb.
357
+ attr_proxy(:socket_sync, true)
358
+ # Enables TCP keepalive; no timing settings exist at present
359
+ attr_proxy(:tcp_keepalive, true)
360
+ # User-Agent header in HTTP request.
361
+ attr_proxy(:agent_name, true)
362
+ # From header in HTTP request.
363
+ attr_proxy(:from, true)
364
+ # An array of response HTTP String (not a HTTP message body) which is used
365
+ # for loopback test. See test/* to see how to use it.
366
+ attr_proxy(:test_loopback_http_response)
367
+ # Decompress a compressed (with gzip or deflate) content body transparently. false by default.
368
+ attr_proxy(:transparent_gzip_decompression, true)
369
+ # Raise BadResponseError if response size does not match with Content-Length header in response. false by default.
370
+ # TODO: enable by default
371
+ attr_proxy(:strict_response_size_check, true)
372
+ # Local socket address. Set HTTPClient#socket_local.host and HTTPClient#socket_local.port to specify local binding hostname and port of TCP socket.
373
+ attr_proxy(:socket_local, true)
374
+
375
+ # Default header for PROPFIND request.
376
+ PROPFIND_DEFAULT_EXTHEADER = { 'Depth' => '0' }
377
+
378
+ # Default User-Agent header
379
+ DEFAULT_AGENT_NAME = 'HTTPClient/1.0'
380
+
381
+ # Creates a HTTPClient instance which manages sessions, cookies, etc.
382
+ #
383
+ # HTTPClient.new takes optional arguments as a Hash.
384
+ # * :proxy - proxy url string
385
+ # * :agent_name - User-Agent String
386
+ # * :from - from header String
387
+ # * :base_url - base URL of resources
388
+ # * :default_header - header Hash all HTTP requests should have
389
+ # * :force_basic_auth - flag for sending Authorization header w/o gettin 401 first
390
+ # User-Agent and From are embedded in HTTP request Header if given.
391
+ # From header is not set without setting it explicitly.
392
+ #
393
+ # proxy = 'http://myproxy:8080'
394
+ # agent_name = 'MyAgent/0.1'
395
+ # from = 'from@example.com'
396
+ # HTTPClient.new(proxy, agent_name, from)
397
+ #
398
+ # After you set :base_url, all resources you pass to get, post and other
399
+ # methods are recognized to be prefixed with base_url. Say base_url is
400
+ # 'https://api.example.com/v1/, get('users') is the same as
401
+ # get('https://api.example.com/v1/users') internally. You can also pass
402
+ # full URL from 'http://' even after setting base_url.
403
+ #
404
+ # The expected base_url and path behavior is the following. Please take
405
+ # care of '/' in base_url and path.
406
+ #
407
+ # The last '/' is important for base_url:
408
+ # 1. http://foo/bar/baz/ + path -> http://foo/bar/baz/path
409
+ # 2. http://foo/bar/baz + path -> http://foo/bar/path
410
+ # Relative path handling:
411
+ # 3. http://foo/bar/baz/ + ../path -> http://foo/bar/path
412
+ # 4. http://foo/bar/baz + ../path -> http://foo/path
413
+ # 5. http://foo/bar/baz/ + ./path -> http://foo/bar/baz/path
414
+ # 6. http://foo/bar/baz + ./path -> http://foo/bar/path
415
+ # The leading '/' of path means absolute path:
416
+ # 7. http://foo/bar/baz/ + /path -> http://foo/path
417
+ # 8. http://foo/bar/baz + /path -> http://foo/path
418
+ #
419
+ # :default_header is for providing default headers Hash that all HTTP
420
+ # requests should have, such as custom 'Authorization' header in API.
421
+ # You can override :default_header with :header Hash parameter in HTTP
422
+ # request methods.
423
+ #
424
+ # :force_basic_auth turns on/off the BasicAuth force flag. Generally
425
+ # HTTP client must send Authorization header after it gets 401 error
426
+ # from server from security reason. But in some situation (e.g. API
427
+ # client) you might want to send Authorization from the beginning.
428
+ def initialize(*args, &block)
429
+ proxy, agent_name, from, base_url, default_header, force_basic_auth =
430
+ keyword_argument(args, :proxy, :agent_name, :from, :base_url, :default_header, :force_basic_auth)
431
+ @proxy = nil # assigned later.
432
+ @no_proxy = nil
433
+ @no_proxy_regexps = []
434
+ @base_url = base_url
435
+ @default_header = default_header || {}
436
+ @www_auth = WWWAuth.new
437
+ @proxy_auth = ProxyAuth.new
438
+ @www_auth.basic_auth.force_auth = @proxy_auth.basic_auth.force_auth = force_basic_auth
439
+ @request_filter = [@proxy_auth, @www_auth]
440
+ @debug_dev = nil
441
+ @redirect_uri_callback = method(:default_redirect_uri_callback)
442
+ @test_loopback_response = []
443
+ @session_manager = SessionManager.new(self)
444
+ @session_manager.agent_name = agent_name || DEFAULT_AGENT_NAME
445
+ @session_manager.from = from
446
+ @session_manager.ssl_config = @ssl_config = SSLConfig.new(self)
447
+ @cookie_manager = CookieManager.new
448
+ @follow_redirect_count = 10
449
+ load_environment
450
+ self.proxy = proxy if proxy
451
+ keep_webmock_compat
452
+ instance_eval(&block) if block
453
+ end
454
+
455
+ # webmock 1.6.2 depends on HTTP::Message#body.content to work.
456
+ # let's keep it work iif webmock is loaded for a while.
457
+ def keep_webmock_compat
458
+ if respond_to?(:do_get_block_with_webmock)
459
+ ::HTTP::Message.module_eval do
460
+ def body
461
+ def (o = self.content).content
462
+ self
463
+ end
464
+ o
465
+ end
466
+ end
467
+ end
468
+ end
469
+
470
+ # Returns debug device if exists. See debug_dev=.
471
+ def debug_dev
472
+ @debug_dev
473
+ end
474
+
475
+ # Sets debug device. Once debug device is set, all HTTP requests and
476
+ # responses are dumped to given device. dev must respond to << for dump.
477
+ #
478
+ # Calling this method resets all existing sessions.
479
+ def debug_dev=(dev)
480
+ @debug_dev = dev
481
+ reset_all
482
+ @session_manager.debug_dev = dev
483
+ end
484
+
485
+ # Returns URI object of HTTP proxy if exists.
486
+ def proxy
487
+ @proxy
488
+ end
489
+
490
+ # Sets HTTP proxy used for HTTP connection. Given proxy can be an URI,
491
+ # a String or nil. You can set user/password for proxy authentication like
492
+ # HTTPClient#proxy = 'http://user:passwd@myproxy:8080'
493
+ #
494
+ # You can use environment variable 'http_proxy' or 'HTTP_PROXY' for it.
495
+ # You need to use 'cgi_http_proxy' or 'CGI_HTTP_PROXY' instead if you run
496
+ # HTTPClient from CGI environment from security reason. (HTTPClient checks
497
+ # 'REQUEST_METHOD' environment variable whether it's CGI or not)
498
+ #
499
+ # Calling this method resets all existing sessions.
500
+ def proxy=(proxy)
501
+ if proxy.nil? || proxy.to_s.empty?
502
+ @proxy = nil
503
+ @proxy_auth.reset_challenge
504
+ else
505
+ @proxy = urify(proxy)
506
+ if @proxy.scheme == nil or @proxy.scheme.downcase != 'http' or
507
+ @proxy.host == nil or @proxy.port == nil
508
+ raise ArgumentError.new("unsupported proxy #{proxy}")
509
+ end
510
+ @proxy_auth.reset_challenge
511
+ if @proxy.user || @proxy.password
512
+ @proxy_auth.set_auth(@proxy.user, @proxy.password)
513
+ end
514
+ end
515
+ reset_all
516
+ @session_manager.proxy = @proxy
517
+ @proxy
518
+ end
519
+
520
+ # Returns NO_PROXY setting String if given.
521
+ def no_proxy
522
+ @no_proxy
523
+ end
524
+
525
+ # Sets NO_PROXY setting String. no_proxy must be a comma separated String.
526
+ # Each entry must be 'host' or 'host:port' such as;
527
+ # HTTPClient#no_proxy = 'example.com,example.co.jp:443'
528
+ #
529
+ # 'localhost' is treated as a no_proxy site regardless of explicitly listed.
530
+ # HTTPClient checks given URI objects before accessing it.
531
+ # 'host' is tail string match. No IP-addr conversion.
532
+ #
533
+ # You can use environment variable 'no_proxy' or 'NO_PROXY' for it.
534
+ #
535
+ # Calling this method resets all existing sessions.
536
+ def no_proxy=(no_proxy)
537
+ @no_proxy = no_proxy
538
+ @no_proxy_regexps.clear
539
+ if @no_proxy
540
+ @no_proxy.scan(/([^\s:,]+)(?::(\d+))?/) do |host, port|
541
+ if host[0] == ?.
542
+ regexp = /#{Regexp.quote(host)}\z/i
543
+ else
544
+ regexp = /(\A|\.)#{Regexp.quote(host)}\z/i
545
+ end
546
+ @no_proxy_regexps << [regexp, port]
547
+ end
548
+ end
549
+ reset_all
550
+ end
551
+
552
+ # Sets credential for Web server authentication.
553
+ # domain:: a String or an URI to specify where HTTPClient should use this
554
+ # credential. If you set uri to nil, HTTPClient uses this credential
555
+ # wherever a server requires it.
556
+ # user:: username String.
557
+ # passwd:: password String.
558
+ #
559
+ # You can set multiple credentials for each uri.
560
+ #
561
+ # clnt.set_auth('http://www.example.com/foo/', 'foo_user', 'passwd')
562
+ # clnt.set_auth('http://www.example.com/bar/', 'bar_user', 'passwd')
563
+ #
564
+ # Calling this method resets all existing sessions.
565
+ def set_auth(domain, user, passwd)
566
+ uri = to_resource_url(domain)
567
+ @www_auth.set_auth(uri, user, passwd)
568
+ reset_all
569
+ end
570
+
571
+ # Deprecated. Use set_auth instead.
572
+ def set_basic_auth(domain, user, passwd)
573
+ uri = to_resource_url(domain)
574
+ @www_auth.basic_auth.set(uri, user, passwd)
575
+ reset_all
576
+ end
577
+
578
+ # Sets credential for Proxy authentication.
579
+ # user:: username String.
580
+ # passwd:: password String.
581
+ #
582
+ # Calling this method resets all existing sessions.
583
+ def set_proxy_auth(user, passwd)
584
+ @proxy_auth.set_auth(user, passwd)
585
+ reset_all
586
+ end
587
+
588
+ # Turn on/off the BasicAuth force flag. Generally HTTP client must
589
+ # send Authorization header after it gets 401 error from server from
590
+ # security reason. But in some situation (e.g. API client) you might
591
+ # want to send Authorization from the beginning.
592
+ def force_basic_auth=(force_basic_auth)
593
+ @www_auth.basic_auth.force_auth = @proxy_auth.basic_auth.force_auth = force_basic_auth
594
+ end
595
+
596
+ # Sets the filename where non-volatile Cookies be saved by calling
597
+ # save_cookie_store.
598
+ # This method tries to load and managing Cookies from the specified file.
599
+ #
600
+ # Calling this method resets all existing sessions.
601
+ def set_cookie_store(filename)
602
+ @cookie_manager.cookies_file = filename
603
+ @cookie_manager.load_cookies if filename
604
+ reset_all
605
+ end
606
+
607
+ # Try to save Cookies to the file specified in set_cookie_store. Unexpected
608
+ # error will be raised if you don't call set_cookie_store first.
609
+ def save_cookie_store
610
+ @cookie_manager.save_cookies
611
+ end
612
+
613
+ # Returns stored cookies.
614
+ def cookies
615
+ if @cookie_manager
616
+ @cookie_manager.cookies
617
+ end
618
+ end
619
+
620
+ # Sets callback proc when HTTP redirect status is returned for get_content
621
+ # and post_content. default_redirect_uri_callback is used by default.
622
+ #
623
+ # If you need strict implementation which does not allow relative URI
624
+ # redirection, set strict_redirect_uri_callback instead.
625
+ #
626
+ # clnt.redirect_uri_callback = clnt.method(:strict_redirect_uri_callback)
627
+ #
628
+ def redirect_uri_callback=(redirect_uri_callback)
629
+ @redirect_uri_callback = redirect_uri_callback
630
+ end
631
+
632
+ # Retrieves a web resource.
633
+ #
634
+ # uri:: a String or an URI object which represents an URL of web resource.
635
+ # query:: a Hash or an Array of query part of URL.
636
+ # e.g. { "a" => "b" } => 'http://host/part?a=b'.
637
+ # Give an array to pass multiple value like
638
+ # [["a", "b"], ["a", "c"]] => 'http://host/part?a=b&a=c'.
639
+ # header:: a Hash or an Array of extra headers. e.g.
640
+ # { 'Accept' => 'text/html' } or
641
+ # [['Accept', 'image/jpeg'], ['Accept', 'image/png']].
642
+ # &block:: Give a block to get chunked message-body of response like
643
+ # get_content(uri) { |chunked_body| ... }.
644
+ # Size of each chunk may not be the same.
645
+ #
646
+ # get_content follows HTTP redirect status (see HTTP::Status.redirect?)
647
+ # internally and try to retrieve content from redirected URL. See
648
+ # redirect_uri_callback= how HTTP redirection is handled.
649
+ #
650
+ # If you need to get full HTTP response including HTTP status and headers,
651
+ # use get method. get returns HTTP::Message as a response and you need to
652
+ # follow HTTP redirect by yourself if you need.
653
+ def get_content(uri, *args, &block)
654
+ query, header = keyword_argument(args, :query, :header)
655
+ success_content(follow_redirect(:get, uri, query, nil, header || {}, &block))
656
+ end
657
+
658
+ # Posts a content.
659
+ #
660
+ # uri:: a String or an URI object which represents an URL of web resource.
661
+ # body:: a Hash or an Array of body part. e.g.
662
+ # { "a" => "b" } => 'a=b'
663
+ # Give an array to pass multiple value like
664
+ # [["a", "b"], ["a", "c"]] => 'a=b&a=c'
665
+ # When you pass a File as a value, it will be posted as a
666
+ # multipart/form-data. e.g.
667
+ # { 'upload' => file }
668
+ # You can also send custom multipart by passing an array of hashes.
669
+ # Each part must have a :content attribute which can be a file, all
670
+ # other keys will become headers.
671
+ # [{ 'Content-Type' => 'text/plain', :content => "some text" },
672
+ # { 'Content-Type' => 'video/mp4', :content => File.new('video.mp4') }]
673
+ # => <Two parts with custom Content-Type header>
674
+ # header:: a Hash or an Array of extra headers. e.g.
675
+ # { 'Accept' => 'text/html' }
676
+ # or
677
+ # [['Accept', 'image/jpeg'], ['Accept', 'image/png']].
678
+ # &block:: Give a block to get chunked message-body of response like
679
+ # post_content(uri) { |chunked_body| ... }.
680
+ # Size of each chunk may not be the same.
681
+ #
682
+ # post_content follows HTTP redirect status (see HTTP::Status.redirect?)
683
+ # internally and try to post the content to redirected URL. See
684
+ # redirect_uri_callback= how HTTP redirection is handled.
685
+ # Bear in mind that you should not depend on post_content because it sends
686
+ # the same POST method to the new location which is prohibited in HTTP spec.
687
+ #
688
+ # If you need to get full HTTP response including HTTP status and headers,
689
+ # use post method.
690
+ def post_content(uri, *args, &block)
691
+ if hashy_argument_has_keys(args, :query, :body)
692
+ query, body, header = keyword_argument(args, :query, :body, :header)
693
+ else
694
+ query = nil
695
+ body, header = keyword_argument(args, :body, :header)
696
+ end
697
+ success_content(follow_redirect(:post, uri, query, body, header || {}, &block))
698
+ end
699
+
700
+ # A method for redirect uri callback. How to use:
701
+ # clnt.redirect_uri_callback = clnt.method(:strict_redirect_uri_callback)
702
+ # This callback does not allow relative redirect such as
703
+ # Location: ../foo/
704
+ # in HTTP header. (raises BadResponseError instead)
705
+ def strict_redirect_uri_callback(uri, res)
706
+ newuri = urify(res.header['location'][0])
707
+ if https?(uri) && !https?(newuri)
708
+ raise BadResponseError.new("redirecting to non-https resource")
709
+ end
710
+ if !http?(newuri) && !https?(newuri)
711
+ raise BadResponseError.new("unexpected location: #{newuri}", res)
712
+ end
713
+ puts "redirect to: #{newuri}" if $DEBUG
714
+ newuri
715
+ end
716
+
717
+ # A default method for redirect uri callback. This method is used by
718
+ # HTTPClient instance by default.
719
+ # This callback allows relative redirect such as
720
+ # Location: ../foo/
721
+ # in HTTP header.
722
+ def default_redirect_uri_callback(uri, res)
723
+ newuri = urify(res.header['location'][0])
724
+ if !http?(newuri) && !https?(newuri)
725
+ warn("#{newuri}: a relative URI in location header which is not recommended")
726
+ warn("'The field value consists of a single absolute URI' in HTTP spec")
727
+ newuri = uri + newuri
728
+ end
729
+ if https?(uri) && !https?(newuri)
730
+ raise BadResponseError.new("redirecting to non-https resource")
731
+ end
732
+ puts "redirect to: #{newuri}" if $DEBUG
733
+ newuri
734
+ end
735
+
736
+ # Sends HEAD request to the specified URL. See request for arguments.
737
+ def head(uri, *args)
738
+ request(:head, uri, argument_to_hash(args, :query, :header, :follow_redirect))
739
+ end
740
+
741
+ # Sends GET request to the specified URL. See request for arguments.
742
+ def get(uri, *args, &block)
743
+ request(:get, uri, argument_to_hash(args, :query, :header, :follow_redirect), &block)
744
+ end
745
+
746
+ # Sends PATCH request to the specified URL. See request for arguments.
747
+ def patch(uri, *args, &block)
748
+ if hashy_argument_has_keys(args, :query, :body)
749
+ new_args = args[0]
750
+ else
751
+ new_args = argument_to_hash(args, :body, :header)
752
+ end
753
+ request(:patch, uri, new_args, &block)
754
+ end
755
+
756
+ # :call-seq:
757
+ # post(uri, {query: query, body: body, header: header, follow_redirect: follow_redirect}) -> HTTP::Message
758
+ # post(uri, body, header, follow_redirect) -> HTTP::Message
759
+ #
760
+ # Sends POST request to the specified URL. See request for arguments.
761
+ # You should not depend on :follow_redirect => true for POST method. It
762
+ # sends the same POST method to the new location which is prohibited in HTTP spec.
763
+ def post(uri, *args, &block)
764
+ if hashy_argument_has_keys(args, :query, :body)
765
+ new_args = args[0]
766
+ else
767
+ new_args = argument_to_hash(args, :body, :header, :follow_redirect)
768
+ end
769
+ request(:post, uri, new_args, &block)
770
+ end
771
+
772
+ # Sends PUT request to the specified URL. See request for arguments.
773
+ def put(uri, *args, &block)
774
+ if hashy_argument_has_keys(args, :query, :body)
775
+ new_args = args[0]
776
+ else
777
+ new_args = argument_to_hash(args, :body, :header)
778
+ end
779
+ request(:put, uri, new_args, &block)
780
+ end
781
+
782
+ # Sends DELETE request to the specified URL. See request for arguments.
783
+ def delete(uri, *args, &block)
784
+ request(:delete, uri, argument_to_hash(args, :body, :header, :query), &block)
785
+ end
786
+
787
+ # Sends OPTIONS request to the specified URL. See request for arguments.
788
+ def options(uri, *args, &block)
789
+ new_args = argument_to_hash(args, :header, :query, :body)
790
+ request(:options, uri, new_args, &block)
791
+ end
792
+
793
+ # Sends PROPFIND request to the specified URL. See request for arguments.
794
+ def propfind(uri, *args, &block)
795
+ request(:propfind, uri, argument_to_hash(args, :header), &block)
796
+ end
797
+
798
+ # Sends PROPPATCH request to the specified URL. See request for arguments.
799
+ def proppatch(uri, *args, &block)
800
+ request(:proppatch, uri, argument_to_hash(args, :body, :header), &block)
801
+ end
802
+
803
+ # Sends TRACE request to the specified URL. See request for arguments.
804
+ def trace(uri, *args, &block)
805
+ request('TRACE', uri, argument_to_hash(args, :query, :header), &block)
806
+ end
807
+
808
+ # Sends a request to the specified URL.
809
+ #
810
+ # method:: HTTP method to be sent. method.to_s.upcase is used.
811
+ # uri:: a String or an URI object which represents an URL of web resource.
812
+ # query:: a Hash or an Array of query part of URL.
813
+ # e.g. { "a" => "b" } => 'http://host/part?a=b'
814
+ # Give an array to pass multiple value like
815
+ # [["a", "b"], ["a", "c"]] => 'http://host/part?a=b&a=c'
816
+ # body:: a Hash or an Array of body part. e.g.
817
+ # { "a" => "b" }
818
+ # => 'a=b'
819
+ # Give an array to pass multiple value like
820
+ # [["a", "b"], ["a", "c"]]
821
+ # => 'a=b&a=c'.
822
+ # When the given method is 'POST' and the given body contains a file
823
+ # as a value, it will be posted as a multipart/form-data. e.g.
824
+ # { 'upload' => file }
825
+ # You can also send custom multipart by passing an array of hashes.
826
+ # Each part must have a :content attribute which can be a file, all
827
+ # other keys will become headers.
828
+ # [{ 'Content-Type' => 'text/plain', :content => "some text" },
829
+ # { 'Content-Type' => 'video/mp4', :content => File.new('video.mp4') }]
830
+ # => <Two parts with custom Content-Type header>
831
+ # See HTTP::Message.file? for actual condition of 'a file'.
832
+ # header:: a Hash or an Array of extra headers. e.g.
833
+ # { 'Accept' => 'text/html' } or
834
+ # [['Accept', 'image/jpeg'], ['Accept', 'image/png']].
835
+ # &block:: Give a block to get chunked message-body of response like
836
+ # get(uri) { |chunked_body| ... }.
837
+ # Size of each chunk may not be the same.
838
+ #
839
+ # You can also pass a String as a body. HTTPClient just sends a String as
840
+ # a HTTP request message body.
841
+ #
842
+ # When you pass an IO as a body, HTTPClient sends it as a HTTP request with
843
+ # chunked encoding (Transfer-Encoding: chunked in HTTP header) if IO does not
844
+ # respond to :size. Bear in mind that some server application does not support
845
+ # chunked request. At least cgi.rb does not support it.
846
+ def request(method, uri, *args, &block)
847
+ query, body, header, follow_redirect = keyword_argument(args, :query, :body, :header, :follow_redirect)
848
+ if method == :propfind
849
+ header ||= PROPFIND_DEFAULT_EXTHEADER
850
+ else
851
+ header ||= {}
852
+ end
853
+ uri = to_resource_url(uri)
854
+ if block
855
+ filtered_block = adapt_block(&block)
856
+ end
857
+ if follow_redirect
858
+ follow_redirect(method, uri, query, body, header, &block)
859
+ else
860
+ do_request(method, uri, query, body, header, &filtered_block)
861
+ end
862
+ end
863
+
864
+ # Sends HEAD request in async style. See request_async for arguments.
865
+ # It immediately returns a HTTPClient::Connection instance as a result.
866
+ def head_async(uri, *args)
867
+ request_async2(:head, uri, argument_to_hash(args, :query, :header))
868
+ end
869
+
870
+ # Sends GET request in async style. See request_async for arguments.
871
+ # It immediately returns a HTTPClient::Connection instance as a result.
872
+ def get_async(uri, *args)
873
+ request_async2(:get, uri, argument_to_hash(args, :query, :header))
874
+ end
875
+
876
+ # Sends PATCH request in async style. See request_async2 for arguments.
877
+ # It immediately returns a HTTPClient::Connection instance as a result.
878
+ def patch_async(uri, *args)
879
+ if hashy_argument_has_keys(args, :query, :body)
880
+ new_args = args[0]
881
+ else
882
+ new_args = argument_to_hash(args, :body, :header)
883
+ end
884
+ request_async2(:patch, uri, new_args)
885
+ end
886
+
887
+ # Sends POST request in async style. See request_async for arguments.
888
+ # It immediately returns a HTTPClient::Connection instance as a result.
889
+ def post_async(uri, *args)
890
+ if hashy_argument_has_keys(args, :query, :body)
891
+ new_args = args[0]
892
+ else
893
+ new_args = argument_to_hash(args, :body, :header)
894
+ end
895
+ request_async2(:post, uri, new_args)
896
+ end
897
+
898
+ # Sends PUT request in async style. See request_async2 for arguments.
899
+ # It immediately returns a HTTPClient::Connection instance as a result.
900
+ def put_async(uri, *args)
901
+ if hashy_argument_has_keys(args, :query, :body)
902
+ new_args = args[0]
903
+ else
904
+ new_args = argument_to_hash(args, :body, :header)
905
+ end
906
+ request_async2(:put, uri, new_args)
907
+ end
908
+
909
+ # Sends DELETE request in async style. See request_async2 for arguments.
910
+ # It immediately returns a HTTPClient::Connection instance as a result.
911
+ def delete_async(uri, *args)
912
+ request_async2(:delete, uri, argument_to_hash(args, :body, :header, :query))
913
+ end
914
+
915
+ # Sends OPTIONS request in async style. See request_async2 for arguments.
916
+ # It immediately returns a HTTPClient::Connection instance as a result.
917
+ def options_async(uri, *args)
918
+ request_async2(:options, uri, argument_to_hash(args, :header, :query, :body))
919
+ end
920
+
921
+ # Sends PROPFIND request in async style. See request_async2 for arguments.
922
+ # It immediately returns a HTTPClient::Connection instance as a result.
923
+ def propfind_async(uri, *args)
924
+ request_async2(:propfind, uri, argument_to_hash(args, :body, :header))
925
+ end
926
+
927
+ # Sends PROPPATCH request in async style. See request_async2 for arguments.
928
+ # It immediately returns a HTTPClient::Connection instance as a result.
929
+ def proppatch_async(uri, *args)
930
+ request_async2(:proppatch, uri, argument_to_hash(args, :body, :header))
931
+ end
932
+
933
+ # Sends TRACE request in async style. See request_async2 for arguments.
934
+ # It immediately returns a HTTPClient::Connection instance as a result.
935
+ def trace_async(uri, *args)
936
+ request_async2(:trace, uri, argument_to_hash(args, :query, :header))
937
+ end
938
+
939
+ # Sends a request in async style. request method creates new Thread for
940
+ # HTTP connection and returns a HTTPClient::Connection instance immediately.
941
+ #
942
+ # Arguments definition is the same as request.
943
+ def request_async(method, uri, query = nil, body = nil, header = {})
944
+ uri = to_resource_url(uri)
945
+ do_request_async(method, uri, query, body, header)
946
+ end
947
+
948
+ # new method that has same signature as 'request'
949
+ def request_async2(method, uri, *args)
950
+ query, body, header = keyword_argument(args, :query, :body, :header)
951
+ if [:post, :put].include?(method)
952
+ body ||= ''
953
+ end
954
+ if method == :propfind
955
+ header ||= PROPFIND_DEFAULT_EXTHEADER
956
+ else
957
+ header ||= {}
958
+ end
959
+ uri = to_resource_url(uri)
960
+ do_request_async(method, uri, query, body, header)
961
+ end
962
+
963
+ # Resets internal session for the given URL. Keep-alive connection for the
964
+ # site (host-port pair) is disconnected if exists.
965
+ def reset(uri)
966
+ uri = to_resource_url(uri)
967
+ @session_manager.reset(uri)
968
+ end
969
+
970
+ # Resets all of internal sessions. Keep-alive connections are disconnected.
971
+ def reset_all
972
+ @session_manager.reset_all
973
+ end
974
+
975
+ private
976
+
977
+ class RetryableResponse < StandardError # :nodoc:
978
+ attr_reader :res
979
+
980
+ def initialize(res = nil)
981
+ @res = res
982
+ end
983
+ end
984
+
985
+ class KeepAliveDisconnected < StandardError # :nodoc:
986
+ attr_reader :sess
987
+ attr_reader :cause
988
+ def initialize(sess = nil, cause = nil)
989
+ super("#{self.class.name}: #{cause ? cause.message : nil}")
990
+ @sess = sess
991
+ @cause = cause
992
+ end
993
+ end
994
+
995
+ def hashy_argument_has_keys(args, *key)
996
+ # if the given arg is a single Hash and...
997
+ args.size == 1 and Hash === args[0] and
998
+ # it has any one of the key
999
+ key.all? { |e| args[0].key?(e) }
1000
+ end
1001
+
1002
+ def do_request(method, uri, query, body, header, &block)
1003
+ res = nil
1004
+ if HTTP::Message.file?(body)
1005
+ pos = body.pos rescue nil
1006
+ end
1007
+ retry_count = @session_manager.protocol_retry_count
1008
+ proxy = no_proxy?(uri) ? nil : @proxy
1009
+ previous_request = previous_response = nil
1010
+ while retry_count > 0
1011
+ body.pos = pos if pos
1012
+ req = create_request(method, uri, query, body, header)
1013
+ if previous_request
1014
+ # to remember IO positions to read
1015
+ req.http_body.positions = previous_request.http_body.positions
1016
+ end
1017
+ begin
1018
+ protect_keep_alive_disconnected do
1019
+ # TODO: remove Connection.new
1020
+ # We want to delete Connection usage in do_get_block but Newrelic gem depends on it.
1021
+ # https://github.com/newrelic/rpm/blob/master/lib/new_relic/agent/instrumentation/httpclient.rb#L34-L36
1022
+ conn = Connection.new
1023
+ res = do_get_block(req, proxy, conn, &block)
1024
+ # Webmock's do_get_block returns ConditionVariable
1025
+ if !res.respond_to?(:previous)
1026
+ res = conn.pop
1027
+ end
1028
+ end
1029
+ res.previous = previous_response
1030
+ break
1031
+ rescue RetryableResponse => e
1032
+ previous_request = req
1033
+ previous_response = res = e.res
1034
+ retry_count -= 1
1035
+ end
1036
+ end
1037
+ res
1038
+ end
1039
+
1040
+ def do_request_async(method, uri, query, body, header)
1041
+ conn = Connection.new
1042
+ t = Thread.new(conn) { |tconn|
1043
+ begin
1044
+ if HTTP::Message.file?(body)
1045
+ pos = body.pos rescue nil
1046
+ end
1047
+ retry_count = @session_manager.protocol_retry_count
1048
+ proxy = no_proxy?(uri) ? nil : @proxy
1049
+ while retry_count > 0
1050
+ body.pos = pos if pos
1051
+ req = create_request(method, uri, query, body, header)
1052
+ begin
1053
+ protect_keep_alive_disconnected do
1054
+ do_get_stream(req, proxy, tconn)
1055
+ end
1056
+ break
1057
+ rescue RetryableResponse
1058
+ retry_count -= 1
1059
+ end
1060
+ end
1061
+ rescue Exception => e
1062
+ conn.push e
1063
+ end
1064
+ }
1065
+ conn.async_thread = t
1066
+ conn
1067
+ end
1068
+
1069
+ def load_environment
1070
+ # http_proxy
1071
+ if getenv('REQUEST_METHOD')
1072
+ # HTTP_PROXY conflicts with the environment variable usage in CGI where
1073
+ # HTTP_* is used for HTTP header information. Unlike open-uri, we
1074
+ # simply ignore http_proxy in CGI env and use cgi_http_proxy instead.
1075
+ self.proxy = getenv('cgi_http_proxy')
1076
+ else
1077
+ self.proxy = getenv('http_proxy')
1078
+ end
1079
+ # no_proxy
1080
+ self.no_proxy = getenv('no_proxy')
1081
+ end
1082
+
1083
+ def getenv(name)
1084
+ ENV[name.downcase] || ENV[name.upcase]
1085
+ end
1086
+
1087
+ def adapt_block(&block)
1088
+ return block if block.arity == 2
1089
+ proc { |r, str| block.call(str) }
1090
+ end
1091
+
1092
+ def follow_redirect(method, uri, query, body, header, &block)
1093
+ uri = to_resource_url(uri)
1094
+ if block
1095
+ b = adapt_block(&block)
1096
+ filtered_block = proc { |r, str|
1097
+ b.call(r, str) if r.ok?
1098
+ }
1099
+ end
1100
+ if HTTP::Message.file?(body)
1101
+ pos = body.pos rescue nil
1102
+ end
1103
+ retry_number = 0
1104
+ previous = nil
1105
+ request_query = query
1106
+ while retry_number < @follow_redirect_count
1107
+ body.pos = pos if pos
1108
+ res = do_request(method, uri, request_query, body, header, &filtered_block)
1109
+ res.previous = previous
1110
+ if res.redirect?
1111
+ if res.header['location'].empty?
1112
+ raise BadResponseError.new("Missing Location header for redirect", res)
1113
+ end
1114
+ method = :get if res.see_other? # See RFC2616 10.3.4
1115
+ uri = urify(@redirect_uri_callback.call(uri, res))
1116
+ # To avoid duped query parameter. 'location' must include query part.
1117
+ request_query = nil
1118
+ previous = res
1119
+ retry_number += 1
1120
+ else
1121
+ return res
1122
+ end
1123
+ end
1124
+ raise BadResponseError.new("retry count exceeded", res)
1125
+ end
1126
+
1127
+ def success_content(res)
1128
+ if res.ok?
1129
+ return res.content
1130
+ else
1131
+ raise BadResponseError.new("unexpected response: #{res.header.inspect}", res)
1132
+ end
1133
+ end
1134
+
1135
+ def protect_keep_alive_disconnected
1136
+ begin
1137
+ yield
1138
+ rescue KeepAliveDisconnected
1139
+ # Force to create new connection
1140
+ Thread.current[:HTTPClient_AcquireNewConnection] = true
1141
+ begin
1142
+ yield
1143
+ ensure
1144
+ Thread.current[:HTTPClient_AcquireNewConnection] = false
1145
+ end
1146
+ end
1147
+ end
1148
+
1149
+ def create_request(method, uri, query, body, header)
1150
+ method = method.to_s.upcase
1151
+ if header.is_a?(Hash)
1152
+ header = @default_header.merge(header).to_a
1153
+ else
1154
+ header = @default_header.to_a + header.dup
1155
+ end
1156
+ boundary = nil
1157
+ if body
1158
+ _, content_type = header.find { |key, value|
1159
+ key.to_s.downcase == 'content-type'
1160
+ }
1161
+ if content_type
1162
+ if /\Amultipart/ =~ content_type
1163
+ if content_type =~ /boundary=(.+)\z/
1164
+ boundary = $1
1165
+ else
1166
+ boundary = create_boundary
1167
+ content_type = "#{content_type}; boundary=#{boundary}"
1168
+ header = override_header(header, 'content-type', content_type)
1169
+ end
1170
+ end
1171
+ else
1172
+ if file_in_form_data?(body)
1173
+ boundary = create_boundary
1174
+ content_type = "multipart/form-data; boundary=#{boundary}"
1175
+ else
1176
+ content_type = 'application/x-www-form-urlencoded'
1177
+ end
1178
+ header << ['Content-Type', content_type]
1179
+ end
1180
+ end
1181
+ req = HTTP::Message.new_request(method, uri, query, body, boundary)
1182
+ header.each do |key, value|
1183
+ req.header.add(key.to_s, value)
1184
+ end
1185
+ if @cookie_manager
1186
+ cookie_value = @cookie_manager.cookie_value(uri)
1187
+ if cookie_value
1188
+ req.header.add('Cookie', cookie_value)
1189
+ end
1190
+ end
1191
+ req
1192
+ end
1193
+
1194
+ def create_boundary
1195
+ Digest::SHA1.hexdigest(Time.now.to_s)
1196
+ end
1197
+
1198
+ def file_in_form_data?(body)
1199
+ HTTP::Message.multiparam_query?(body) &&
1200
+ body.any? { |k, v| HTTP::Message.file?(v) }
1201
+ end
1202
+
1203
+ def override_header(header, key, value)
1204
+ result = []
1205
+ header.each do |k, v|
1206
+ if k.to_s.downcase == key
1207
+ result << [key, value]
1208
+ else
1209
+ result << [k, v]
1210
+ end
1211
+ end
1212
+ result
1213
+ end
1214
+
1215
+ NO_PROXY_HOSTS = ['localhost']
1216
+
1217
+ def no_proxy?(uri)
1218
+ if !@proxy or NO_PROXY_HOSTS.include?(uri.host)
1219
+ return true
1220
+ end
1221
+ @no_proxy_regexps.each do |regexp, port|
1222
+ if !port || uri.port == port.to_i
1223
+ if regexp =~ uri.host
1224
+ return true
1225
+ end
1226
+ end
1227
+ end
1228
+ false
1229
+ end
1230
+
1231
+ # !! CAUTION !!
1232
+ # Method 'do_get*' runs under MT conditon. Be careful to change.
1233
+ def do_get_block(req, proxy, conn, &block)
1234
+ @request_filter.each do |filter|
1235
+ filter.filter_request(req)
1236
+ end
1237
+ if str = @test_loopback_response.shift
1238
+ dump_dummy_request_response(req.http_body.dump, str) if @debug_dev
1239
+ res = HTTP::Message.new_response(str, req.header)
1240
+ conn.push(res)
1241
+ return res
1242
+ end
1243
+ content = block ? nil : ''.dup
1244
+ res = HTTP::Message.new_response(content, req.header)
1245
+ @debug_dev << "= Request\n\n" if @debug_dev
1246
+ sess = @session_manager.query(req, proxy)
1247
+ res.peer_cert = sess.ssl_peer_cert
1248
+ @debug_dev << "\n\n= Response\n\n" if @debug_dev
1249
+ do_get_header(req, res, sess)
1250
+ conn.push(res)
1251
+ sess.get_body do |part|
1252
+ set_encoding(part, res.body_encoding)
1253
+ if block
1254
+ block.call(res, part.dup)
1255
+ else
1256
+ content << part
1257
+ end
1258
+ end
1259
+ # there could be a race condition but it's OK to cache unreusable
1260
+ # connection because we do retry for that case.
1261
+ @session_manager.keep(sess) unless sess.closed?
1262
+ commands = @request_filter.collect { |filter|
1263
+ filter.filter_response(req, res)
1264
+ }
1265
+ if commands.find { |command| command == :retry }
1266
+ raise RetryableResponse.new(res)
1267
+ end
1268
+ res
1269
+ end
1270
+
1271
+ def do_get_stream(req, proxy, conn)
1272
+ @request_filter.each do |filter|
1273
+ filter.filter_request(req)
1274
+ end
1275
+ if str = @test_loopback_response.shift
1276
+ dump_dummy_request_response(req.http_body.dump, str) if @debug_dev
1277
+ conn.push(HTTP::Message.new_response(StringIO.new(str), req.header))
1278
+ return
1279
+ end
1280
+ piper, pipew = IO.pipe
1281
+ pipew.binmode
1282
+ res = HTTP::Message.new_response(piper, req.header)
1283
+ @debug_dev << "= Request\n\n" if @debug_dev
1284
+ sess = @session_manager.query(req, proxy)
1285
+ res.peer_cert = sess.ssl_peer_cert
1286
+ @debug_dev << "\n\n= Response\n\n" if @debug_dev
1287
+ do_get_header(req, res, sess)
1288
+ conn.push(res)
1289
+ sess.get_body do |part|
1290
+ set_encoding(part, res.body_encoding)
1291
+ pipew.write(part)
1292
+ end
1293
+ pipew.close
1294
+ @session_manager.keep(sess) unless sess.closed?
1295
+ _ = @request_filter.collect { |filter|
1296
+ filter.filter_response(req, res)
1297
+ }
1298
+ # ignore commands (not retryable in async mode)
1299
+ res
1300
+ end
1301
+
1302
+ def do_get_header(req, res, sess)
1303
+ res.http_version, res.status, res.reason, headers = sess.get_header
1304
+ res.header.set_headers(headers)
1305
+ if @cookie_manager
1306
+ res.header['set-cookie'].each do |cookie|
1307
+ @cookie_manager.parse(cookie, req.header.request_uri)
1308
+ end
1309
+ end
1310
+ end
1311
+
1312
+ def dump_dummy_request_response(req, res)
1313
+ @debug_dev << "= Dummy Request\n\n"
1314
+ @debug_dev << req
1315
+ @debug_dev << "\n\n= Dummy Response\n\n"
1316
+ @debug_dev << res
1317
+ end
1318
+
1319
+ def set_encoding(str, encoding)
1320
+ str.force_encoding(encoding) if encoding
1321
+ end
1322
+
1323
+ def to_resource_url(uri)
1324
+ u = urify(uri)
1325
+ if @base_url && u.scheme.nil? && u.host.nil?
1326
+ urify(@base_url) + uri
1327
+ else
1328
+ u
1329
+ end
1330
+ end
1331
+ end