http 2.2.2 → 3.0.0.pre

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.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +46 -13
  3. data/.travis.yml +17 -12
  4. data/CHANGES.md +25 -1
  5. data/Gemfile +11 -4
  6. data/Guardfile +2 -0
  7. data/README.md +4 -5
  8. data/Rakefile +14 -13
  9. data/http.gemspec +3 -1
  10. data/lib/http.rb +1 -0
  11. data/lib/http/chainable.rb +15 -14
  12. data/lib/http/client.rb +27 -24
  13. data/lib/http/connection.rb +6 -4
  14. data/lib/http/content_type.rb +1 -0
  15. data/lib/http/errors.rb +3 -2
  16. data/lib/http/feature.rb +2 -1
  17. data/lib/http/features/auto_deflate.rb +77 -20
  18. data/lib/http/features/auto_inflate.rb +2 -1
  19. data/lib/http/headers.rb +3 -2
  20. data/lib/http/headers/known.rb +23 -22
  21. data/lib/http/headers/mixin.rb +1 -0
  22. data/lib/http/mime_type.rb +1 -0
  23. data/lib/http/mime_type/adapter.rb +2 -1
  24. data/lib/http/mime_type/json.rb +2 -1
  25. data/lib/http/options.rb +15 -12
  26. data/lib/http/redirector.rb +4 -3
  27. data/lib/http/request.rb +25 -10
  28. data/lib/http/request/body.rb +67 -0
  29. data/lib/http/request/writer.rb +32 -37
  30. data/lib/http/response.rb +17 -2
  31. data/lib/http/response/body.rb +16 -12
  32. data/lib/http/response/parser.rb +1 -0
  33. data/lib/http/response/status.rb +1 -0
  34. data/lib/http/response/status/reasons.rb +1 -0
  35. data/lib/http/timeout/global.rb +1 -0
  36. data/lib/http/timeout/null.rb +2 -1
  37. data/lib/http/timeout/per_operation.rb +19 -6
  38. data/lib/http/uri.rb +8 -2
  39. data/lib/http/version.rb +1 -1
  40. data/spec/lib/http/client_spec.rb +104 -4
  41. data/spec/lib/http/content_type_spec.rb +1 -0
  42. data/spec/lib/http/features/auto_deflate_spec.rb +32 -64
  43. data/spec/lib/http/features/auto_inflate_spec.rb +1 -0
  44. data/spec/lib/http/headers/mixin_spec.rb +1 -0
  45. data/spec/lib/http/headers_spec.rb +36 -35
  46. data/spec/lib/http/options/body_spec.rb +1 -0
  47. data/spec/lib/http/options/features_spec.rb +1 -0
  48. data/spec/lib/http/options/form_spec.rb +1 -0
  49. data/spec/lib/http/options/headers_spec.rb +2 -1
  50. data/spec/lib/http/options/json_spec.rb +1 -0
  51. data/spec/lib/http/options/new_spec.rb +2 -1
  52. data/spec/lib/http/options/proxy_spec.rb +1 -0
  53. data/spec/lib/http/options_spec.rb +1 -0
  54. data/spec/lib/http/redirector_spec.rb +1 -0
  55. data/spec/lib/http/request/body_spec.rb +138 -0
  56. data/spec/lib/http/request/writer_spec.rb +44 -74
  57. data/spec/lib/http/request_spec.rb +14 -0
  58. data/spec/lib/http/response/body_spec.rb +20 -4
  59. data/spec/lib/http/response/status_spec.rb +27 -26
  60. data/spec/lib/http/response_spec.rb +10 -0
  61. data/spec/lib/http/uri_spec.rb +11 -0
  62. data/spec/lib/http_spec.rb +18 -6
  63. data/spec/regression_specs.rb +1 -0
  64. data/spec/spec_helper.rb +1 -0
  65. data/spec/support/black_hole.rb +9 -2
  66. data/spec/support/capture_warning.rb +1 -0
  67. data/spec/support/dummy_server.rb +2 -1
  68. data/spec/support/dummy_server/servlet.rb +1 -1
  69. data/spec/support/fakeio.rb +21 -0
  70. data/spec/support/http_handling_shared.rb +1 -0
  71. data/spec/support/proxy_server.rb +1 -0
  72. data/spec/support/servers/config.rb +1 -0
  73. data/spec/support/servers/runner.rb +1 -0
  74. data/spec/support/ssl_helper.rb +3 -2
  75. metadata +20 -9
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require "forwardable"
3
4
 
4
5
  require "http/form_data"
@@ -23,7 +24,7 @@ module HTTP
23
24
  end
24
25
 
25
26
  # Make an HTTP request
26
- def request(verb, uri, opts = {})
27
+ def request(verb, uri, opts = {}) # rubocop:disable Style/OptionHash
27
28
  opts = @default_options.merge(opts)
28
29
  uri = make_request_uri(uri, opts)
29
30
  headers = make_request_headers(opts)
@@ -31,11 +32,12 @@ module HTTP
31
32
  proxy = opts.proxy
32
33
 
33
34
  req = HTTP::Request.new(
34
- :verb => verb,
35
- :uri => uri,
36
- :headers => headers,
37
- :proxy => proxy,
38
- :body => body
35
+ :verb => verb,
36
+ :uri => uri,
37
+ :headers => headers,
38
+ :proxy => proxy,
39
+ :body => body,
40
+ :auto_deflate => opts.feature(:auto_deflate)
39
41
  )
40
42
 
41
43
  res = perform(req, opts)
@@ -146,29 +148,30 @@ module HTTP
146
148
  headers[Headers::COOKIE] = cookies
147
149
  end
148
150
 
151
+ if (auto_deflate = opts.feature(:auto_deflate))
152
+ # We need to delete Content-Length header. It will be set automatically
153
+ # by HTTP::Request::Writer
154
+ headers.delete(Headers::CONTENT_LENGTH)
155
+
156
+ headers[Headers::CONTENT_ENCODING] = auto_deflate.method
157
+ end
158
+
149
159
  headers
150
160
  end
151
161
 
152
162
  # Create the request body object to send
153
163
  def make_request_body(opts, headers)
154
- request_body =
155
- case
156
- when opts.body
157
- opts.body
158
- when opts.form
159
- form = HTTP::FormData.create opts.form
160
- headers[Headers::CONTENT_TYPE] ||= form.content_type
161
- headers[Headers::CONTENT_LENGTH] ||= form.content_length
162
- form.to_s
163
- when opts.json
164
- body = MimeType[:json].encode opts.json
165
- headers[Headers::CONTENT_TYPE] ||= "application/json; charset=#{body.encoding.name}"
166
- body
167
- end
168
- if (auto_deflate = opts.feature(:auto_deflate))
169
- auto_deflate.deflate(headers, request_body)
170
- else
171
- request_body
164
+ case
165
+ when opts.body
166
+ opts.body
167
+ when opts.form
168
+ form = HTTP::FormData.create opts.form
169
+ headers[Headers::CONTENT_TYPE] ||= form.content_type
170
+ form
171
+ when opts.json
172
+ body = MimeType[:json].encode opts.json
173
+ headers[Headers::CONTENT_TYPE] ||= "application/json; charset=#{body.encoding.name}"
174
+ body
172
175
  end
173
176
  end
174
177
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require "forwardable"
3
4
 
4
5
  require "http/headers"
@@ -10,17 +11,17 @@ module HTTP
10
11
  extend Forwardable
11
12
 
12
13
  # Allowed values for CONNECTION header
13
- KEEP_ALIVE = "Keep-Alive".freeze
14
- CLOSE = "close".freeze
14
+ KEEP_ALIVE = "Keep-Alive"
15
+ CLOSE = "close"
15
16
 
16
17
  # Attempt to read this much data
17
18
  BUFFER_SIZE = 16_384
18
19
 
19
20
  # HTTP/1.0
20
- HTTP_1_0 = "1.0".freeze
21
+ HTTP_1_0 = "1.0"
21
22
 
22
23
  # HTTP/1.1
23
- HTTP_1_1 = "1.1".freeze
24
+ HTTP_1_1 = "1.1"
24
25
 
25
26
  # Returned after HTTP CONNECT (via proxy)
26
27
  attr_reader :proxy_response_headers
@@ -211,6 +212,7 @@ module HTTP
211
212
 
212
213
  value = @socket.readpartial(size)
213
214
  if value == :eof
215
+ @parser << ""
214
216
  :eof
215
217
  elsif value
216
218
  @parser << value
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module HTTP
3
4
  ContentType = Struct.new(:mime_type, :charset) do
4
5
  MIME_TYPE_RE = %r{^([^/]+/[^;]+)(?:$|;)}
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module HTTP
3
4
  # Generic error
4
5
  class Error < StandardError; end
@@ -18,6 +19,6 @@ module HTTP
18
19
  # Generic Timeout error
19
20
  class TimeoutError < Error; end
20
21
 
21
- # Header name is invalid
22
- class InvalidHeaderNameError < Error; end
22
+ # Header value is of unexpected format (similar to Net::HTTPHeaderSyntaxError)
23
+ class HeaderError < Error; end
23
24
  end
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module HTTP
3
4
  class Feature
4
- def initialize(opts = {})
5
+ def initialize(opts = {}) # rubocop:disable Style/OptionHash
5
6
  @opts = opts
6
7
  end
7
8
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "zlib"
4
+ require "tempfile"
4
5
 
5
6
  module HTTP
6
7
  module Features
@@ -12,32 +13,88 @@ module HTTP
12
13
 
13
14
  @method = @opts.key?(:method) ? @opts[:method].to_s : "gzip"
14
15
 
15
- raise Error, "Only gzip and deflate methods are supported" unless %w(gzip deflate).include?(@method)
16
+ raise Error, "Only gzip and deflate methods are supported" unless %w[gzip deflate].include?(@method)
16
17
  end
17
18
 
18
- def deflate(headers, body)
19
- return body unless body
20
- return body unless body.is_a?(String)
19
+ def deflated_body(body)
20
+ case method
21
+ when "gzip"
22
+ GzippedBody.new(body)
23
+ when "deflate"
24
+ DeflatedBody.new(body)
25
+ else
26
+ raise ArgumentError, "Unsupported deflate method: #{method}"
27
+ end
28
+ end
21
29
 
22
- # We need to delete Content-Length header. It will be set automatically
23
- # by HTTP::Request::Writer
24
- headers.delete(Headers::CONTENT_LENGTH)
30
+ class CompressedBody
31
+ def initialize(body)
32
+ @body = body
33
+ @compressed = nil
34
+ end
25
35
 
26
- headers[Headers::CONTENT_ENCODING] = method
36
+ def size
37
+ compress_all! unless @compressed
38
+ @compressed.size
39
+ end
27
40
 
28
- case method
29
- when "gzip" then
30
- StringIO.open do |out|
31
- Zlib::GzipWriter.wrap(out) do |gz|
32
- gz.write body
33
- gz.finish
34
- out.tap(&:rewind).read
35
- end
41
+ def each(&block)
42
+ return to_enum __method__ unless block
43
+
44
+ if @compressed
45
+ compressed_each(&block)
46
+ else
47
+ compress(&block)
36
48
  end
37
- when "deflate" then
38
- Zlib::Deflate.deflate(body)
39
- else
40
- raise ArgumentError, "Unsupported deflate method: #{method}"
49
+
50
+ self
51
+ end
52
+
53
+ private
54
+
55
+ def compressed_each
56
+ while (data = @compressed.read(Connection::BUFFER_SIZE))
57
+ yield data
58
+ end
59
+ ensure
60
+ @compressed.close!
61
+ end
62
+
63
+ def compress_all!
64
+ @compressed = Tempfile.new("http-compressed_body", :binmode => true)
65
+ compress { |data| @compressed.write(data) }
66
+ @compressed.rewind
67
+ end
68
+ end
69
+
70
+ class GzippedBody < CompressedBody
71
+ def compress(&block)
72
+ gzip = Zlib::GzipWriter.new(BlockIO.new(block))
73
+ @body.each { |chunk| gzip.write(chunk) }
74
+ ensure
75
+ gzip.finish
76
+ end
77
+
78
+ class BlockIO
79
+ def initialize(block)
80
+ @block = block
81
+ end
82
+
83
+ def write(data)
84
+ @block.call(data)
85
+ end
86
+ end
87
+ end
88
+
89
+ class DeflatedBody < CompressedBody
90
+ def compress
91
+ deflater = Zlib::Deflate.new
92
+
93
+ @body.each { |chunk| yield deflater.deflate(chunk) }
94
+
95
+ yield deflater.finish
96
+ ensure
97
+ deflater.close
41
98
  end
42
99
  end
43
100
  end
@@ -1,9 +1,10 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module HTTP
3
4
  module Features
4
5
  class AutoInflate < Feature
5
6
  def stream_for(connection, response)
6
- if %w(deflate gzip x-gzip).include?(response.headers[:content_encoding])
7
+ if %w[deflate gzip x-gzip].include?(response.headers[:content_encoding])
7
8
  Response::Inflater.new(connection)
8
9
  else
9
10
  connection
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require "forwardable"
3
4
 
4
5
  require "http/errors"
@@ -196,7 +197,7 @@ module HTTP
196
197
  # Transforms `name` to canonical HTTP header capitalization
197
198
  #
198
199
  # @param [String] name
199
- # @raise [InvalidHeaderNameError] if normalized name does not
200
+ # @raise [HeaderError] if normalized name does not
200
201
  # match {HEADER_NAME_RE}
201
202
  # @return [String] canonical HTTP header name
202
203
  def normalize_header(name)
@@ -206,7 +207,7 @@ module HTTP
206
207
 
207
208
  return normalized if normalized =~ COMPLIANT_NAME_RE
208
209
 
209
- raise InvalidHeaderNameError, "Invalid HTTP header field name: #{name.inspect}"
210
+ raise HeaderError, "Invalid HTTP header field name: #{name.inspect}"
210
211
  end
211
212
  end
212
213
  end
@@ -1,83 +1,84 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module HTTP
3
4
  class Headers
4
5
  # Content-Types that are acceptable for the response.
5
- ACCEPT = "Accept".freeze
6
+ ACCEPT = "Accept"
6
7
 
7
8
  # The age the object has been in a proxy cache in seconds.
8
- AGE = "Age".freeze
9
+ AGE = "Age"
9
10
 
10
11
  # Authentication credentials for HTTP authentication.
11
- AUTHORIZATION = "Authorization".freeze
12
+ AUTHORIZATION = "Authorization"
12
13
 
13
14
  # Used to specify directives that must be obeyed by all caching mechanisms
14
15
  # along the request-response chain.
15
- CACHE_CONTROL = "Cache-Control".freeze
16
+ CACHE_CONTROL = "Cache-Control"
16
17
 
17
18
  # An HTTP cookie previously sent by the server with Set-Cookie.
18
- COOKIE = "Cookie".freeze
19
+ COOKIE = "Cookie"
19
20
 
20
21
  # Control options for the current connection and list
21
22
  # of hop-by-hop request fields.
22
- CONNECTION = "Connection".freeze
23
+ CONNECTION = "Connection"
23
24
 
24
25
  # The length of the request body in octets (8-bit bytes).
25
- CONTENT_LENGTH = "Content-Length".freeze
26
+ CONTENT_LENGTH = "Content-Length"
26
27
 
27
28
  # The MIME type of the body of the request
28
29
  # (used with POST and PUT requests).
29
- CONTENT_TYPE = "Content-Type".freeze
30
+ CONTENT_TYPE = "Content-Type"
30
31
 
31
32
  # The date and time that the message was sent (in "HTTP-date" format as
32
33
  # defined by RFC 7231 Date/Time Formats).
33
- DATE = "Date".freeze
34
+ DATE = "Date"
34
35
 
35
36
  # An identifier for a specific version of a resource,
36
37
  # often a message digest.
37
- ETAG = "ETag".freeze
38
+ ETAG = "ETag"
38
39
 
39
40
  # Gives the date/time after which the response is considered stale (in
40
41
  # "HTTP-date" format as defined by RFC 7231).
41
- EXPIRES = "Expires".freeze
42
+ EXPIRES = "Expires"
42
43
 
43
44
  # The domain name of the server (for virtual hosting), and the TCP port
44
45
  # number on which the server is listening. The port number may be omitted
45
46
  # if the port is the standard port for the service requested.
46
- HOST = "Host".freeze
47
+ HOST = "Host"
47
48
 
48
49
  # Allows a 304 Not Modified to be returned if content is unchanged.
49
- IF_MODIFIED_SINCE = "If-Modified-Since".freeze
50
+ IF_MODIFIED_SINCE = "If-Modified-Since"
50
51
 
51
52
  # Allows a 304 Not Modified to be returned if content is unchanged.
52
- IF_NONE_MATCH = "If-None-Match".freeze
53
+ IF_NONE_MATCH = "If-None-Match"
53
54
 
54
55
  # The last modified date for the requested object (in "HTTP-date" format as
55
56
  # defined by RFC 7231).
56
- LAST_MODIFIED = "Last-Modified".freeze
57
+ LAST_MODIFIED = "Last-Modified"
57
58
 
58
59
  # Used in redirection, or when a new resource has been created.
59
- LOCATION = "Location".freeze
60
+ LOCATION = "Location"
60
61
 
61
62
  # Authorization credentials for connecting to a proxy.
62
- PROXY_AUTHORIZATION = "Proxy-Authorization".freeze
63
+ PROXY_AUTHORIZATION = "Proxy-Authorization"
63
64
 
64
65
  # An HTTP cookie.
65
- SET_COOKIE = "Set-Cookie".freeze
66
+ SET_COOKIE = "Set-Cookie"
66
67
 
67
68
  # The form of encoding used to safely transfer the entity to the user.
68
69
  # Currently defined methods are: chunked, compress, deflate, gzip, identity.
69
- TRANSFER_ENCODING = "Transfer-Encoding".freeze
70
+ TRANSFER_ENCODING = "Transfer-Encoding"
70
71
 
71
72
  # Indicates what additional content codings have been applied to the
72
73
  # entity-body.
73
- CONTENT_ENCODING = "Content-Encoding".freeze
74
+ CONTENT_ENCODING = "Content-Encoding"
74
75
 
75
76
  # The user agent string of the user agent.
76
- USER_AGENT = "User-Agent".freeze
77
+ USER_AGENT = "User-Agent"
77
78
 
78
79
  # Tells downstream proxies how to match future request headers to decide
79
80
  # whether the cached response can be used rather than requesting a fresh
80
81
  # one from the origin server.
81
- VARY = "Vary".freeze
82
+ VARY = "Vary"
82
83
  end
83
84
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require "forwardable"
3
4
 
4
5
  module HTTP
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module HTTP
3
4
  # MIME type encode/decode adapters
4
5
  module MimeType
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require "forwardable"
3
4
  require "singleton"
4
5
 
@@ -13,7 +14,7 @@ module HTTP
13
14
  def_delegators :instance, :encode, :decode
14
15
  end
15
16
 
16
- %w(encode decode).each do |operation|
17
+ %w[encode decode].each do |operation|
17
18
  class_eval <<-RUBY, __FILE__, __LINE__
18
19
  def #{operation}(*)
19
20
  fail Error, "\#{self.class} does not supports ##{operation}"
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require "json"
3
4
  require "http/mime_type/adapter"
4
5
 
@@ -14,7 +15,7 @@ module HTTP
14
15
 
15
16
  # Decodes JSON
16
17
  def decode(str)
17
- ::JSON.load str
18
+ ::JSON.parse str
18
19
  end
19
20
  end
20
21