httpclient 2.5.3.3 → 2.6.0

Sign up to get free protection for your applications and to get access to all the features.
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.