httpclient 2.2.1 → 2.2.2

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.
data/lib/httpclient.rb CHANGED
@@ -11,6 +11,7 @@ require 'stringio'
11
11
  require 'digest/sha1'
12
12
 
13
13
  # Extra library
14
+ require 'httpclient/version'
14
15
  require 'httpclient/util'
15
16
  require 'httpclient/ssl_config'
16
17
  require 'httpclient/connection'
@@ -229,7 +230,6 @@ require 'httpclient/cookie'
229
230
  # ruby -rhttpclient -e 'p HTTPClient.head(ARGV.shift).header["last-modified"]' http://dev.ctor.org/
230
231
  #
231
232
  class HTTPClient
232
- VERSION = '2.2.1'
233
233
  RUBY_VERSION_STRING = "ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]"
234
234
  /: (\S+) (\S+)/ =~ %q$Id$
235
235
  LIB_NAME = "(#{$1}/#{$2}, #{RUBY_VERSION_STRING})"
@@ -335,6 +335,8 @@ class HTTPClient
335
335
  attr_proxy(:send_timeout, true)
336
336
  # Response receiving timeout in sec.
337
337
  attr_proxy(:receive_timeout, true)
338
+ # Reuse the same connection within this timeout in sec. from last used.
339
+ attr_proxy(:keep_alive_timeout, true)
338
340
  # Negotiation retry count for authentication. 5 by default.
339
341
  attr_proxy(:protocol_retry_count, true)
340
342
  # if your ruby is older than 2005-09-06, do not set socket_sync = false to
@@ -1060,7 +1062,7 @@ private
1060
1062
  do_get_header(req, res, sess)
1061
1063
  conn.push(res)
1062
1064
  sess.get_body do |part|
1063
- force_binary(part)
1065
+ set_encoding(part, res.body_encoding)
1064
1066
  if block
1065
1067
  block.call(res, part)
1066
1068
  else
@@ -1096,7 +1098,7 @@ private
1096
1098
  do_get_header(req, res, sess)
1097
1099
  conn.push(res)
1098
1100
  sess.get_body do |part|
1099
- force_binary(part)
1101
+ set_encoding(part, res.body_encoding)
1100
1102
  pipew.write(part)
1101
1103
  end
1102
1104
  pipew.close
@@ -1109,9 +1111,7 @@ private
1109
1111
 
1110
1112
  def do_get_header(req, res, sess)
1111
1113
  res.http_version, res.status, res.reason, headers = sess.get_header
1112
- headers.each do |key, value|
1113
- res.header.add(key, value)
1114
- end
1114
+ res.header.set_headers(headers)
1115
1115
  if @cookie_manager
1116
1116
  res.header['set-cookie'].each do |cookie|
1117
1117
  @cookie_manager.parse(cookie, req.header.request_uri)
@@ -1125,4 +1125,8 @@ private
1125
1125
  @debug_dev << "\n\n= Dummy Response\n\n"
1126
1126
  @debug_dev << res
1127
1127
  end
1128
+
1129
+ def set_encoding(str, encoding)
1130
+ str.force_encoding(encoding) if encoding
1131
+ end
1128
1132
  end
@@ -7,6 +7,7 @@
7
7
 
8
8
 
9
9
  require 'time'
10
+ require 'open-uri' # for encoding
10
11
 
11
12
 
12
13
  # A namespace module for HTTP Message definitions used by HTTPClient.
@@ -122,6 +123,8 @@ module HTTP
122
123
  attr_accessor :body_charset # :nodoc:
123
124
  # Used for dumping response.
124
125
  attr_accessor :body_date # :nodoc:
126
+ # Used for keeping content encoding.
127
+ attr_reader :body_encoding # :nodoc:
125
128
 
126
129
  # HTTP response status code to reason phrase mapping definition.
127
130
  STATUS_CODE_MAP = {
@@ -166,6 +169,7 @@ module HTTP
166
169
  @body_type = nil
167
170
  @body_charset = nil
168
171
  @body_date = nil
172
+ @body_encoding = nil
169
173
 
170
174
  @is_request = nil
171
175
  @header_item = []
@@ -205,14 +209,31 @@ module HTTP
205
209
  end
206
210
 
207
211
  # Returns 'Content-Type' header value.
208
- def contenttype
212
+ def content_type
209
213
  self['Content-Type'][0]
210
214
  end
211
215
 
212
216
  # Sets 'Content-Type' header value. Overrides if already exists.
213
- def contenttype=(contenttype)
217
+ def content_type=(content_type)
214
218
  delete('Content-Type')
215
- self['Content-Type'] = contenttype
219
+ self['Content-Type'] = content_type
220
+ end
221
+
222
+ alias contenttype content_type
223
+ alias contenttype= content_type=
224
+
225
+ if defined?(Encoding::ASCII_8BIT)
226
+ def set_body_encoding
227
+ if type = self.content_type
228
+ OpenURI::Meta.init(o = '')
229
+ o.meta_add_field('content-type', type)
230
+ @body_encoding = o.encoding
231
+ end
232
+ end
233
+ else
234
+ def set_body_encoding
235
+ @body_encoding = nil
236
+ end
216
237
  end
217
238
 
218
239
  # Sets byte size of message body.
@@ -285,6 +306,13 @@ module HTTP
285
306
  get(key).collect { |item| item[1] }
286
307
  end
287
308
 
309
+ def set_headers(headers)
310
+ headers.each do |key, value|
311
+ add(key, value)
312
+ end
313
+ set_body_encoding
314
+ end
315
+
288
316
  def create_query_uri()
289
317
  if @request_method == 'CONNECT'
290
318
  return "#{@request_uri.host}:#{@request_uri.port}"
@@ -787,7 +815,7 @@ module HTTP
787
815
  end
788
816
 
789
817
  def escape_query(query) # :nodoc:
790
- query.sort_by { |attr, value| attr.to_s }.collect { |attr, value|
818
+ query.collect { |attr, value|
791
819
  if value.respond_to?(:read)
792
820
  value = value.read
793
821
  end
@@ -796,12 +824,14 @@ module HTTP
796
824
  end
797
825
 
798
826
  # from CGI.escape
799
- def escape(str) # :nodoc:
800
- if defined?(Encoding::ASCII_8BIT)
827
+ if defined?(Encoding::ASCII_8BIT)
828
+ def escape(str) # :nodoc:
801
829
  str.dup.force_encoding(Encoding::ASCII_8BIT).gsub(/([^ a-zA-Z0-9_.-]+)/) {
802
830
  '%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
803
831
  }.tr(' ', '+')
804
- else
832
+ end
833
+ else
834
+ def escape(str) # :nodoc:
805
835
  str.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
806
836
  '%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
807
837
  }.tr(' ', '+')
@@ -915,14 +945,21 @@ module HTTP
915
945
  @http_header.reason_phrase = reason
916
946
  end
917
947
 
948
+ # Returns 'Content-Type' header value.
949
+ def content_type
950
+ @http_header.content_type
951
+ end
952
+
918
953
  # Sets 'Content-Type' header value. Overrides if already exists.
919
- def contenttype
920
- @http_header.contenttype
954
+ def content_type=(content_type)
955
+ @http_header.content_type = content_type
921
956
  end
957
+ alias contenttype content_type
958
+ alias contenttype= content_type=
922
959
 
923
- # Returns 'Content-Type' header value.
924
- def contenttype=(contenttype)
925
- @http_header.contenttype = contenttype
960
+ # Returns content encoding
961
+ def body_encoding
962
+ @http_header.body_encoding
926
963
  end
927
964
 
928
965
  # Returns a content of message body. A String or an IO.
@@ -102,6 +102,7 @@ class HTTPClient
102
102
  attr_accessor :connect_retry
103
103
  attr_accessor :send_timeout
104
104
  attr_accessor :receive_timeout
105
+ attr_accessor :keep_alive_timeout
105
106
  attr_accessor :read_block_size
106
107
  attr_accessor :protocol_retry_count
107
108
 
@@ -129,7 +130,8 @@ class HTTPClient
129
130
  @connect_timeout = 60
130
131
  @connect_retry = 1
131
132
  @send_timeout = 120
132
- @receive_timeout = 60 # For each read_block_size bytes
133
+ @receive_timeout = 60 # For each read_block_size bytes
134
+ @keep_alive_timeout = 15 # '15' is from Apache 2 default
133
135
  @read_block_size = 1024 * 16 # follows net/http change in 1.8.7
134
136
  @protocol_retry_count = 5
135
137
 
@@ -139,8 +141,9 @@ class HTTPClient
139
141
  @transparent_gzip_decompression = false
140
142
  @socket_local = Site.new
141
143
 
142
- @sess_pool = []
144
+ @sess_pool = {}
143
145
  @sess_pool_mutex = Mutex.new
146
+ @sess_pool_last_checked = Time.now
144
147
  end
145
148
 
146
149
  def proxy=(proxy)
@@ -172,14 +175,15 @@ class HTTPClient
172
175
  close_all
173
176
  end
174
177
 
178
+ # assert: sess.last_used must not be nil
175
179
  def keep(sess)
176
180
  add_cached_session(sess)
177
181
  end
178
182
 
179
183
  def invalidate(site)
180
184
  @sess_pool_mutex.synchronize do
181
- @sess_pool.each do |sess|
182
- if sess.dest == site
185
+ if pool = @sess_pool[site]
186
+ pool.each do |sess|
183
187
  sess.invalidate
184
188
  end
185
189
  end
@@ -189,11 +193,12 @@ class HTTPClient
189
193
  private
190
194
 
191
195
  def open(uri, via_proxy = false)
196
+ site = Site.new(uri)
192
197
  sess = nil
193
- if cached = get_cached_session(uri)
198
+ if cached = get_cached_session(site)
194
199
  sess = cached
195
200
  else
196
- sess = Session.new(@client, Site.new(uri), @agent_name, @from)
201
+ sess = Session.new(@client, site, @agent_name, @from)
197
202
  sess.proxy = via_proxy ? @proxy : nil
198
203
  sess.socket_sync = @socket_sync
199
204
  sess.requested_version = @protocol_version if @protocol_version
@@ -214,8 +219,10 @@ class HTTPClient
214
219
 
215
220
  def close_all
216
221
  @sess_pool_mutex.synchronize do
217
- @sess_pool.each do |sess|
218
- sess.close
222
+ @sess_pool.each do |site, pool|
223
+ pool.each do |sess|
224
+ sess.close
225
+ end
219
226
  end
220
227
  end
221
228
  @sess_pool.clear
@@ -223,7 +230,7 @@ class HTTPClient
223
230
 
224
231
  # This method might not work as you expected...
225
232
  def close(dest)
226
- if cached = get_cached_session(dest)
233
+ if cached = get_cached_session(Site.new(dest))
227
234
  cached.close
228
235
  true
229
236
  else
@@ -231,27 +238,44 @@ class HTTPClient
231
238
  end
232
239
  end
233
240
 
234
- def get_cached_session(uri)
235
- cached = nil
241
+ def get_cached_session(site)
236
242
  @sess_pool_mutex.synchronize do
237
- new_pool = []
238
- @sess_pool.each do |s|
239
- if s.invalidated?
240
- s.close # close & remove from the pool
241
- elsif !cached && s.dest.match(uri)
242
- cached = s
243
- else
244
- new_pool << s
243
+ now = Time.now
244
+ if now > @sess_pool_last_checked + @keep_alive_timeout
245
+ scrub_cached_session(now)
246
+ @sess_pool_last_checked = now
247
+ end
248
+ if pool = @sess_pool[site]
249
+ pool.each_with_index do |sess, idx|
250
+ if valid_session?(sess, now)
251
+ return pool.slice!(idx)
252
+ end
245
253
  end
246
254
  end
247
- @sess_pool = new_pool
248
255
  end
249
- cached
256
+ nil
257
+ end
258
+
259
+ def scrub_cached_session(now)
260
+ @sess_pool.each do |site, pool|
261
+ pool.replace(pool.select { |sess|
262
+ if valid_session?(sess, now)
263
+ true
264
+ else
265
+ sess.close # close & remove from the pool
266
+ false
267
+ end
268
+ })
269
+ end
270
+ end
271
+
272
+ def valid_session?(sess, now)
273
+ !sess.invalidated? and (now <= sess.last_used + @keep_alive_timeout)
250
274
  end
251
275
 
252
276
  def add_cached_session(sess)
253
277
  @sess_pool_mutex.synchronize do
254
- @sess_pool << sess
278
+ (@sess_pool[sess.dest] ||= []).unshift(sess)
255
279
  end
256
280
  end
257
281
  end
@@ -523,6 +547,7 @@ class HTTPClient
523
547
  attr_accessor :test_loopback_http_response
524
548
 
525
549
  attr_accessor :transparent_gzip_decompression
550
+ attr_reader :last_used
526
551
 
527
552
  def initialize(client, dest, agent_name, from)
528
553
  @client = client
@@ -561,6 +586,7 @@ class HTTPClient
561
586
  @readbuf = nil
562
587
 
563
588
  @transparent_gzip_decompression = false
589
+ @last_used = nil
564
590
  end
565
591
 
566
592
  # Send a request to the server
@@ -594,6 +620,7 @@ class HTTPClient
594
620
  @state = :META if @state == :WAIT
595
621
  @next_connection = nil
596
622
  @requests.push(req)
623
+ @last_used = Time.now
597
624
  end
598
625
 
599
626
  def close
@@ -116,11 +116,6 @@ class HTTPClient
116
116
  def https?(uri)
117
117
  uri.scheme.downcase == 'https'
118
118
  end
119
-
120
- def force_binary(str)
121
- str.force_encoding(Encoding::ASCII_8BIT) if defined?(Encoding::ASCII_8BIT)
122
- str
123
- end
124
119
  end
125
120
 
126
121
 
@@ -0,0 +1,3 @@
1
+ class HTTPClient
2
+ VERSION = '2.2.2'
3
+ end
@@ -1356,6 +1356,19 @@ EOS
1356
1356
  end
1357
1357
  end
1358
1358
 
1359
+ def test_body_param_order
1360
+ ary = ('b'..'d').map { |k| ['key2', k] } << ['key1', 'a'] << ['key3', 'z']
1361
+ assert_equal("key2=b&key2=c&key2=d&key1=a&key3=z", HTTP::Message.escape_query(ary))
1362
+ end
1363
+
1364
+ if RUBY_VERSION > "1.9"
1365
+ def test_charset
1366
+ body = @client.get(serverurl + 'charset').body
1367
+ assert_equal(Encoding::EUC_JP, body.encoding)
1368
+ assert_equal('あいうえお'.encode(Encoding::EUC_JP), body)
1369
+ end
1370
+ end
1371
+
1359
1372
  private
1360
1373
 
1361
1374
  def check_query_get(query)
@@ -1379,7 +1392,7 @@ private
1379
1392
  :DocumentRoot => File.dirname(File.expand_path(__FILE__))
1380
1393
  )
1381
1394
  @serverport = @server.config[:Port]
1382
- [:hello, :sleep, :servlet_redirect, :redirect1, :redirect2, :redirect3, :redirect_self, :relative_redirect, :chunked, :largebody, :status, :compressed].each do |sym|
1395
+ [:hello, :sleep, :servlet_redirect, :redirect1, :redirect2, :redirect3, :redirect_self, :relative_redirect, :chunked, :largebody, :status, :compressed, :charset].each do |sym|
1383
1396
  @server.mount(
1384
1397
  "/#{sym}",
1385
1398
  WEBrick::HTTPServlet::ProcHandler.new(method("do_#{sym}").to_proc)
@@ -1456,6 +1469,7 @@ private
1456
1469
  end
1457
1470
 
1458
1471
  def do_compressed(req, res)
1472
+ res['content-type'] = 'application/octet-stream'
1459
1473
  if req.query['enc'] == 'gzip'
1460
1474
  res['content-encoding'] = 'gzip'
1461
1475
  res.body = GZIP_CONTENT
@@ -1465,6 +1479,15 @@ private
1465
1479
  end
1466
1480
  end
1467
1481
 
1482
+ def do_charset(req, res)
1483
+ if RUBY_VERSION > "1.9"
1484
+ res.body = 'あいうえお'.encode("euc-jp")
1485
+ res['Content-Type'] = 'text/plain; charset=euc-jp'
1486
+ else
1487
+ res.body = 'this endpoint is for 1.9 or later'
1488
+ end
1489
+ end
1490
+
1468
1491
  def do_status(req, res)
1469
1492
  res.status = req.query['status'].to_i
1470
1493
  end
@@ -1485,6 +1508,7 @@ private
1485
1508
  end
1486
1509
 
1487
1510
  def do_POST(req, res)
1511
+ res["content-type"] = "text/plain" # iso-8859-1, not US-ASCII
1488
1512
  res.body = 'post,' + req.body.to_s
1489
1513
  res["x-query"] = body_response(req)
1490
1514
  end
data/test/test_ssl.rb CHANGED
@@ -99,16 +99,25 @@ class TestSSL < Test::Unit::TestCase
99
99
  assert_equal("hello", @client.get_content(@url))
100
100
  assert(@verify_callback_called)
101
101
  #
102
- cfg.verify_depth = 1
102
+ unless ENV['TRAVIS']
103
+ # On travis environment, verify_depth seems to not work properly.
104
+ # Ubuntu 10.04 + OpenSSL 0.9.8k issue?
105
+ cfg.verify_depth = 1 # 2 required: root-sub
103
106
  @verify_callback_called = false
104
107
  begin
105
108
  @client.get(@url)
106
- assert(false)
109
+ assert(false, "verify_depth is not supported? #{OpenSSL::OPENSSL_VERSION}")
107
110
  rescue OpenSSL::SSL::SSLError => ssle
108
111
  assert_match(/certificate verify failed/, ssle.message)
109
112
  assert(@verify_callback_called)
110
113
  end
111
114
  #
115
+ cfg.verify_depth = 2 # 2 required: root-sub
116
+ @verify_callback_called = false
117
+ @client.get(@url)
118
+ assert(@verify_callback_called)
119
+ #
120
+ end
112
121
  cfg.verify_depth = nil
113
122
  cfg.cert_store = OpenSSL::X509::Store.new
114
123
  cfg.verify_mode = OpenSSL::SSL::VERIFY_PEER
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: httpclient
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.1
4
+ version: 2.2.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-06-02 00:00:00.000000000 Z
12
+ date: 2011-10-17 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description:
15
15
  email: nahi@ruby-lang.org
@@ -17,56 +17,57 @@ executables: []
17
17
  extensions: []
18
18
  extra_rdoc_files: []
19
19
  files:
20
- - lib/oauthclient.rb
21
- - lib/http-access2.rb
22
- - lib/http-access2/cookie.rb
23
- - lib/http-access2/http.rb
24
- - lib/hexdump.rb
20
+ - lib/httpclient/auth.rb
21
+ - lib/httpclient/ssl_config.rb
22
+ - lib/httpclient/http.rb
25
23
  - lib/httpclient/cacert_sha1.p7s
26
- - lib/httpclient/cookie.rb
27
- - lib/httpclient/session.rb
28
24
  - lib/httpclient/timeout.rb
29
- - lib/httpclient/util.rb
30
- - lib/httpclient/cacert.p7s
31
- - lib/httpclient/ssl_config.rb
25
+ - lib/httpclient/version.rb
32
26
  - lib/httpclient/connection.rb
33
- - lib/httpclient/auth.rb
34
- - lib/httpclient/http.rb
27
+ - lib/httpclient/session.rb
28
+ - lib/httpclient/cacert.p7s
29
+ - lib/httpclient/cookie.rb
30
+ - lib/httpclient/util.rb
31
+ - lib/oauthclient.rb
35
32
  - lib/httpclient.rb
36
- - sample/async.rb
37
- - sample/howto.rb
38
- - sample/cookie.rb
39
- - sample/oauth_buzz.rb
40
- - sample/wcat.rb
41
- - sample/oauth_twitter.rb
33
+ - lib/http-access2.rb
34
+ - lib/http-access2/http.rb
35
+ - lib/http-access2/cookie.rb
36
+ - lib/hexdump.rb
37
+ - sample/auth.rb
42
38
  - sample/dav.rb
43
- - sample/thread.rb
44
39
  - sample/stream.rb
45
- - sample/ssl/0key.pem
40
+ - sample/async.rb
41
+ - sample/wcat.rb
42
+ - sample/ssl/1000cert.pem
43
+ - sample/ssl/1000key.pem
46
44
  - sample/ssl/ssl_client.rb
45
+ - sample/ssl/0key.pem
47
46
  - sample/ssl/htdocs/index.html
48
- - sample/ssl/webrick_httpsd.rb
49
47
  - sample/ssl/0cert.pem
50
- - sample/ssl/1000cert.pem
51
- - sample/ssl/1000key.pem
48
+ - sample/ssl/webrick_httpsd.rb
49
+ - sample/thread.rb
52
50
  - sample/oauth_friendfeed.rb
53
- - sample/auth.rb
51
+ - sample/oauth_twitter.rb
52
+ - sample/cookie.rb
53
+ - sample/howto.rb
54
+ - sample/oauth_buzz.rb
55
+ - test/client.cert
56
+ - test/test_http-access2.rb
57
+ - test/server.cert
58
+ - test/htdigest
59
+ - test/test_ssl.rb
54
60
  - test/test_cookie.rb
61
+ - test/runner.rb
55
62
  - test/htpasswd
56
- - test/test_httpclient.rb
57
63
  - test/test_auth.rb
58
- - test/runner.rb
59
- - test/helper.rb
60
- - test/subca.cert
61
- - test/server.key
62
64
  - test/client.key
65
+ - test/helper.rb
63
66
  - test/ca.cert
64
- - test/htdigest
65
- - test/test_http-access2.rb
66
- - test/server.cert
67
- - test/test_ssl.rb
68
- - test/client.cert
69
67
  - test/sslsvr.rb
68
+ - test/subca.cert
69
+ - test/server.key
70
+ - test/test_httpclient.rb
70
71
  homepage: http://github.com/nahi/httpclient
71
72
  licenses: []
72
73
  post_install_message:
@@ -87,7 +88,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
87
88
  version: '0'
88
89
  requirements: []
89
90
  rubyforge_project:
90
- rubygems_version: 1.8.4
91
+ rubygems_version: 1.8.10
91
92
  signing_key:
92
93
  specification_version: 3
93
94
  summary: gives something like the functionality of libwww-perl (LWP) in Ruby