httpclient 2.5.3.3 → 2.6.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3f9393154ead7b6adecbda232894fb4fd0f6c0c4
4
- data.tar.gz: 93aba8378632669c8c99d5c91a222a97b59fb28a
3
+ metadata.gz: a6c0658e8a69a8fc6b9bd591912d99dfdb01aeb0
4
+ data.tar.gz: e513505c66b946b6c3dd2fb3b60a3f9b9252b76c
5
5
  SHA512:
6
- metadata.gz: 20aaefda3dadda680b3fe25bc54fc0f7f099654d2b59177f1bedba3bc9442d03f1c1953971c316da32e3fe8a11419c0f5d0fabf5267cf40d353625eb07e14661
7
- data.tar.gz: 11be25379bf090278ac260ab235d1ac4bddad1bb03298d614e5716c455c8816ff6f4e189cb5df5e9e2c15574c5cf996c5311f78d2cf672d0f04e05efb74b4e7e
6
+ metadata.gz: aca638628efee8a54e2a6747db133232be76f355b892f89f4816790180955019aa1edfc9d3426b97988e6fa51fbc19eb2b7735c4c6ab8b570bb166678beccfd6
7
+ data.tar.gz: 02ec73e1b3ae1edf0dd0fe5f6e070de3f8b4a309066e1ce8f2b945ed4d53d532a2bfd513374ce1dfd793862221b57e172c21e4cc4166b32b811409d773e74af7
@@ -12,12 +12,18 @@
12
12
  require 'httpclient'
13
13
 
14
14
  METHODS = ['head', 'get', 'post', 'put', 'delete', 'options', 'propfind', 'proppatch', 'trace']
15
- if ARGV.size >= 2 && METHODS.include?(ARGV[0])
15
+ method = ARGV.shift
16
+ url = ARGV.shift
17
+ if method && url
16
18
  client = HTTPClient.new
17
- client.debug_dev = STDERR
18
- $DEBUG = true
19
- require 'pp'
20
- pp client.send(*ARGV)
19
+ if method == 'download'
20
+ print client.get_content(url)
21
+ else
22
+ client.debug_dev = STDERR
23
+ $DEBUG = true
24
+ require 'pp'
25
+ pp client.send(*ARGV)
26
+ end
21
27
  exit
22
28
  end
23
29
 
@@ -1,98 +1,52 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- # httpclient shell command.
3
+ # jsonclient shell command.
4
4
  #
5
- # Usage: 1) % httpclient get https://www.google.co.jp/ q=ruby
6
- # Usage: 2) % httpclient
5
+ # Usage: 1) % jsonclient post https://www.example.com/ content.json
6
+ # Usage: 2) % jsonclient
7
7
  #
8
8
  # For 1) it issues a GET request to the given URI and shows the wiredump and
9
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
- require 'json'
10
+ # JSONClient as 'self'. You can call JSONClient instance methods like;
11
+ # > post "https://www.example.com/resource", {'hello' => 'world'}
12
+ require 'jsonclient'
14
13
 
15
- module HTTP
16
- class Message
17
- # Returns JSON object of message body
18
- alias original_content content
19
- def content
20
- if JSONClient::CONTENT_TYPE_JSON_REGEX =~ content_type
21
- JSON.parse(original_content)
22
- else
23
- original_content
24
- end
25
- end
14
+ method = ARGV.shift
15
+ url = ARGV.shift
16
+ body = []
17
+ if ['post', 'put'].include?(method)
18
+ if ARGV.size == 1 && File.exist?(ARGV[0])
19
+ body << File.read(ARGV[0])
20
+ else
21
+ body << ARGF.read
26
22
  end
27
23
  end
28
-
29
-
30
- # JSONClient provides JSON related methods in addition to HTTPClient.
31
- class JSONClient < HTTPClient
32
- CONTENT_TYPE_JSON_REGEX = /(application|text)\/(x-)?json/i
33
-
34
- attr_accessor :content_type_json
35
-
36
- class JSONRequestHeaderFilter
37
- attr_accessor :replace
38
-
39
- def initialize(client)
40
- @client = client
41
- @replace = false
42
- end
43
-
44
- def filter_request(req)
45
- req.header['content-type'] = @client.content_type_json if @replace
46
- end
47
-
48
- def filter_response(req, res)
49
- @replace = false
50
- end
51
- end
52
-
53
- def initialize(*args)
54
- super
55
- @header_filter = JSONRequestHeaderFilter.new(self)
56
- @request_filter << @header_filter
57
- @content_type_json = 'application/json; charset=utf-8'
58
- end
59
-
60
- def post(uri, *args, &block)
61
- @header_filter.replace = true
62
- request(:post, uri, jsonify(argument_to_hash(args, :body, :header, :follow_redirect)), &block)
63
- end
64
-
65
- def put(uri, *args, &block)
66
- @header_filter.replace = true
67
- request(:put, uri, jsonify(argument_to_hash(args, :body, :header)), &block)
68
- end
69
-
70
- private
71
-
72
- def jsonify(hash)
73
- if hash[:body] && hash[:body].is_a?(Hash)
74
- hash[:body] = JSON.generate(hash[:body])
24
+ if method && url
25
+ require 'pp'
26
+ client = JSONClient.new
27
+ client.debug_dev = STDERR if $DEBUG
28
+ res = client.send(method, url, *body)
29
+ STDERR.puts('RESPONSE HEADER: ')
30
+ PP.pp(res.headers, STDERR)
31
+ if res.ok?
32
+ begin
33
+ puts JSON.pretty_generate(res.content)
34
+ rescue JSON::GeneratorError
35
+ puts res.content
75
36
  end
76
- hash
37
+ exit 0
38
+ else
39
+ STDERR.puts res.content
40
+ exit 1
77
41
  end
78
42
  end
79
43
 
80
- METHODS = ['head', 'get', 'post', 'put', 'delete', 'options', 'propfind', 'proppatch', 'trace']
81
- if ARGV.size >= 2 && METHODS.include?(ARGV[0])
82
- client = JSONClient.new
83
- client.debug_dev = STDERR
84
- $DEBUG = true
85
- require 'pp'
86
- pp client.send(*ARGV)
87
- exit
88
- end
89
-
90
44
  require 'irb'
91
45
  require 'irb/completion'
92
46
 
93
47
  class Runner
94
48
  def initialize
95
- @httpclient = HTTPClient.new
49
+ @httpclient = JSONClient.new
96
50
  end
97
51
 
98
52
  def method_missing(msg, *a, &b)
@@ -124,7 +78,7 @@ class Runner
124
78
  end
125
79
 
126
80
  def to_s
127
- 'HTTPClient'
81
+ 'JSONClient'
128
82
  end
129
83
  end
130
84
 
@@ -308,7 +308,7 @@ class HTTPClient
308
308
 
309
309
  # HTTPClient::SSLConfig:: SSL configurator.
310
310
  attr_reader :ssl_config
311
- # WebAgent::CookieManager:: Cookies configurator.
311
+ # HTTPClient::CookieManager:: Cookies configurator.
312
312
  attr_accessor :cookie_manager
313
313
  # An array of response HTTP message body String which is used for loop-back
314
314
  # test. See test/* to see how to use it. If you want to do loop-back test
@@ -415,7 +415,7 @@ class HTTPClient
415
415
  @session_manager.agent_name = agent_name || DEFAULT_AGENT_NAME
416
416
  @session_manager.from = from
417
417
  @session_manager.ssl_config = @ssl_config = SSLConfig.new(self)
418
- @cookie_manager = WebAgent::CookieManager.new
418
+ @cookie_manager = CookieManager.new
419
419
  @follow_redirect_count = 10
420
420
  load_environment
421
421
  self.proxy = proxy if proxy
@@ -576,7 +576,6 @@ class HTTPClient
576
576
 
577
577
  # Try to save Cookies to the file specified in set_cookie_store. Unexpected
578
578
  # error will be raised if you don't call set_cookie_store first.
579
- # (interface mismatch between WebAgent::CookieManager implementation)
580
579
  def save_cookie_store
581
580
  @cookie_manager.save_cookies
582
581
  end
@@ -927,6 +926,11 @@ class HTTPClient
927
926
  private
928
927
 
929
928
  class RetryableResponse < StandardError # :nodoc:
929
+ attr_reader :res
930
+
931
+ def initialize(res = nil)
932
+ @res = res
933
+ end
930
934
  end
931
935
 
932
936
  class KeepAliveDisconnected < StandardError # :nodoc:
@@ -947,24 +951,37 @@ private
947
951
  end
948
952
 
949
953
  def do_request(method, uri, query, body, header, &block)
950
- conn = Connection.new
951
954
  res = nil
952
955
  if HTTP::Message.file?(body)
953
956
  pos = body.pos rescue nil
954
957
  end
955
958
  retry_count = @session_manager.protocol_retry_count
956
959
  proxy = no_proxy?(uri) ? nil : @proxy
960
+ previous_request = previous_response = nil
957
961
  while retry_count > 0
958
962
  body.pos = pos if pos
959
963
  req = create_request(method, uri, query, body, header)
964
+ if previous_request
965
+ # to remember IO positions to read
966
+ req.http_body.positions = previous_request.http_body.positions
967
+ end
960
968
  begin
961
969
  protect_keep_alive_disconnected do
962
- do_get_block(req, proxy, conn, &block)
970
+ # TODO: remove Connection.new
971
+ # We want to delete Connection usage in do_get_block but Newrelic gem depends on it.
972
+ # https://github.com/newrelic/rpm/blob/master/lib/new_relic/agent/instrumentation/httpclient.rb#L34-L36
973
+ conn = Connection.new
974
+ res = do_get_block(req, proxy, conn, &block)
975
+ # Webmock's do_get_block returns ConditionVariable
976
+ if !res.respond_to?(:previous)
977
+ res = conn.pop
978
+ end
963
979
  end
964
- res = conn.pop
980
+ res.previous = previous_response
965
981
  break
966
- rescue RetryableResponse
967
- res = conn.pop
982
+ rescue RetryableResponse => e
983
+ previous_request = req
984
+ previous_response = res = e.res
968
985
  retry_count -= 1
969
986
  end
970
987
  end
@@ -1029,15 +1046,21 @@ private
1029
1046
  pos = body.pos rescue nil
1030
1047
  end
1031
1048
  retry_number = 0
1049
+ previous = nil
1050
+ request_query = query
1032
1051
  while retry_number < @follow_redirect_count
1033
1052
  body.pos = pos if pos
1034
- res = do_request(method, uri, query, body, header, &filtered_block)
1053
+ res = do_request(method, uri, request_query, body, header, &filtered_block)
1054
+ res.previous = previous
1035
1055
  if res.redirect?
1036
1056
  if res.header['location'].empty?
1037
1057
  raise BadResponseError.new("Missing Location header for redirect", res)
1038
1058
  end
1039
1059
  method = :get if res.see_other? # See RFC2616 10.3.4
1040
1060
  uri = urify(@redirect_uri_callback.call(uri, res))
1061
+ # To avoid duped query parameter. 'location' must include query part.
1062
+ request_query = nil
1063
+ previous = res
1041
1064
  retry_number += 1
1042
1065
  else
1043
1066
  return res
@@ -1058,10 +1081,13 @@ private
1058
1081
  begin
1059
1082
  yield
1060
1083
  rescue KeepAliveDisconnected => e
1061
- if e.sess
1062
- @session_manager.invalidate(e.sess.dest)
1084
+ # Force to create new connection
1085
+ Thread.current[:HTTPClient_AcquireNewConnection] = true
1086
+ begin
1087
+ yield
1088
+ ensure
1089
+ Thread.current[:HTTPClient_AcquireNewConnection] = false
1063
1090
  end
1064
- yield
1065
1091
  end
1066
1092
  end
1067
1093
 
@@ -1101,8 +1127,11 @@ private
1101
1127
  header.each do |key, value|
1102
1128
  req.header.add(key.to_s, value)
1103
1129
  end
1104
- if @cookie_manager && cookie = @cookie_manager.find(uri)
1105
- req.header.add('Cookie', cookie)
1130
+ if @cookie_manager
1131
+ cookie_value = @cookie_manager.cookie_value(uri)
1132
+ if cookie_value
1133
+ req.header.add('Cookie', cookie_value)
1134
+ end
1106
1135
  end
1107
1136
  req
1108
1137
  end
@@ -1152,8 +1181,9 @@ private
1152
1181
  end
1153
1182
  if str = @test_loopback_response.shift
1154
1183
  dump_dummy_request_response(req.http_body.dump, str) if @debug_dev
1155
- conn.push(HTTP::Message.new_response(str, req.header))
1156
- return
1184
+ res = HTTP::Message.new_response(str, req.header)
1185
+ conn.push(res)
1186
+ return res
1157
1187
  end
1158
1188
  content = block ? nil : ''
1159
1189
  res = HTTP::Message.new_response(content, req.header)
@@ -1178,8 +1208,9 @@ private
1178
1208
  filter.filter_response(req, res)
1179
1209
  }
1180
1210
  if commands.find { |command| command == :retry }
1181
- raise RetryableResponse.new
1211
+ raise RetryableResponse.new(res)
1182
1212
  end
1213
+ res
1183
1214
  end
1184
1215
 
1185
1216
  def do_get_stream(req, proxy, conn)
@@ -1209,6 +1240,7 @@ private
1209
1240
  filter.filter_response(req, res)
1210
1241
  }
1211
1242
  # ignore commands (not retryable in async mode)
1243
+ res
1212
1244
  end
1213
1245
 
1214
1246
  def do_get_header(req, res, sess)
@@ -95,6 +95,13 @@ class HTTPClient
95
95
  @authenticator.each do |auth|
96
96
  next unless auth.set? # hasn't be set, don't use it
97
97
  if cred = auth.get(req)
98
+ if cred == :skip
99
+ # some authenticator (NTLM and Negotiate) does not
100
+ # need to send extra header after authorization. In such case
101
+ # it should block other authenticators to respond and :skip is
102
+ # the marker for such case.
103
+ return
104
+ end
98
105
  req.header.set('Authorization', auth.scheme + " " + cred)
99
106
  return
100
107
  end
@@ -179,6 +186,13 @@ class HTTPClient
179
186
  @authenticator.each do |auth|
180
187
  next unless auth.set? # hasn't be set, don't use it
181
188
  if cred = auth.get(req)
189
+ if cred == :skip
190
+ # some authenticator (NTLM and Negotiate) does not
191
+ # need to send extra header after authorization. In such case
192
+ # it should block other authenticators to respond and :skip is
193
+ # the marker for such case.
194
+ return
195
+ end
182
196
  req.header.set('Proxy-Authorization', auth.scheme + " " + cred)
183
197
  return
184
198
  end
@@ -208,36 +222,43 @@ class HTTPClient
208
222
  end
209
223
  end
210
224
 
211
- # Authentication filter for handling BasicAuth negotiation.
212
- # Used in WWWAuth and ProxyAuth.
213
- class BasicAuth
225
+ # Authentication filter base class.
226
+ class AuthBase
214
227
  include HTTPClient::Util
215
- include Mutex_m
216
228
 
217
229
  # Authentication scheme.
218
230
  attr_reader :scheme
219
231
 
232
+ def initialize(scheme)
233
+ @scheme = scheme
234
+ @challenge = {}
235
+ end
236
+
237
+ # Resets challenge state. Do not send '*Authorization' header until the
238
+ # server sends '*Authentication' again.
239
+ def reset_challenge
240
+ synchronize do
241
+ @challenge.clear
242
+ end
243
+ end
244
+ end
245
+
246
+ # Authentication filter for handling BasicAuth negotiation.
247
+ # Used in WWWAuth and ProxyAuth.
248
+ class BasicAuth < AuthBase
249
+ include Mutex_m
250
+
220
251
  # Send Authorization Header without receiving 401
221
252
  attr_accessor :force_auth
222
253
 
223
254
  # Creates new BasicAuth filter.
224
255
  def initialize
225
- super
256
+ super('Basic')
226
257
  @cred = nil
227
258
  @auth = {}
228
- @challenge = {}
229
- @scheme = "Basic"
230
259
  @force_auth = false
231
260
  end
232
261
 
233
- # Resets challenge state. Do not send '*Authorization' header until the
234
- # server sends '*Authentication' again.
235
- def reset_challenge
236
- synchronize {
237
- @challenge.clear
238
- }
239
- end
240
-
241
262
  # Set authentication credential.
242
263
  # uri == nil for generic purpose (allow to use user/password for any URL).
243
264
  def set(uri, user, passwd)
@@ -283,7 +304,6 @@ class HTTPClient
283
304
  end
284
305
 
285
306
  class ProxyBasicAuth < BasicAuth
286
-
287
307
  def set(uri, user, passwd)
288
308
  synchronize do
289
309
  @cred = ["#{user}:#{passwd}"].pack('m').tr("\n", '')
@@ -308,27 +328,14 @@ class HTTPClient
308
328
 
309
329
  # Authentication filter for handling DigestAuth negotiation.
310
330
  # Used in WWWAuth.
311
- class DigestAuth
331
+ class DigestAuth < AuthBase
312
332
  include Mutex_m
313
333
 
314
- # Authentication scheme.
315
- attr_reader :scheme
316
-
317
334
  # Creates new DigestAuth filter.
318
335
  def initialize
319
- super
336
+ super('Digest')
320
337
  @auth = {}
321
- @challenge = {}
322
338
  @nonce_count = 0
323
- @scheme = "Digest"
324
- end
325
-
326
- # Resets challenge state. Do not send '*Authorization' header until the
327
- # server sends '*Authentication' again.
328
- def reset_challenge
329
- synchronize do
330
- @challenge.clear
331
- end
332
339
  end
333
340
 
334
341
  # Set authentication credential.
@@ -483,41 +490,28 @@ class HTTPClient
483
490
  true
484
491
  }
485
492
  end
486
-
487
493
  end
488
494
 
489
495
  # Authentication filter for handling Negotiate/NTLM negotiation.
490
496
  # Used in WWWAuth and ProxyAuth.
491
497
  #
492
498
  # NegotiateAuth depends on 'ruby/ntlm' module.
493
- class NegotiateAuth
499
+ class NegotiateAuth < AuthBase
494
500
  include Mutex_m
495
501
 
496
- # Authentication scheme.
497
- attr_reader :scheme
498
502
  # NTLM opt for ruby/ntlm. {:ntlmv2 => true} by default.
499
503
  attr_reader :ntlm_opt
500
504
 
501
505
  # Creates new NegotiateAuth filter.
502
506
  def initialize(scheme = "Negotiate")
503
- super()
507
+ super(scheme)
504
508
  @auth = {}
505
509
  @auth_default = nil
506
- @challenge = {}
507
- @scheme = scheme
508
510
  @ntlm_opt = {
509
511
  :ntlmv2 => true
510
512
  }
511
513
  end
512
514
 
513
- # Resets challenge state. Do not send '*Authorization' header until the
514
- # server sends '*Authentication' again.
515
- def reset_challenge
516
- synchronize do
517
- @challenge.clear
518
- end
519
- end
520
-
521
515
  # Set authentication credential.
522
516
  # uri == nil for generic purpose (allow to use user/password for any URL).
523
517
  def set(uri, user, passwd)
@@ -561,16 +555,19 @@ class HTTPClient
561
555
  when :init
562
556
  t1 = Net::NTLM::Message::Type1.new
563
557
  t1.domain = domain if domain
564
- return t1.encode64
558
+ t1.encode64
565
559
  when :response
566
560
  t2 = Net::NTLM::Message.decode64(authphrase)
567
561
  param = {:user => user, :password => passwd}
568
562
  param[:domain] = domain if domain
569
563
  t3 = t2.response(param, @ntlm_opt.dup)
570
- @challenge.delete(domain_uri)
571
- return t3.encode64
564
+ @challenge[target_uri][:state] = :done
565
+ t3.encode64
566
+ when :done
567
+ :skip
568
+ else
569
+ nil
572
570
  end
573
- nil
574
571
  }
575
572
  end
576
573
 
@@ -596,25 +593,12 @@ class HTTPClient
596
593
  # Used in ProxyAuth.
597
594
  #
598
595
  # SSPINegotiateAuth depends on 'win32/sspi' module.
599
- class SSPINegotiateAuth
596
+ class SSPINegotiateAuth < AuthBase
600
597
  include Mutex_m
601
598
 
602
- # Authentication scheme.
603
- attr_reader :scheme
604
-
605
599
  # Creates new SSPINegotiateAuth filter.
606
600
  def initialize
607
- super
608
- @challenge = {}
609
- @scheme = "Negotiate"
610
- end
611
-
612
- # Resets challenge state. Do not send '*Authorization' header until the
613
- # server sends '*Authentication' again.
614
- def reset_challenge
615
- synchronize do
616
- @challenge.clear
617
- end
601
+ super('Negotiate')
618
602
  end
619
603
 
620
604
  # Set authentication credential.
@@ -646,21 +630,24 @@ class HTTPClient
646
630
  when :init
647
631
  if defined?(Win32::SSPI)
648
632
  authenticator = param[:authenticator] = Win32::SSPI::NegotiateAuth.new
649
- return authenticator.get_initial_token(@scheme)
633
+ authenticator.get_initial_token(@scheme)
650
634
  else # use GSSAPI
651
635
  authenticator = param[:authenticator] = GSSAPI::Simple.new(domain_uri.host, 'HTTP')
652
636
  # Base64 encode the context token
653
- return [authenticator.init_context].pack('m').gsub(/\n/,'')
637
+ [authenticator.init_context].pack('m').gsub(/\n/,'')
654
638
  end
655
639
  when :response
656
- @challenge.delete(domain_uri)
640
+ @challenge[target_uri][:state] = :done
657
641
  if defined?(Win32::SSPI)
658
- return authenticator.complete_authentication(authphrase)
642
+ authenticator.complete_authentication(authphrase)
659
643
  else # use GSSAPI
660
- return authenticator.init_context(authphrase.unpack('m').pop)
644
+ authenticator.init_context(authphrase.unpack('m').pop)
661
645
  end
646
+ when :done
647
+ :skip
648
+ else
649
+ nil
662
650
  end
663
- nil
664
651
  }
665
652
  end
666
653
 
@@ -692,13 +679,9 @@ class HTTPClient
692
679
  # CAUTION: This impl does NOT support OAuth Request Body Hash spec for now.
693
680
  # http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/oauth-bodyhash.html
694
681
  #
695
- class OAuth
696
- include HTTPClient::Util
682
+ class OAuth < AuthBase
697
683
  include Mutex_m
698
684
 
699
- # Authentication scheme.
700
- attr_reader :scheme
701
-
702
685
  class Config
703
686
  include HTTPClient::Util
704
687
 
@@ -768,23 +751,13 @@ class HTTPClient
768
751
 
769
752
  # Creates new DigestAuth filter.
770
753
  def initialize
771
- super
754
+ super('OAuth')
772
755
  @config = nil # common config
773
756
  @auth = {} # configs for each site
774
- @challenge = {}
775
757
  @nonce_count = 0
776
758
  @signature_handler = {
777
759
  'HMAC-SHA1' => method(:sign_hmac_sha1)
778
760
  }
779
- @scheme = "OAuth"
780
- end
781
-
782
- # Resets challenge state. Do not send '*Authorization' header until the
783
- # server sends '*Authentication' again.
784
- def reset_challenge
785
- synchronize do
786
- @challenge.clear
787
- end
788
761
  end
789
762
 
790
763
  # Set authentication credential.