httpclient-jgraichen 2.3.4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/README.txt +759 -0
  3. data/bin/httpclient +65 -0
  4. data/lib/hexdump.rb +50 -0
  5. data/lib/http-access2.rb +55 -0
  6. data/lib/http-access2/cookie.rb +1 -0
  7. data/lib/http-access2/http.rb +1 -0
  8. data/lib/httpclient.rb +1156 -0
  9. data/lib/httpclient/auth.rb +899 -0
  10. data/lib/httpclient/cacert.p7s +1912 -0
  11. data/lib/httpclient/connection.rb +88 -0
  12. data/lib/httpclient/cookie.rb +438 -0
  13. data/lib/httpclient/http.rb +1046 -0
  14. data/lib/httpclient/include_client.rb +83 -0
  15. data/lib/httpclient/session.rb +1028 -0
  16. data/lib/httpclient/ssl_config.rb +405 -0
  17. data/lib/httpclient/timeout.rb +140 -0
  18. data/lib/httpclient/util.rb +178 -0
  19. data/lib/httpclient/version.rb +3 -0
  20. data/lib/oauthclient.rb +110 -0
  21. data/sample/async.rb +8 -0
  22. data/sample/auth.rb +11 -0
  23. data/sample/cookie.rb +18 -0
  24. data/sample/dav.rb +103 -0
  25. data/sample/howto.rb +49 -0
  26. data/sample/oauth_buzz.rb +57 -0
  27. data/sample/oauth_friendfeed.rb +59 -0
  28. data/sample/oauth_twitter.rb +61 -0
  29. data/sample/ssl/0cert.pem +22 -0
  30. data/sample/ssl/0key.pem +30 -0
  31. data/sample/ssl/1000cert.pem +19 -0
  32. data/sample/ssl/1000key.pem +18 -0
  33. data/sample/ssl/htdocs/index.html +10 -0
  34. data/sample/ssl/ssl_client.rb +22 -0
  35. data/sample/ssl/webrick_httpsd.rb +29 -0
  36. data/sample/stream.rb +21 -0
  37. data/sample/thread.rb +27 -0
  38. data/sample/wcat.rb +21 -0
  39. data/test/ca-chain.cert +44 -0
  40. data/test/ca.cert +23 -0
  41. data/test/client.cert +19 -0
  42. data/test/client.key +15 -0
  43. data/test/helper.rb +129 -0
  44. data/test/htdigest +1 -0
  45. data/test/htpasswd +2 -0
  46. data/test/runner.rb +2 -0
  47. data/test/server.cert +19 -0
  48. data/test/server.key +15 -0
  49. data/test/sslsvr.rb +65 -0
  50. data/test/subca.cert +21 -0
  51. data/test/test_auth.rb +348 -0
  52. data/test/test_cookie.rb +412 -0
  53. data/test/test_hexdump.rb +14 -0
  54. data/test/test_http-access2.rb +507 -0
  55. data/test/test_httpclient.rb +1783 -0
  56. data/test/test_include_client.rb +52 -0
  57. data/test/test_ssl.rb +235 -0
  58. metadata +100 -0
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # httpclient shell command.
4
+ #
5
+ # Usage: 1) % httpclient get https://www.google.co.jp/ q=ruby
6
+ # Usage: 2) % httpclient
7
+ #
8
+ # For 1) it issues a GET request to the given URI and shows the wiredump and
9
+ # the parsed result. For 2) it invokes irb shell with the binding that has a
10
+ # HTTPClient as 'self'. You can call HTTPClient instance methods like;
11
+ # > get "https://www.google.co.jp/", :q => :ruby
12
+ require 'httpclient'
13
+
14
+ METHODS = ['head', 'get', 'post', 'put', 'delete', 'options', 'propfind', 'proppatch', 'trace']
15
+ if ARGV.size >= 2 && METHODS.include?(ARGV[0])
16
+ client = HTTPClient.new
17
+ client.debug_dev = STDERR
18
+ $DEBUG = true
19
+ require 'pp'
20
+ pp client.send(*ARGV)
21
+ exit
22
+ end
23
+
24
+ require 'irb'
25
+ require 'irb/completion'
26
+
27
+ class Runner
28
+ def initialize
29
+ @httpclient = HTTPClient.new
30
+ end
31
+
32
+ def method_missing(msg, *a, &b)
33
+ debug, $DEBUG = $DEBUG, true
34
+ begin
35
+ @httpclient.send(msg, *a, &b)
36
+ ensure
37
+ $DEBUG = debug
38
+ end
39
+ end
40
+
41
+ def run
42
+ IRB.setup(nil)
43
+ ws = IRB::WorkSpace.new(binding)
44
+ irb = IRB::Irb.new(ws)
45
+ IRB.conf[:MAIN_CONTEXT] = irb.context
46
+
47
+ trap("SIGINT") do
48
+ irb.signal_handle
49
+ end
50
+
51
+ begin
52
+ catch(:IRB_EXIT) do
53
+ irb.eval_input
54
+ end
55
+ ensure
56
+ IRB.irb_at_exit
57
+ end
58
+ end
59
+
60
+ def to_s
61
+ 'HTTPClient'
62
+ end
63
+ end
64
+
65
+ Runner.new.run
@@ -0,0 +1,50 @@
1
+ # encoding: binary
2
+
3
+ # This was written by Arai-san and published at
4
+ # http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/31987
5
+
6
+
7
+ module HexDump
8
+ # str must be in BINARY encoding in 1.9
9
+ def encode(str)
10
+ offset = 0
11
+ result = []
12
+ while raw = str.slice(offset, 16) and raw.length > 0
13
+ # data field
14
+ data = ''
15
+ for v in raw.unpack('N* a*')
16
+ if v.kind_of? Integer
17
+ data << sprintf("%08x ", v)
18
+ else
19
+ v.each_byte {|c| data << sprintf("%02x", c) }
20
+ end
21
+ end
22
+ # text field
23
+ text = raw.tr("\000-\037\177-\377", ".")
24
+ result << sprintf("%08x %-36s %s", offset, data, text)
25
+ offset += 16
26
+ # omit duplicate line
27
+ if /^(#{regex_quote_n(raw)})+/n =~ str[offset .. -1]
28
+ result << sprintf("%08x ...", offset)
29
+ offset += $&.length
30
+ # should print at the end
31
+ if offset == str.length
32
+ result << sprintf("%08x %-36s %s", offset-16, data, text)
33
+ end
34
+ end
35
+ end
36
+ result
37
+ end
38
+ module_function :encode
39
+
40
+ if RUBY_VERSION >= "1.9"
41
+ # raw must be in BINARY encoding in 1.9
42
+ def self.regex_quote_n(raw)
43
+ Regexp.quote(raw)
44
+ end
45
+ else
46
+ def self.regex_quote_n(raw)
47
+ Regexp.quote(raw, 'n')
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,55 @@
1
+ # HTTPAccess2 - HTTP accessing library.
2
+ # Copyright (C) 2000-2007 NAKAMURA, Hiroshi <nakahiro@sarion.co.jp>.
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
+ # http-access2.rb is based on http-access.rb in http-access/0.0.4. Some
9
+ # part of it is copyrighted by Maebashi-san who made and published
10
+ # http-access/0.0.4. http-access/0.0.4 did not include license notice but
11
+ # when I asked Maebashi-san he agreed that I can redistribute it under the
12
+ # same terms of Ruby. Many thanks to Maebashi-san.
13
+
14
+
15
+ require 'httpclient'
16
+
17
+
18
+ module HTTPAccess2
19
+ VERSION = ::HTTPClient::VERSION
20
+ RUBY_VERSION_STRING = ::HTTPClient::RUBY_VERSION_STRING
21
+ SSLEnabled = ::HTTPClient::SSLEnabled
22
+ SSPIEnabled = ::HTTPClient::SSPIEnabled
23
+ DEBUG_SSL = true
24
+
25
+ Util = ::HTTPClient::Util
26
+
27
+ class Client < ::HTTPClient
28
+ class RetryableResponse < StandardError
29
+ end
30
+ end
31
+
32
+ SSLConfig = ::HTTPClient::SSLConfig
33
+ BasicAuth = ::HTTPClient::BasicAuth
34
+ DigestAuth = ::HTTPClient::DigestAuth
35
+ NegotiateAuth = ::HTTPClient::NegotiateAuth
36
+ AuthFilterBase = ::HTTPClient::AuthFilterBase
37
+ WWWAuth = ::HTTPClient::WWWAuth
38
+ ProxyAuth = ::HTTPClient::ProxyAuth
39
+ Site = ::HTTPClient::Site
40
+ Connection = ::HTTPClient::Connection
41
+ SessionManager = ::HTTPClient::SessionManager
42
+ SSLSocketWrap = ::HTTPClient::SSLSocketWrap
43
+ DebugSocket = ::HTTPClient::DebugSocket
44
+
45
+ class Session < ::HTTPClient::Session
46
+ class Error < StandardError
47
+ end
48
+ class InvalidState < Error
49
+ end
50
+ class BadResponse < Error
51
+ end
52
+ class KeepAliveDisconnected < Error
53
+ end
54
+ end
55
+ end
@@ -0,0 +1 @@
1
+ require 'httpclient/cookie'
@@ -0,0 +1 @@
1
+ require 'httpclient/http'
@@ -0,0 +1,1156 @@
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 '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 wth 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
+ # === Handling Cookies
153
+ #
154
+ # 1. Using volatile Cookies. Nothing to do. HTTPClient handles Cookies.
155
+ #
156
+ # clnt = HTTPClient.new
157
+ # res = clnt.get(url1) # receives Cookies.
158
+ # res = clnt.get(url2) # sends Cookies if needed.
159
+ # p res.cookies
160
+ #
161
+ # 2. Saving non volatile Cookies to a specified file. Need to set a file at
162
+ # first and invoke save method at last.
163
+ #
164
+ # clnt = HTTPClient.new
165
+ # clnt.set_cookie_store('/home/nahi/cookie.dat')
166
+ # clnt.get(url)
167
+ # ...
168
+ # clnt.save_cookie_store
169
+ #
170
+ # 3. Disabling Cookies.
171
+ #
172
+ # clnt = HTTPClient.new
173
+ # clnt.cookie_manager = nil
174
+ #
175
+ # === Configuring authentication credentials
176
+ #
177
+ # 1. Authentication with Web server. Supports BasicAuth, DigestAuth, and
178
+ # Negotiate/NTLM (requires ruby/ntlm module).
179
+ #
180
+ # clnt = HTTPClient.new
181
+ # domain = 'http://dev.ctor.org/http-access2/'
182
+ # user = 'user'
183
+ # password = 'user'
184
+ # clnt.set_auth(domain, user, password)
185
+ # p clnt.get('http://dev.ctor.org/http-access2/login').status
186
+ #
187
+ # 2. Authentication with Proxy server. Supports BasicAuth and NTLM
188
+ # (requires win32/sspi)
189
+ #
190
+ # clnt = HTTPClient.new(proxy)
191
+ # user = 'proxy'
192
+ # password = 'proxy'
193
+ # clnt.set_proxy_auth(user, password)
194
+ # p clnt.get(url)
195
+ #
196
+ # === Invoking HTTP methods with custom header
197
+ #
198
+ # Pass a Hash or an Array for header argument.
199
+ #
200
+ # header = { 'Accept' => 'text/html' }
201
+ # clnt.get(uri, query, header)
202
+ #
203
+ # header = [['Accept', 'image/jpeg'], ['Accept', 'image/png']]
204
+ # clnt.get_content(uri, query, header)
205
+ #
206
+ # === Invoking HTTP methods asynchronously
207
+ #
208
+ # See head_async, get_async, post_async, put_async, delete_async,
209
+ # options_async, propfind_async, proppatch_async, and trace_async.
210
+ # It immediately returns a HTTPClient::Connection instance as a returning value.
211
+ #
212
+ # connection = clnt.post_async(url, body)
213
+ # print 'posting.'
214
+ # while true
215
+ # break if connection.finished?
216
+ # print '.'
217
+ # sleep 1
218
+ # end
219
+ # puts '.'
220
+ # res = connection.pop
221
+ # p res.status
222
+ # p res.body.read # res.body is an IO for the res of async method.
223
+ #
224
+ # === Shortcut methods
225
+ #
226
+ # You can invoke get_content, get, etc. without creating HTTPClient instance.
227
+ #
228
+ # ruby -rhttpclient -e 'puts HTTPClient.get_content(ARGV.shift)' http://dev.ctor.org/
229
+ # ruby -rhttpclient -e 'p HTTPClient.head(ARGV.shift).header["last-modified"]' http://dev.ctor.org/
230
+ #
231
+ class HTTPClient
232
+ RUBY_VERSION_STRING = "ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE})"
233
+ LIB_NAME = "(#{VERSION}, #{RUBY_VERSION_STRING})"
234
+
235
+ include Util
236
+
237
+ # Raised for indicating running environment configuration error for example
238
+ # accessing via SSL under the ruby which is not compiled with OpenSSL.
239
+ class ConfigurationError < StandardError
240
+ end
241
+
242
+ # Raised for indicating HTTP response error.
243
+ class BadResponseError < RuntimeError
244
+ # HTTP::Message:: a response
245
+ attr_reader :res
246
+
247
+ def initialize(msg, res = nil) # :nodoc:
248
+ super(msg)
249
+ @res = res
250
+ end
251
+ end
252
+
253
+ # Raised for indicating a timeout error.
254
+ class TimeoutError < RuntimeError
255
+ end
256
+
257
+ # Raised for indicating a connection timeout error.
258
+ # You can configure connection timeout via HTTPClient#connect_timeout=.
259
+ class ConnectTimeoutError < TimeoutError
260
+ end
261
+
262
+ # Raised for indicating a request sending timeout error.
263
+ # You can configure request sending timeout via HTTPClient#send_timeout=.
264
+ class SendTimeoutError < TimeoutError
265
+ end
266
+
267
+ # Raised for indicating a response receiving timeout error.
268
+ # You can configure response receiving timeout via
269
+ # HTTPClient#receive_timeout=.
270
+ class ReceiveTimeoutError < TimeoutError
271
+ end
272
+
273
+ # Deprecated. just for backward compatibility
274
+ class Session
275
+ BadResponse = ::HTTPClient::BadResponseError
276
+ end
277
+
278
+ class << self
279
+ %w(get_content post_content head get post put delete options propfind proppatch trace).each do |name|
280
+ eval <<-EOD
281
+ def #{name}(*arg, &block)
282
+ clnt = new
283
+ begin
284
+ clnt.#{name}(*arg, &block)
285
+ ensure
286
+ clnt.reset_all
287
+ end
288
+ end
289
+ EOD
290
+ end
291
+
292
+ private
293
+
294
+ def attr_proxy(symbol, assignable = false)
295
+ name = symbol.to_s
296
+ define_method(name) {
297
+ @session_manager.__send__(name)
298
+ }
299
+ if assignable
300
+ aname = name + '='
301
+ define_method(aname) { |rhs|
302
+ reset_all
303
+ @session_manager.__send__(aname, rhs)
304
+ }
305
+ end
306
+ end
307
+ end
308
+
309
+ # HTTPClient::SSLConfig:: SSL configurator.
310
+ attr_reader :ssl_config
311
+ # WebAgent::CookieManager:: Cookies configurator.
312
+ attr_accessor :cookie_manager
313
+ # An array of response HTTP message body String which is used for loop-back
314
+ # test. See test/* to see how to use it. If you want to do loop-back test
315
+ # of HTTP header, use test_loopback_http_response instead.
316
+ attr_reader :test_loopback_response
317
+ # An array of request filter which can trap HTTP request/response.
318
+ # See HTTPClient::WWWAuth to see how to use it.
319
+ attr_reader :request_filter
320
+ # HTTPClient::ProxyAuth:: Proxy authentication handler.
321
+ attr_reader :proxy_auth
322
+ # HTTPClient::WWWAuth:: WWW authentication handler.
323
+ attr_reader :www_auth
324
+ # How many times get_content and post_content follows HTTP redirect.
325
+ # 10 by default.
326
+ attr_accessor :follow_redirect_count
327
+
328
+ # Set HTTP version as a String:: 'HTTP/1.0' or 'HTTP/1.1'
329
+ attr_proxy(:protocol_version, true)
330
+ # Connect timeout in sec.
331
+ attr_proxy(:connect_timeout, true)
332
+ # Request sending timeout in sec.
333
+ attr_proxy(:send_timeout, true)
334
+ # Response receiving timeout in sec.
335
+ attr_proxy(:receive_timeout, true)
336
+ # Reuse the same connection within this timeout in sec. from last used.
337
+ attr_proxy(:keep_alive_timeout, true)
338
+ # Size of reading block for non-chunked response.
339
+ attr_proxy(:read_block_size, true)
340
+ # Negotiation retry count for authentication. 5 by default.
341
+ attr_proxy(:protocol_retry_count, true)
342
+ # if your ruby is older than 2005-09-06, do not set socket_sync = false to
343
+ # avoid an SSL socket blocking bug in openssl/buffering.rb.
344
+ attr_proxy(:socket_sync, true)
345
+ # User-Agent header in HTTP request.
346
+ attr_proxy(:agent_name, true)
347
+ # From header in HTTP request.
348
+ attr_proxy(:from, true)
349
+ # An array of response HTTP String (not a HTTP message body) which is used
350
+ # for loopback test. See test/* to see how to use it.
351
+ attr_proxy(:test_loopback_http_response)
352
+ # Decompress a compressed (with gzip or deflate) content body transparently. false by default.
353
+ attr_proxy(:transparent_gzip_decompression, true)
354
+ # Local socket address. Set HTTPClient#socket_local.host and HTTPClient#socket_local.port to specify local binding hostname and port of TCP socket.
355
+ attr_proxy(:socket_local, true)
356
+
357
+ # Default header for PROPFIND request.
358
+ PROPFIND_DEFAULT_EXTHEADER = { 'Depth' => '0' }
359
+
360
+ # Default User-Agent header
361
+ DEFAULT_AGENT_NAME = 'HTTPClient/1.0'
362
+
363
+ # Creates a HTTPClient instance which manages sessions, cookies, etc.
364
+ #
365
+ # HTTPClient.new takes 3 optional arguments for proxy url string,
366
+ # User-Agent String and From header String. User-Agent and From are embedded
367
+ # in HTTP request Header if given. No User-Agent and From header added
368
+ # without setting it explicitly.
369
+ #
370
+ # proxy = 'http://myproxy:8080'
371
+ # agent_name = 'MyAgent/0.1'
372
+ # from = 'from@example.com'
373
+ # HTTPClient.new(proxy, agent_name, from)
374
+ #
375
+ # You can use a keyword argument style Hash. Keys are :proxy, :agent_name
376
+ # and :from.
377
+ #
378
+ # HTTPClient.new(:agent_name => 'MyAgent/0.1')
379
+ def initialize(*args)
380
+ proxy, agent_name, from = keyword_argument(args, :proxy, :agent_name, :from)
381
+ @proxy = nil # assigned later.
382
+ @no_proxy = nil
383
+ @no_proxy_regexps = []
384
+ @www_auth = WWWAuth.new
385
+ @proxy_auth = ProxyAuth.new
386
+ @request_filter = [@proxy_auth, @www_auth]
387
+ @debug_dev = nil
388
+ @redirect_uri_callback = method(:default_redirect_uri_callback)
389
+ @test_loopback_response = []
390
+ @session_manager = SessionManager.new(self)
391
+ @session_manager.agent_name = agent_name || DEFAULT_AGENT_NAME
392
+ @session_manager.from = from
393
+ @session_manager.ssl_config = @ssl_config = SSLConfig.new(self)
394
+ @cookie_manager = WebAgent::CookieManager.new
395
+ @follow_redirect_count = 10
396
+ load_environment
397
+ self.proxy = proxy if proxy
398
+ keep_webmock_compat
399
+ end
400
+
401
+ # webmock 1.6.2 depends on HTTP::Message#body.content to work.
402
+ # let's keep it work iif webmock is loaded for a while.
403
+ def keep_webmock_compat
404
+ if respond_to?(:do_get_block_with_webmock)
405
+ ::HTTP::Message.module_eval do
406
+ def body
407
+ def (o = self.content).content
408
+ self
409
+ end
410
+ o
411
+ end
412
+ end
413
+ end
414
+ end
415
+
416
+ # Returns debug device if exists. See debug_dev=.
417
+ def debug_dev
418
+ @debug_dev
419
+ end
420
+
421
+ # Sets debug device. Once debug device is set, all HTTP requests and
422
+ # responses are dumped to given device. dev must respond to << for dump.
423
+ #
424
+ # Calling this method resets all existing sessions.
425
+ def debug_dev=(dev)
426
+ @debug_dev = dev
427
+ reset_all
428
+ @session_manager.debug_dev = dev
429
+ end
430
+
431
+ # Returns URI object of HTTP proxy if exists.
432
+ def proxy
433
+ @proxy
434
+ end
435
+
436
+ # Sets HTTP proxy used for HTTP connection. Given proxy can be an URI,
437
+ # a String or nil. You can set user/password for proxy authentication like
438
+ # HTTPClient#proxy = 'http://user:passwd@myproxy:8080'
439
+ #
440
+ # You can use environment variable 'http_proxy' or 'HTTP_PROXY' for it.
441
+ # You need to use 'cgi_http_proxy' or 'CGI_HTTP_PROXY' instead if you run
442
+ # HTTPClient from CGI environment from security reason. (HTTPClient checks
443
+ # 'REQUEST_METHOD' environment variable whether it's CGI or not)
444
+ #
445
+ # Calling this method resets all existing sessions.
446
+ def proxy=(proxy)
447
+ if proxy.nil? || proxy.to_s.empty?
448
+ @proxy = nil
449
+ @proxy_auth.reset_challenge
450
+ else
451
+ @proxy = urify(proxy)
452
+ if @proxy.scheme == nil or @proxy.scheme.downcase != 'http' or
453
+ @proxy.host == nil or @proxy.port == nil
454
+ raise ArgumentError.new("unsupported proxy #{proxy}")
455
+ end
456
+ @proxy_auth.reset_challenge
457
+ if @proxy.user || @proxy.password
458
+ @proxy_auth.set_auth(@proxy.user, @proxy.password)
459
+ end
460
+ end
461
+ reset_all
462
+ @session_manager.proxy = @proxy
463
+ @proxy
464
+ end
465
+
466
+ # Returns NO_PROXY setting String if given.
467
+ def no_proxy
468
+ @no_proxy
469
+ end
470
+
471
+ # Sets NO_PROXY setting String. no_proxy must be a comma separated String.
472
+ # Each entry must be 'host' or 'host:port' such as;
473
+ # HTTPClient#no_proxy = 'example.com,example.co.jp:443'
474
+ #
475
+ # 'localhost' is treated as a no_proxy site regardless of explicitly listed.
476
+ # HTTPClient checks given URI objects before accessing it.
477
+ # 'host' is tail string match. No IP-addr conversion.
478
+ #
479
+ # You can use environment variable 'no_proxy' or 'NO_PROXY' for it.
480
+ #
481
+ # Calling this method resets all existing sessions.
482
+ def no_proxy=(no_proxy)
483
+ @no_proxy = no_proxy
484
+ @no_proxy_regexps.clear
485
+ if @no_proxy
486
+ @no_proxy.scan(/([^\s:,]+)(?::(\d+))?/) do |host, port|
487
+ if host[0] == ?.
488
+ regexp = /#{Regexp.quote(host)}\z/i
489
+ else
490
+ regexp = /(\A|\.)#{Regexp.quote(host)}\z/i
491
+ end
492
+ @no_proxy_regexps << [regexp, port]
493
+ end
494
+ end
495
+ reset_all
496
+ end
497
+
498
+ # Sets credential for Web server authentication.
499
+ # domain:: a String or an URI to specify where HTTPClient should use this
500
+ # credential. If you set uri to nil, HTTPClient uses this credential
501
+ # wherever a server requires it.
502
+ # user:: username String.
503
+ # passwd:: password String.
504
+ #
505
+ # You can set multiple credentials for each uri.
506
+ #
507
+ # clnt.set_auth('http://www.example.com/foo/', 'foo_user', 'passwd')
508
+ # clnt.set_auth('http://www.example.com/bar/', 'bar_user', 'passwd')
509
+ #
510
+ # Calling this method resets all existing sessions.
511
+ def set_auth(domain, user, passwd)
512
+ uri = urify(domain)
513
+ @www_auth.set_auth(uri, user, passwd)
514
+ reset_all
515
+ end
516
+
517
+ # Deprecated. Use set_auth instead.
518
+ def set_basic_auth(domain, user, passwd)
519
+ uri = urify(domain)
520
+ @www_auth.basic_auth.set(uri, user, passwd)
521
+ reset_all
522
+ end
523
+
524
+ # Sets credential for Proxy authentication.
525
+ # user:: username String.
526
+ # passwd:: password String.
527
+ #
528
+ # Calling this method resets all existing sessions.
529
+ def set_proxy_auth(user, passwd)
530
+ @proxy_auth.set_auth(user, passwd)
531
+ reset_all
532
+ end
533
+
534
+ # Sets the filename where non-volatile Cookies be saved by calling
535
+ # save_cookie_store.
536
+ # This method tries to load and managing Cookies from the specified file.
537
+ #
538
+ # Calling this method resets all existing sessions.
539
+ def set_cookie_store(filename)
540
+ @cookie_manager.cookies_file = filename
541
+ @cookie_manager.load_cookies if filename
542
+ reset_all
543
+ end
544
+
545
+ # Try to save Cookies to the file specified in set_cookie_store. Unexpected
546
+ # error will be raised if you don't call set_cookie_store first.
547
+ # (interface mismatch between WebAgent::CookieManager implementation)
548
+ def save_cookie_store
549
+ @cookie_manager.save_cookies
550
+ end
551
+
552
+ # Returns stored cookies.
553
+ def cookies
554
+ if @cookie_manager
555
+ @cookie_manager.cookies
556
+ end
557
+ end
558
+
559
+ # Sets callback proc when HTTP redirect status is returned for get_content
560
+ # and post_content. default_redirect_uri_callback is used by default.
561
+ #
562
+ # If you need strict implementation which does not allow relative URI
563
+ # redirection, set strict_redirect_uri_callback instead.
564
+ #
565
+ # clnt.redirect_uri_callback = clnt.method(:strict_redirect_uri_callback)
566
+ #
567
+ def redirect_uri_callback=(redirect_uri_callback)
568
+ @redirect_uri_callback = redirect_uri_callback
569
+ end
570
+
571
+ # Retrieves a web resource.
572
+ #
573
+ # uri:: a String or an URI object which represents an URL of web resource.
574
+ # query:: a Hash or an Array of query part of URL.
575
+ # e.g. { "a" => "b" } => 'http://host/part?a=b'.
576
+ # Give an array to pass multiple value like
577
+ # [["a", "b"], ["a", "c"]] => 'http://host/part?a=b&a=c'.
578
+ # header:: a Hash or an Array of extra headers. e.g.
579
+ # { 'Accept' => 'text/html' } or
580
+ # [['Accept', 'image/jpeg'], ['Accept', 'image/png']].
581
+ # &block:: Give a block to get chunked message-body of response like
582
+ # get_content(uri) { |chunked_body| ... }.
583
+ # Size of each chunk may not be the same.
584
+ #
585
+ # get_content follows HTTP redirect status (see HTTP::Status.redirect?)
586
+ # internally and try to retrieve content from redirected URL. See
587
+ # redirect_uri_callback= how HTTP redirection is handled.
588
+ #
589
+ # If you need to get full HTTP response including HTTP status and headers,
590
+ # use get method. get returns HTTP::Message as a response and you need to
591
+ # follow HTTP redirect by yourself if you need.
592
+ def get_content(uri, *args, &block)
593
+ query, header = keyword_argument(args, :query, :header)
594
+ success_content(follow_redirect(:get, uri, query, nil, header || {}, &block))
595
+ end
596
+
597
+ # Posts a content.
598
+ #
599
+ # uri:: a String or an URI object which represents an URL of web resource.
600
+ # body:: a Hash or an Array of body part. e.g.
601
+ # { "a" => "b" } => 'a=b'
602
+ # Give an array to pass multiple value like
603
+ # [["a", "b"], ["a", "c"]] => 'a=b&a=c'
604
+ # When you pass a File as a value, it will be posted as a
605
+ # multipart/form-data. e.g.
606
+ # { 'upload' => file }
607
+ # You can also send custom multipart by passing an array of hashes.
608
+ # Each part must have a :content attribute which can be a file, all
609
+ # other keys will become headers.
610
+ # [{ 'Content-Type' => 'text/plain', :content => "some text" },
611
+ # { 'Content-Type' => 'video/mp4', :content => File.new('video.mp4') }]
612
+ # => <Two parts with custom Content-Type header>
613
+ # header:: a Hash or an Array of extra headers. e.g.
614
+ # { 'Accept' => 'text/html' }
615
+ # or
616
+ # [['Accept', 'image/jpeg'], ['Accept', 'image/png']].
617
+ # &block:: Give a block to get chunked message-body of response like
618
+ # post_content(uri) { |chunked_body| ... }.
619
+ # Size of each chunk may not be the same.
620
+ #
621
+ # post_content follows HTTP redirect status (see HTTP::Status.redirect?)
622
+ # internally and try to post the content to redirected URL. See
623
+ # redirect_uri_callback= how HTTP redirection is handled.
624
+ # Bear in mind that you should not depend on post_content because it sends
625
+ # the same POST method to the new location which is prohibited in HTTP spec.
626
+ #
627
+ # If you need to get full HTTP response including HTTP status and headers,
628
+ # use post method.
629
+ def post_content(uri, *args, &block)
630
+ body, header = keyword_argument(args, :body, :header)
631
+ success_content(follow_redirect(:post, uri, nil, body, header || {}, &block))
632
+ end
633
+
634
+ # A method for redirect uri callback. How to use:
635
+ # clnt.redirect_uri_callback = clnt.method(:strict_redirect_uri_callback)
636
+ # This callback does not allow relative redirect such as
637
+ # Location: ../foo/
638
+ # in HTTP header. (raises BadResponseError instead)
639
+ def strict_redirect_uri_callback(uri, res)
640
+ newuri = urify(res.header['location'][0])
641
+ if https?(uri) && !https?(newuri)
642
+ raise BadResponseError.new("redirecting to non-https resource")
643
+ end
644
+ if !http?(newuri) && !https?(newuri)
645
+ raise BadResponseError.new("unexpected location: #{newuri}", res)
646
+ end
647
+ puts "redirect to: #{newuri}" if $DEBUG
648
+ newuri
649
+ end
650
+
651
+ # A default method for redirect uri callback. This method is used by
652
+ # HTTPClient instance by default.
653
+ # This callback allows relative redirect such as
654
+ # Location: ../foo/
655
+ # in HTTP header.
656
+ def default_redirect_uri_callback(uri, res)
657
+ newuri = urify(res.header['location'][0])
658
+ if !http?(newuri) && !https?(newuri)
659
+ newuri = uri + newuri
660
+ warn("could be a relative URI in location header which is not recommended")
661
+ warn("'The field value consists of a single absolute URI' in HTTP spec")
662
+ end
663
+ if https?(uri) && !https?(newuri)
664
+ raise BadResponseError.new("redirecting to non-https resource")
665
+ end
666
+ puts "redirect to: #{newuri}" if $DEBUG
667
+ newuri
668
+ end
669
+
670
+ # Sends HEAD request to the specified URL. See request for arguments.
671
+ def head(uri, *args)
672
+ request(:head, uri, argument_to_hash(args, :query, :header, :follow_redirect))
673
+ end
674
+
675
+ # Sends GET request to the specified URL. See request for arguments.
676
+ def get(uri, *args, &block)
677
+ request(:get, uri, argument_to_hash(args, :query, :header, :follow_redirect), &block)
678
+ end
679
+
680
+ # Sends POST request to the specified URL. See request for arguments.
681
+ # You should not depend on :follow_redirect => true for POST method. It
682
+ # sends the same POST method to the new location which is prohibited in HTTP spec.
683
+ def post(uri, *args, &block)
684
+ request(:post, uri, argument_to_hash(args, :body, :header, :follow_redirect), &block)
685
+ end
686
+
687
+ # Sends PUT request to the specified URL. See request for arguments.
688
+ def put(uri, *args, &block)
689
+ request(:put, uri, argument_to_hash(args, :body, :header), &block)
690
+ end
691
+
692
+ # Sends DELETE request to the specified URL. See request for arguments.
693
+ def delete(uri, *args, &block)
694
+ request(:delete, uri, argument_to_hash(args, :body, :header), &block)
695
+ end
696
+
697
+ # Sends OPTIONS request to the specified URL. See request for arguments.
698
+ def options(uri, *args, &block)
699
+ request(:options, uri, argument_to_hash(args, :header), &block)
700
+ end
701
+
702
+ # Sends PROPFIND request to the specified URL. See request for arguments.
703
+ def propfind(uri, *args, &block)
704
+ request(:propfind, uri, argument_to_hash(args, :header), &block)
705
+ end
706
+
707
+ # Sends PROPPATCH request to the specified URL. See request for arguments.
708
+ def proppatch(uri, *args, &block)
709
+ request(:proppatch, uri, argument_to_hash(args, :body, :header), &block)
710
+ end
711
+
712
+ # Sends TRACE request to the specified URL. See request for arguments.
713
+ def trace(uri, *args, &block)
714
+ request('TRACE', uri, argument_to_hash(args, :query, :header), &block)
715
+ end
716
+
717
+ # Sends a request to the specified URL.
718
+ #
719
+ # method:: HTTP method to be sent. method.to_s.upcase is used.
720
+ # uri:: a String or an URI object which represents an URL of web resource.
721
+ # query:: a Hash or an Array of query part of URL.
722
+ # e.g. { "a" => "b" } => 'http://host/part?a=b'
723
+ # Give an array to pass multiple value like
724
+ # [["a", "b"], ["a", "c"]] => 'http://host/part?a=b&a=c'
725
+ # body:: a Hash or an Array of body part. e.g.
726
+ # { "a" => "b" }
727
+ # => 'a=b'
728
+ # Give an array to pass multiple value like
729
+ # [["a", "b"], ["a", "c"]]
730
+ # => 'a=b&a=c'.
731
+ # When the given method is 'POST' and the given body contains a file
732
+ # as a value, it will be posted as a multipart/form-data. e.g.
733
+ # { 'upload' => file }
734
+ # You can also send custom multipart by passing an array of hashes.
735
+ # Each part must have a :content attribute which can be a file, all
736
+ # other keys will become headers.
737
+ # [{ 'Content-Type' => 'text/plain', :content => "some text" },
738
+ # { 'Content-Type' => 'video/mp4', :content => File.new('video.mp4') }]
739
+ # => <Two parts with custom Content-Type header>
740
+ # See HTTP::Message.file? for actual condition of 'a file'.
741
+ # header:: a Hash or an Array of extra headers. e.g.
742
+ # { 'Accept' => 'text/html' } or
743
+ # [['Accept', 'image/jpeg'], ['Accept', 'image/png']].
744
+ # &block:: Give a block to get chunked message-body of response like
745
+ # get(uri) { |chunked_body| ... }.
746
+ # Size of each chunk may not be the same.
747
+ #
748
+ # You can also pass a String as a body. HTTPClient just sends a String as
749
+ # a HTTP request message body.
750
+ #
751
+ # When you pass an IO as a body, HTTPClient sends it as a HTTP request with
752
+ # chunked encoding (Transfer-Encoding: chunked in HTTP header) if IO does not
753
+ # respond to :read. Bear in mind that some server application does not support
754
+ # chunked request. At least cgi.rb does not support it.
755
+ def request(method, uri, *args, &block)
756
+ query, body, header, follow_redirect = keyword_argument(args, :query, :body, :header, :follow_redirect)
757
+ if [:post, :put].include?(method)
758
+ body ||= ''
759
+ end
760
+ if method == :propfind
761
+ header ||= PROPFIND_DEFAULT_EXTHEADER
762
+ else
763
+ header ||= {}
764
+ end
765
+ uri = urify(uri)
766
+ if block
767
+ filtered_block = proc { |res, str|
768
+ block.call(str)
769
+ }
770
+ end
771
+ if follow_redirect
772
+ follow_redirect(method, uri, query, body, header, &block)
773
+ else
774
+ do_request(method, uri, query, body, header, &filtered_block)
775
+ end
776
+ end
777
+
778
+ # Sends HEAD request in async style. See request_async for arguments.
779
+ # It immediately returns a HTTPClient::Connection instance as a result.
780
+ def head_async(uri, *args)
781
+ query, header = keyword_argument(args, :query, :header)
782
+ request_async(:head, uri, query, nil, header || {})
783
+ end
784
+
785
+ # Sends GET request in async style. See request_async for arguments.
786
+ # It immediately returns a HTTPClient::Connection instance as a result.
787
+ def get_async(uri, *args)
788
+ query, header = keyword_argument(args, :query, :header)
789
+ request_async(:get, uri, query, nil, header || {})
790
+ end
791
+
792
+ # Sends POST request in async style. See request_async for arguments.
793
+ # It immediately returns a HTTPClient::Connection instance as a result.
794
+ def post_async(uri, *args)
795
+ body, header = keyword_argument(args, :body, :header)
796
+ request_async(:post, uri, nil, body || '', header || {})
797
+ end
798
+
799
+ # Sends PUT request in async style. See request_async for arguments.
800
+ # It immediately returns a HTTPClient::Connection instance as a result.
801
+ def put_async(uri, *args)
802
+ body, header = keyword_argument(args, :body, :header)
803
+ request_async(:put, uri, nil, body || '', header || {})
804
+ end
805
+
806
+ # Sends DELETE request in async style. See request_async for arguments.
807
+ # It immediately returns a HTTPClient::Connection instance as a result.
808
+ def delete_async(uri, *args)
809
+ header = keyword_argument(args, :header)
810
+ request_async(:delete, uri, nil, nil, header || {})
811
+ end
812
+
813
+ # Sends OPTIONS request in async style. See request_async for arguments.
814
+ # It immediately returns a HTTPClient::Connection instance as a result.
815
+ def options_async(uri, *args)
816
+ header = keyword_argument(args, :header)
817
+ request_async(:options, uri, nil, nil, header || {})
818
+ end
819
+
820
+ # Sends PROPFIND request in async style. See request_async for arguments.
821
+ # It immediately returns a HTTPClient::Connection instance as a result.
822
+ def propfind_async(uri, *args)
823
+ header = keyword_argument(args, :header)
824
+ request_async(:propfind, uri, nil, nil, header || PROPFIND_DEFAULT_EXTHEADER)
825
+ end
826
+
827
+ # Sends PROPPATCH request in async style. See request_async for arguments.
828
+ # It immediately returns a HTTPClient::Connection instance as a result.
829
+ def proppatch_async(uri, *args)
830
+ body, header = keyword_argument(args, :body, :header)
831
+ request_async(:proppatch, uri, nil, body, header || {})
832
+ end
833
+
834
+ # Sends TRACE request in async style. See request_async for arguments.
835
+ # It immediately returns a HTTPClient::Connection instance as a result.
836
+ def trace_async(uri, *args)
837
+ query, body, header = keyword_argument(args, :query, :body, :header)
838
+ request_async(:trace, uri, query, body, header || {})
839
+ end
840
+
841
+ # Sends a request in async style. request method creates new Thread for
842
+ # HTTP connection and returns a HTTPClient::Connection instance immediately.
843
+ #
844
+ # Arguments definition is the same as request.
845
+ def request_async(method, uri, query = nil, body = nil, header = {})
846
+ uri = urify(uri)
847
+ do_request_async(method, uri, query, body, header)
848
+ end
849
+
850
+ # Resets internal session for the given URL. Keep-alive connection for the
851
+ # site (host-port pair) is disconnected if exists.
852
+ def reset(uri)
853
+ uri = urify(uri)
854
+ @session_manager.reset(uri)
855
+ end
856
+
857
+ # Resets all of internal sessions. Keep-alive connections are disconnected.
858
+ def reset_all
859
+ @session_manager.reset_all
860
+ end
861
+
862
+ private
863
+
864
+ class RetryableResponse < StandardError # :nodoc:
865
+ end
866
+
867
+ class KeepAliveDisconnected < StandardError # :nodoc:
868
+ attr_reader :sess
869
+ def initialize(sess = nil)
870
+ @sess = sess
871
+ end
872
+ end
873
+
874
+ def do_request(method, uri, query, body, header, &block)
875
+ conn = Connection.new
876
+ res = nil
877
+ if HTTP::Message.file?(body)
878
+ pos = body.pos rescue nil
879
+ end
880
+ retry_count = @session_manager.protocol_retry_count
881
+ proxy = no_proxy?(uri) ? nil : @proxy
882
+ while retry_count > 0
883
+ body.pos = pos if pos
884
+ req = create_request(method, uri, query, body, header)
885
+ begin
886
+ protect_keep_alive_disconnected do
887
+ do_get_block(req, proxy, conn, &block)
888
+ end
889
+ res = conn.pop
890
+ break
891
+ rescue RetryableResponse
892
+ res = conn.pop
893
+ retry_count -= 1
894
+ end
895
+ end
896
+ res
897
+ end
898
+
899
+ def do_request_async(method, uri, query, body, header)
900
+ conn = Connection.new
901
+ t = Thread.new(conn) { |tconn|
902
+ begin
903
+ if HTTP::Message.file?(body)
904
+ pos = body.pos rescue nil
905
+ end
906
+ retry_count = @session_manager.protocol_retry_count
907
+ proxy = no_proxy?(uri) ? nil : @proxy
908
+ while retry_count > 0
909
+ body.pos = pos if pos
910
+ req = create_request(method, uri, query, body, header)
911
+ begin
912
+ protect_keep_alive_disconnected do
913
+ do_get_stream(req, proxy, tconn)
914
+ end
915
+ break
916
+ rescue RetryableResponse
917
+ retry_count -= 1
918
+ end
919
+ end
920
+ rescue Exception
921
+ conn.push $!
922
+ end
923
+ }
924
+ conn.async_thread = t
925
+ conn
926
+ end
927
+
928
+ def load_environment
929
+ # http_proxy
930
+ if getenv('REQUEST_METHOD')
931
+ # HTTP_PROXY conflicts with the environment variable usage in CGI where
932
+ # HTTP_* is used for HTTP header information. Unlike open-uri, we
933
+ # simply ignore http_proxy in CGI env and use cgi_http_proxy instead.
934
+ self.proxy = getenv('cgi_http_proxy')
935
+ else
936
+ self.proxy = getenv('http_proxy')
937
+ end
938
+ # no_proxy
939
+ self.no_proxy = getenv('no_proxy')
940
+ end
941
+
942
+ def getenv(name)
943
+ ENV[name.downcase] || ENV[name.upcase]
944
+ end
945
+
946
+ def follow_redirect(method, uri, query, body, header, &block)
947
+ uri = urify(uri)
948
+ if block
949
+ filtered_block = proc { |r, str|
950
+ block.call(str) if r.ok?
951
+ }
952
+ end
953
+ if HTTP::Message.file?(body)
954
+ pos = body.pos rescue nil
955
+ end
956
+ retry_number = 0
957
+ while retry_number < @follow_redirect_count
958
+ body.pos = pos if pos
959
+ res = do_request(method, uri, query, body, header, &filtered_block)
960
+ if res.redirect?
961
+ method = :get if res.see_other? # See RFC2616 10.3.4
962
+ uri = urify(@redirect_uri_callback.call(uri, res))
963
+ retry_number += 1
964
+ else
965
+ return res
966
+ end
967
+ end
968
+ raise BadResponseError.new("retry count exceeded", res)
969
+ end
970
+
971
+ def success_content(res)
972
+ if res.ok?
973
+ return res.content
974
+ else
975
+ raise BadResponseError.new("unexpected response: #{res.header.inspect}", res)
976
+ end
977
+ end
978
+
979
+ def protect_keep_alive_disconnected
980
+ begin
981
+ yield
982
+ rescue KeepAliveDisconnected => e
983
+ if e.sess
984
+ @session_manager.invalidate(e.sess.dest)
985
+ end
986
+ yield
987
+ end
988
+ end
989
+
990
+ def create_request(method, uri, query, body, header)
991
+ method = method.to_s.upcase
992
+ if header.is_a?(Hash)
993
+ header = header.to_a
994
+ else
995
+ header = header.dup
996
+ end
997
+ boundary = nil
998
+ if body
999
+ _, content_type = header.find { |key, value|
1000
+ key.downcase == 'content-type'
1001
+ }
1002
+ if content_type
1003
+ if /\Amultipart/ =~ content_type
1004
+ if content_type =~ /boundary=(.+)\z/
1005
+ boundary = $1
1006
+ else
1007
+ boundary = create_boundary
1008
+ content_type = "#{content_type}; boundary=#{boundary}"
1009
+ header = override_header(header, 'Content-Type', content_type)
1010
+ end
1011
+ end
1012
+ else
1013
+ if file_in_form_data?(body)
1014
+ boundary = create_boundary
1015
+ content_type = "multipart/form-data; boundary=#{boundary}"
1016
+ else
1017
+ content_type = 'application/x-www-form-urlencoded'
1018
+ end
1019
+ header << ['Content-Type', content_type]
1020
+ end
1021
+ end
1022
+ req = HTTP::Message.new_request(method, uri, query, body, boundary)
1023
+ header.each do |key, value|
1024
+ req.header.add(key.to_s, value)
1025
+ end
1026
+ if @cookie_manager && cookie = @cookie_manager.find(uri)
1027
+ req.header.add('Cookie', cookie)
1028
+ end
1029
+ req
1030
+ end
1031
+
1032
+ def create_boundary
1033
+ Digest::SHA1.hexdigest(Time.now.to_s)
1034
+ end
1035
+
1036
+ def file_in_form_data?(body)
1037
+ HTTP::Message.multiparam_query?(body) &&
1038
+ body.any? { |k, v| HTTP::Message.file?(v) }
1039
+ end
1040
+
1041
+ def override_header(header, key, value)
1042
+ result = []
1043
+ header.each do |k, v|
1044
+ if k.downcase == key.downcase
1045
+ result << [key, value]
1046
+ else
1047
+ result << [k, v]
1048
+ end
1049
+ end
1050
+ result
1051
+ end
1052
+
1053
+ NO_PROXY_HOSTS = ['localhost']
1054
+
1055
+ def no_proxy?(uri)
1056
+ if !@proxy or NO_PROXY_HOSTS.include?(uri.host)
1057
+ return true
1058
+ end
1059
+ @no_proxy_regexps.each do |regexp, port|
1060
+ if !port || uri.port == port.to_i
1061
+ if regexp =~ uri.host
1062
+ return true
1063
+ end
1064
+ end
1065
+ end
1066
+ false
1067
+ end
1068
+
1069
+ # !! CAUTION !!
1070
+ # Method 'do_get*' runs under MT conditon. Be careful to change.
1071
+ def do_get_block(req, proxy, conn, &block)
1072
+ @request_filter.each do |filter|
1073
+ filter.filter_request(req)
1074
+ end
1075
+ if str = @test_loopback_response.shift
1076
+ dump_dummy_request_response(req.http_body.dump, str) if @debug_dev
1077
+ conn.push(HTTP::Message.new_response(str, req.header))
1078
+ return
1079
+ end
1080
+ content = block ? nil : ''
1081
+ res = HTTP::Message.new_response(content, req.header)
1082
+ @debug_dev << "= Request\n\n" if @debug_dev
1083
+ sess = @session_manager.query(req, proxy)
1084
+ res.peer_cert = sess.ssl_peer_cert
1085
+ @debug_dev << "\n\n= Response\n\n" if @debug_dev
1086
+ do_get_header(req, res, sess)
1087
+ conn.push(res)
1088
+ sess.get_body do |part|
1089
+ set_encoding(part, res.body_encoding)
1090
+ if block
1091
+ block.call(res, part)
1092
+ else
1093
+ content << part
1094
+ end
1095
+ end
1096
+ # there could be a race condition but it's OK to cache unreusable
1097
+ # connection because we do retry for that case.
1098
+ @session_manager.keep(sess) unless sess.closed?
1099
+ commands = @request_filter.collect { |filter|
1100
+ filter.filter_response(req, res)
1101
+ }
1102
+ if commands.find { |command| command == :retry }
1103
+ raise RetryableResponse.new
1104
+ end
1105
+ end
1106
+
1107
+ def do_get_stream(req, proxy, conn)
1108
+ @request_filter.each do |filter|
1109
+ filter.filter_request(req)
1110
+ end
1111
+ if str = @test_loopback_response.shift
1112
+ dump_dummy_request_response(req.http_body.dump, str) if @debug_dev
1113
+ conn.push(HTTP::Message.new_response(StringIO.new(str), req.header))
1114
+ return
1115
+ end
1116
+ piper, pipew = IO.pipe
1117
+ res = HTTP::Message.new_response(piper, req.header)
1118
+ @debug_dev << "= Request\n\n" if @debug_dev
1119
+ sess = @session_manager.query(req, proxy)
1120
+ res.peer_cert = sess.ssl_peer_cert
1121
+ @debug_dev << "\n\n= Response\n\n" if @debug_dev
1122
+ do_get_header(req, res, sess)
1123
+ conn.push(res)
1124
+ sess.get_body do |part|
1125
+ set_encoding(part, res.body_encoding)
1126
+ pipew.write(part)
1127
+ end
1128
+ pipew.close
1129
+ @session_manager.keep(sess) unless sess.closed?
1130
+ _ = @request_filter.collect { |filter|
1131
+ filter.filter_response(req, res)
1132
+ }
1133
+ # ignore commands (not retryable in async mode)
1134
+ end
1135
+
1136
+ def do_get_header(req, res, sess)
1137
+ res.http_version, res.status, res.reason, headers = sess.get_header
1138
+ res.header.set_headers(headers)
1139
+ if @cookie_manager
1140
+ res.header['set-cookie'].each do |cookie|
1141
+ @cookie_manager.parse(cookie, req.header.request_uri)
1142
+ end
1143
+ end
1144
+ end
1145
+
1146
+ def dump_dummy_request_response(req, res)
1147
+ @debug_dev << "= Dummy Request\n\n"
1148
+ @debug_dev << req
1149
+ @debug_dev << "\n\n= Dummy Response\n\n"
1150
+ @debug_dev << res
1151
+ end
1152
+
1153
+ def set_encoding(str, encoding)
1154
+ str.force_encoding(encoding) if encoding
1155
+ end
1156
+ end