maedana-httpclient 2.1.5.2.1

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