http 4.4.1 → 5.0.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.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +65 -0
  3. data/.gitignore +6 -10
  4. data/.rspec +0 -4
  5. data/.rubocop/layout.yml +8 -0
  6. data/.rubocop/style.yml +32 -0
  7. data/.rubocop.yml +8 -110
  8. data/.rubocop_todo.yml +192 -0
  9. data/.yardopts +1 -1
  10. data/CHANGES.md +87 -3
  11. data/Gemfile +18 -10
  12. data/README.md +17 -20
  13. data/Rakefile +2 -10
  14. data/http.gemspec +3 -3
  15. data/lib/http/chainable.rb +23 -17
  16. data/lib/http/client.rb +36 -30
  17. data/lib/http/connection.rb +11 -7
  18. data/lib/http/content_type.rb +12 -7
  19. data/lib/http/feature.rb +3 -1
  20. data/lib/http/features/auto_deflate.rb +6 -6
  21. data/lib/http/features/auto_inflate.rb +6 -5
  22. data/lib/http/features/instrumentation.rb +1 -1
  23. data/lib/http/features/logging.rb +19 -21
  24. data/lib/http/headers.rb +50 -13
  25. data/lib/http/mime_type/adapter.rb +3 -1
  26. data/lib/http/mime_type/json.rb +1 -0
  27. data/lib/http/options.rb +5 -8
  28. data/lib/http/redirector.rb +2 -1
  29. data/lib/http/request/body.rb +1 -0
  30. data/lib/http/request/writer.rb +3 -2
  31. data/lib/http/request.rb +13 -10
  32. data/lib/http/response/body.rb +2 -2
  33. data/lib/http/response/inflater.rb +1 -1
  34. data/lib/http/response/parser.rb +74 -62
  35. data/lib/http/response/status.rb +4 -3
  36. data/lib/http/response.rb +17 -15
  37. data/lib/http/timeout/global.rb +17 -31
  38. data/lib/http/timeout/null.rb +2 -1
  39. data/lib/http/timeout/per_operation.rb +31 -54
  40. data/lib/http/uri.rb +5 -5
  41. data/lib/http/version.rb +1 -1
  42. data/spec/lib/http/client_spec.rb +119 -30
  43. data/spec/lib/http/connection_spec.rb +8 -5
  44. data/spec/lib/http/features/auto_inflate_spec.rb +4 -2
  45. data/spec/lib/http/features/instrumentation_spec.rb +28 -21
  46. data/spec/lib/http/features/logging_spec.rb +8 -9
  47. data/spec/lib/http/headers_spec.rb +53 -18
  48. data/spec/lib/http/options/headers_spec.rb +1 -1
  49. data/spec/lib/http/options/merge_spec.rb +16 -16
  50. data/spec/lib/http/redirector_spec.rb +2 -1
  51. data/spec/lib/http/request/writer_spec.rb +13 -1
  52. data/spec/lib/http/request_spec.rb +5 -5
  53. data/spec/lib/http/response/parser_spec.rb +33 -4
  54. data/spec/lib/http/response/status_spec.rb +3 -3
  55. data/spec/lib/http/response_spec.rb +11 -22
  56. data/spec/lib/http_spec.rb +30 -3
  57. data/spec/spec_helper.rb +21 -21
  58. data/spec/support/black_hole.rb +1 -1
  59. data/spec/support/dummy_server/servlet.rb +17 -6
  60. data/spec/support/dummy_server.rb +7 -7
  61. data/spec/support/fuubar.rb +21 -0
  62. data/spec/support/http_handling_shared.rb +4 -4
  63. data/spec/support/simplecov.rb +19 -0
  64. data/spec/support/ssl_helper.rb +4 -4
  65. metadata +18 -12
  66. data/.coveralls.yml +0 -1
  67. data/.travis.yml +0 -39
data/lib/http/headers.rb CHANGED
@@ -13,14 +13,18 @@ module HTTP
13
13
  include Enumerable
14
14
 
15
15
  # Matches HTTP header names when in "Canonical-Http-Format"
16
- CANONICAL_NAME_RE = /^[A-Z][a-z]*(?:-[A-Z][a-z]*)*$/
16
+ CANONICAL_NAME_RE = /\A[A-Z][a-z]*(?:-[A-Z][a-z]*)*\z/.freeze
17
17
 
18
18
  # Matches valid header field name according to RFC.
19
19
  # @see http://tools.ietf.org/html/rfc7230#section-3.2
20
- COMPLIANT_NAME_RE = /^[A-Za-z0-9!#\$%&'*+\-.^_`|~]+$/
20
+ COMPLIANT_NAME_RE = /\A[A-Za-z0-9!#$%&'*+\-.^_`|~]+\z/.freeze
21
21
 
22
22
  # Class constructor.
23
23
  def initialize
24
+ # The @pile stores each header value using a three element array:
25
+ # 0 - the normalized header key, used for lookup
26
+ # 1 - the header key as it will be sent with a request
27
+ # 2 - the value
24
28
  @pile = []
25
29
  end
26
30
 
@@ -45,12 +49,31 @@ module HTTP
45
49
 
46
50
  # Appends header.
47
51
  #
48
- # @param [#to_s] name header name
52
+ # @param [String, Symbol] name header name. When specified as a string, the
53
+ # name is sent as-is. When specified as a symbol, the name is converted
54
+ # to a string of capitalized words separated by a dash. Word boundaries
55
+ # are determined by an underscore (`_`) or a dash (`-`).
56
+ # Ex: `:content_type` is sent as `"Content-Type"`, and `"auth_key"` (string)
57
+ # is sent as `"auth_key"`.
49
58
  # @param [Array<#to_s>, #to_s] value header value(s) to be appended
50
59
  # @return [void]
51
60
  def add(name, value)
52
- name = normalize_header name.to_s
53
- Array(value).each { |v| @pile << [name, v.to_s] }
61
+ lookup_name = normalize_header(name.to_s)
62
+ wire_name = case name
63
+ when String
64
+ name
65
+ when Symbol
66
+ lookup_name
67
+ else
68
+ raise HTTP::HeaderError, "HTTP header must be a String or Symbol: #{name.inspect}"
69
+ end
70
+ Array(value).each do |v|
71
+ @pile << [
72
+ lookup_name,
73
+ wire_name,
74
+ validate_value(v)
75
+ ]
76
+ end
54
77
  end
55
78
 
56
79
  # Returns list of header values if any.
@@ -58,7 +81,7 @@ module HTTP
58
81
  # @return [Array<String>]
59
82
  def get(name)
60
83
  name = normalize_header name.to_s
61
- @pile.select { |k, _| k == name }.map { |_, v| v }
84
+ @pile.select { |k, _| k == name }.map { |_, _, v| v }
62
85
  end
63
86
 
64
87
  # Smart version of {#get}.
@@ -88,7 +111,7 @@ module HTTP
88
111
  #
89
112
  # @return [Hash]
90
113
  def to_h
91
- Hash[keys.map { |k| [k, self[k]] }]
114
+ keys.map { |k| [k, self[k]] }.to_h
92
115
  end
93
116
  alias to_hash to_h
94
117
 
@@ -96,7 +119,7 @@ module HTTP
96
119
  #
97
120
  # @return [Array<[String, String]>]
98
121
  def to_a
99
- @pile.map { |pair| pair.map(&:dup) }
122
+ @pile.map { |item| item[1..2] }
100
123
  end
101
124
 
102
125
  # Returns human-readable representation of `self` instance.
@@ -110,7 +133,7 @@ module HTTP
110
133
  #
111
134
  # @return [Array<String>]
112
135
  def keys
113
- @pile.map { |k, _| k }.uniq
136
+ @pile.map { |_, k, _| k }.uniq
114
137
  end
115
138
 
116
139
  # Compares headers to another Headers or Array of key/value pairs
@@ -118,7 +141,8 @@ module HTTP
118
141
  # @return [Boolean]
119
142
  def ==(other)
120
143
  return false unless other.respond_to? :to_a
121
- @pile == other.to_a
144
+
145
+ to_a == other.to_a
122
146
  end
123
147
 
124
148
  # Calls the given block once for each key/value pair in headers container.
@@ -127,7 +151,8 @@ module HTTP
127
151
  # @return [Headers] self-reference
128
152
  def each
129
153
  return to_enum(__method__) unless block_given?
130
- @pile.each { |arr| yield(arr) }
154
+
155
+ @pile.each { |item| yield(item[1..2]) }
131
156
  self
132
157
  end
133
158
 
@@ -139,7 +164,7 @@ module HTTP
139
164
 
140
165
  # @!method hash
141
166
  # Compute a hash-code for this headers container.
142
- # Two conatiners with the same content will have the same hash code.
167
+ # Two containers with the same content will have the same hash code.
143
168
  #
144
169
  # @see http://www.ruby-doc.org/core/Object.html#method-i-hash
145
170
  # @return [Fixnum]
@@ -150,7 +175,7 @@ module HTTP
150
175
  # @api private
151
176
  def initialize_copy(orig)
152
177
  super
153
- @pile = to_a
178
+ @pile = @pile.map(&:dup)
154
179
  end
155
180
 
156
181
  # Merges `other` headers into `self`.
@@ -209,5 +234,17 @@ module HTTP
209
234
 
210
235
  raise HeaderError, "Invalid HTTP header field name: #{name.inspect}"
211
236
  end
237
+
238
+ # Ensures there is no new line character in the header value
239
+ #
240
+ # @param [String] value
241
+ # @raise [HeaderError] if value includes new line character
242
+ # @return [String] stringified header value
243
+ def validate_value(value)
244
+ v = value.to_s
245
+ return v unless v.include?("\n")
246
+
247
+ raise HeaderError, "Invalid HTTP header field value: #{v.inspect}"
248
+ end
212
249
  end
213
250
  end
@@ -14,13 +14,15 @@ module HTTP
14
14
  def_delegators :instance, :encode, :decode
15
15
  end
16
16
 
17
+ # rubocop:disable Style/DocumentDynamicEvalDefinition
17
18
  %w[encode decode].each do |operation|
18
- class_eval <<-RUBY, __FILE__, __LINE__
19
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
19
20
  def #{operation}(*)
20
21
  fail Error, "\#{self.class} does not supports ##{operation}"
21
22
  end
22
23
  RUBY
23
24
  end
25
+ # rubocop:enable Style/DocumentDynamicEvalDefinition
24
26
  end
25
27
  end
26
28
  end
@@ -10,6 +10,7 @@ module HTTP
10
10
  # Encodes object to JSON
11
11
  def encode(obj)
12
12
  return obj.to_json if obj.respond_to?(:to_json)
13
+
13
14
  ::JSON.dump obj
14
15
  end
15
16
 
data/lib/http/options.rb CHANGED
@@ -1,14 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # rubocop:disable Metrics/ClassLength
4
-
5
3
  require "http/headers"
6
4
  require "openssl"
7
5
  require "socket"
8
6
  require "http/uri"
9
7
 
10
8
  module HTTP
11
- class Options
9
+ class Options # rubocop:disable Metrics/ClassLength
12
10
  @default_socket_class = TCPSocket
13
11
  @default_ssl_socket_class = OpenSSL::SSL::SSLSocket
14
12
  @default_timeout_class = HTTP::Timeout::Null
@@ -18,9 +16,8 @@ module HTTP
18
16
  attr_accessor :default_socket_class, :default_ssl_socket_class, :default_timeout_class
19
17
  attr_reader :available_features
20
18
 
21
- def new(options = {}) # rubocop:disable Style/OptionHash
22
- return options if options.is_a?(self)
23
- super
19
+ def new(options = {})
20
+ options.is_a?(self) ? options : super
24
21
  end
25
22
 
26
23
  def defined_options
@@ -35,7 +32,7 @@ module HTTP
35
32
 
36
33
  def def_option(name, reader_only: false, &interpreter)
37
34
  defined_options << name.to_sym
38
- interpreter ||= lambda { |v| v }
35
+ interpreter ||= ->(v) { v }
39
36
 
40
37
  if reader_only
41
38
  attr_reader name
@@ -50,7 +47,7 @@ module HTTP
50
47
  end
51
48
  end
52
49
 
53
- def initialize(options = {}) # rubocop:disable Style/OptionHash
50
+ def initialize(options = {})
54
51
  defaults = {
55
52
  :response => :auto,
56
53
  :proxy => {},
@@ -39,7 +39,7 @@ module HTTP
39
39
  # @param [Hash] opts
40
40
  # @option opts [Boolean] :strict (true) redirector hops policy
41
41
  # @option opts [#to_i] :max_hops (5) maximum allowed amount of hops
42
- def initialize(opts = {}) # rubocop:disable Style/OptionHash
42
+ def initialize(opts = {})
43
43
  @strict = opts.fetch(:strict, true)
44
44
  @max_hops = opts.fetch(:max_hops, 5).to_i
45
45
  end
@@ -90,6 +90,7 @@ module HTTP
90
90
 
91
91
  if UNSAFE_VERBS.include?(verb) && STRICT_SENSITIVE_CODES.include?(code)
92
92
  raise StateError, "can't follow #{@response.status} redirect" if @strict
93
+
93
94
  verb = :get
94
95
  end
95
96
 
@@ -19,6 +19,7 @@ module HTTP
19
19
  @source.bytesize
20
20
  elsif @source.respond_to?(:read)
21
21
  raise RequestError, "IO object must respond to #size" unless @source.respond_to?(:size)
22
+
22
23
  @source.size
23
24
  elsif @source.nil?
24
25
  0
@@ -108,12 +108,13 @@ module HTTP
108
108
  until data.empty?
109
109
  length = @socket.write(data)
110
110
  break unless data.bytesize > length
111
+
111
112
  data = data.byteslice(length..-1)
112
113
  end
113
114
  rescue Errno::EPIPE
114
115
  raise
115
- rescue IOError, SocketError, SystemCallError => ex
116
- raise ConnectionError, "error writing to socket: #{ex}", ex.backtrace
116
+ rescue IOError, SocketError, SystemCallError => e
117
+ raise ConnectionError, "error writing to socket: #{e}", e.backtrace
117
118
  end
118
119
  end
119
120
  end
data/lib/http/request.rb CHANGED
@@ -46,7 +46,10 @@ module HTTP
46
46
  :patch,
47
47
 
48
48
  # draft-reschke-webdav-search: WebDAV Search
49
- :search
49
+ :search,
50
+
51
+ # RFC 4791: Calendaring Extensions to WebDAV -- CalDAV
52
+ :mkcalendar
50
53
  ].freeze
51
54
 
52
55
  # Allowed schemes
@@ -54,10 +57,10 @@ module HTTP
54
57
 
55
58
  # Default ports of supported schemes
56
59
  PORTS = {
57
- :http => 80,
58
- :https => 443,
59
- :ws => 80,
60
- :wss => 443
60
+ :http => 80,
61
+ :https => 443,
62
+ :ws => 80,
63
+ :wss => 443
61
64
  }.freeze
62
65
 
63
66
  # Method is given as a lowercase symbol e.g. :get, :post
@@ -168,8 +171,8 @@ module HTTP
168
171
  # Headers to send with proxy connect request
169
172
  def proxy_connect_headers
170
173
  connect_headers = HTTP::Headers.coerce(
171
- Headers::HOST => headers[Headers::HOST],
172
- Headers::USER_AGENT => headers[Headers::USER_AGENT]
174
+ Headers::HOST => headers[Headers::HOST],
175
+ Headers::USER_AGENT => headers[Headers::USER_AGENT]
173
176
  )
174
177
 
175
178
  connect_headers[Headers::PROXY_AUTHORIZATION] = proxy_authorization_header if using_authenticated_proxy?
@@ -213,7 +216,7 @@ module HTTP
213
216
 
214
217
  # @return [String] Default host (with port if needed) header value.
215
218
  def default_host_header_value
216
- PORTS[@scheme] != port ? "#{host}:#{port}" : host
219
+ PORTS[@scheme] == port ? host : "#{host}:#{port}"
217
220
  end
218
221
 
219
222
  def prepare_body(body)
@@ -223,8 +226,8 @@ module HTTP
223
226
  def prepare_headers(headers)
224
227
  headers = HTTP::Headers.coerce(headers || {})
225
228
 
226
- headers[Headers::HOST] ||= default_host_header_value
227
- headers[Headers::USER_AGENT] ||= USER_AGENT
229
+ headers[Headers::HOST] ||= default_host_header_value
230
+ headers[Headers::USER_AGENT] ||= USER_AGENT
228
231
 
229
232
  headers
230
233
  end
@@ -27,8 +27,7 @@ module HTTP
27
27
  # (see HTTP::Client#readpartial)
28
28
  def readpartial(*args)
29
29
  stream!
30
- chunk = @stream.readpartial(*args)
31
- chunk.force_encoding(@encoding) if chunk
30
+ @stream.readpartial(*args)&.force_encoding(@encoding)
32
31
  end
33
32
 
34
33
  # Iterate over the body, allowing it to be enumerable
@@ -64,6 +63,7 @@ module HTTP
64
63
  # Assert that the body is actively being streamed
65
64
  def stream!
66
65
  raise StateError, "body has already been consumed" if @streaming == false
66
+
67
67
  @streaming = true
68
68
  end
69
69
 
@@ -16,7 +16,7 @@ module HTTP
16
16
  if chunk
17
17
  chunk = zstream.inflate(chunk)
18
18
  elsif !zstream.closed?
19
- zstream.finish
19
+ zstream.finish if zstream.total_in.positive?
20
20
  zstream.close
21
21
  end
22
22
  chunk
@@ -1,69 +1,63 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "http-parser"
3
+ require "llhttp"
4
4
 
5
5
  module HTTP
6
6
  class Response
7
7
  # @api private
8
- #
9
- # NOTE(ixti): This class is a subject of future refactoring, thus don't
10
- # expect this class API to be stable until this message disappears and
11
- # class is not marked as private anymore.
12
8
  class Parser
13
- attr_reader :headers
9
+ attr_reader :parser, :headers, :status_code, :http_version
14
10
 
15
11
  def initialize
16
- @state = HttpParser::Parser.new_instance { |i| i.type = :response }
17
- @parser = HttpParser::Parser.new(self)
18
-
12
+ @handler = Handler.new(self)
13
+ @parser = LLHttp::Parser.new(@handler, :type => :response)
19
14
  reset
20
15
  end
21
16
 
22
- # @return [self]
23
- def add(data)
24
- # XXX(ixti): API doc of HttpParser::Parser is misleading, it says that
25
- # it returns boolean true if data was parsed successfully, but instead
26
- # it's response tells if there was an error; So when it's `true` that
27
- # means parse failed, and `false` means parse was successful.
28
- # case of success.
29
- return self unless @parser.parse(@state, data)
30
-
31
- raise IOError, "Could not parse data"
17
+ def reset
18
+ @parser.finish
19
+ @handler.reset
20
+ @header_finished = false
21
+ @message_finished = false
22
+ @headers = Headers.new
23
+ @chunk = nil
24
+ @status_code = nil
25
+ @http_version = nil
32
26
  end
33
- alias << add
34
27
 
35
- def headers?
36
- @finished[:headers]
37
- end
28
+ def add(data)
29
+ parser << data
38
30
 
39
- def http_version
40
- @state.http_version
31
+ self
32
+ rescue LLHttp::Error => e
33
+ raise IOError, e.message
41
34
  end
42
35
 
43
- def status_code
44
- @state.http_status
36
+ alias << add
37
+
38
+ def mark_header_finished
39
+ @header_finished = true
40
+ @status_code = @parser.status_code
41
+ @http_version = "#{@parser.http_major}.#{@parser.http_minor}"
45
42
  end
46
43
 
47
- #
48
- # HTTP::Parser callbacks
49
- #
44
+ def headers?
45
+ @header_finished
46
+ end
50
47
 
51
- def on_header_field(_response, field)
52
- append_header if @reading_header_value
53
- @field << field
48
+ def add_header(name, value)
49
+ @headers.add(name, value)
54
50
  end
55
51
 
56
- def on_header_value(_response, value)
57
- @reading_header_value = true
58
- @field_value << value
52
+ def mark_message_finished
53
+ @message_finished = true
59
54
  end
60
55
 
61
- def on_headers_complete(_reposse)
62
- append_header if @reading_header_value
63
- @finished[:headers] = true
56
+ def finished?
57
+ @message_finished
64
58
  end
65
59
 
66
- def on_body(_response, chunk)
60
+ def add_body(chunk)
67
61
  if @chunk
68
62
  @chunk << chunk
69
63
  else
@@ -85,32 +79,50 @@ module HTTP
85
79
  chunk
86
80
  end
87
81
 
88
- def on_message_complete(_response)
89
- @finished[:message] = true
90
- end
82
+ class Handler < LLHttp::Delegate
83
+ def initialize(target)
84
+ @target = target
85
+ super()
86
+ reset
87
+ end
91
88
 
92
- def reset
93
- @state.reset!
94
-
95
- @finished = Hash.new(false)
96
- @headers = HTTP::Headers.new
97
- @reading_header_value = false
98
- @field = +""
99
- @field_value = +""
100
- @chunk = nil
101
- end
89
+ def reset
90
+ @reading_header_value = false
91
+ @field_value = +""
92
+ @field = +""
93
+ end
102
94
 
103
- def finished?
104
- @finished[:message]
105
- end
95
+ def on_header_field(field)
96
+ append_header if @reading_header_value
97
+ @field << field
98
+ end
99
+
100
+ def on_header_value(value)
101
+ @reading_header_value = true
102
+ @field_value << value
103
+ end
106
104
 
107
- private
105
+ def on_headers_complete
106
+ append_header if @reading_header_value
107
+ @target.mark_header_finished
108
+ end
109
+
110
+ def on_body(body)
111
+ @target.add_body(body)
112
+ end
108
113
 
109
- def append_header
110
- @headers.add(@field, @field_value)
111
- @reading_header_value = false
112
- @field_value = +""
113
- @field = +""
114
+ def on_message_complete
115
+ @target.mark_message_finished
116
+ end
117
+
118
+ private
119
+
120
+ def append_header
121
+ @target.add_header(@field, @field_value)
122
+ @reading_header_value = false
123
+ @field_value = +""
124
+ @field = +""
125
+ end
114
126
  end
115
127
  end
116
128
  end
@@ -58,7 +58,7 @@ module HTTP
58
58
  # SYMBOLS[418] # => :im_a_teapot
59
59
  #
60
60
  # @return [Hash<Fixnum => Symbol>]
61
- SYMBOLS = Hash[REASONS.map { |k, v| [k, symbolize(v)] }].freeze
61
+ SYMBOLS = REASONS.transform_values { |v| symbolize(v) }.freeze
62
62
 
63
63
  # Reversed {SYMBOLS} map.
64
64
  #
@@ -69,7 +69,7 @@ module HTTP
69
69
  # SYMBOL_CODES[:im_a_teapot] # => 418
70
70
  #
71
71
  # @return [Hash<Symbol => Fixnum>]
72
- SYMBOL_CODES = Hash[SYMBOLS.map { |k, v| [v, k] }].freeze
72
+ SYMBOL_CODES = SYMBOLS.map { |k, v| [v, k] }.to_h.freeze
73
73
 
74
74
  # @return [Fixnum] status code
75
75
  attr_reader :code
@@ -132,7 +132,7 @@ module HTTP
132
132
  end
133
133
 
134
134
  SYMBOLS.each do |code, symbol|
135
- class_eval <<-RUBY, __FILE__, __LINE__
135
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
136
136
  def #{symbol}? # def bad_request?
137
137
  #{code} == code # 400 == code
138
138
  end # end
@@ -141,6 +141,7 @@ module HTTP
141
141
 
142
142
  def __setobj__(obj)
143
143
  raise TypeError, "Expected #{obj.inspect} to respond to #to_i" unless obj.respond_to? :to_i
144
+
144
145
  @code = obj.to_i
145
146
  end
146
147
 
data/lib/http/response.rb CHANGED
@@ -7,7 +7,6 @@ require "http/content_type"
7
7
  require "http/mime_type"
8
8
  require "http/response/status"
9
9
  require "http/response/inflater"
10
- require "http/uri"
11
10
  require "http/cookie_jar"
12
11
  require "time"
13
12
 
@@ -26,8 +25,8 @@ module HTTP
26
25
  # @return [Body]
27
26
  attr_reader :body
28
27
 
29
- # @return [URI, nil]
30
- attr_reader :uri
28
+ # @return [Request]
29
+ attr_reader :request
31
30
 
32
31
  # @return [Hash]
33
32
  attr_reader :proxy_headers
@@ -41,10 +40,10 @@ module HTTP
41
40
  # @option opts [HTTP::Connection] :connection
42
41
  # @option opts [String] :encoding Encoding to use when reading body
43
42
  # @option opts [String] :body
44
- # @option opts [String] :uri
43
+ # @option opts [HTTP::Request] request
45
44
  def initialize(opts)
46
45
  @version = opts.fetch(:version)
47
- @uri = HTTP::URI.parse(opts.fetch(:uri)) if opts.include? :uri
46
+ @request = opts.fetch(:request)
48
47
  @status = HTTP::Response::Status.new(opts.fetch(:status))
49
48
  @headers = HTTP::Headers.coerce(opts[:headers] || {})
50
49
  @proxy_headers = HTTP::Headers.coerce(opts[:proxy_headers] || {})
@@ -61,24 +60,28 @@ module HTTP
61
60
 
62
61
  # @!method reason
63
62
  # @return (see HTTP::Response::Status#reason)
64
- def_delegator :status, :reason
63
+ def_delegator :@status, :reason
65
64
 
66
65
  # @!method code
67
66
  # @return (see HTTP::Response::Status#code)
68
- def_delegator :status, :code
67
+ def_delegator :@status, :code
69
68
 
70
69
  # @!method to_s
71
70
  # (see HTTP::Response::Body#to_s)
72
- def_delegator :body, :to_s
71
+ def_delegator :@body, :to_s
73
72
  alias to_str to_s
74
73
 
75
74
  # @!method readpartial
76
75
  # (see HTTP::Response::Body#readpartial)
77
- def_delegator :body, :readpartial
76
+ def_delegator :@body, :readpartial
78
77
 
79
78
  # @!method connection
80
79
  # (see HTTP::Response::Body#connection)
81
- def_delegator :body, :connection
80
+ def_delegator :@body, :connection
81
+
82
+ # @!method uri
83
+ # @return (see HTTP::Request#uri)
84
+ def_delegator :@request, :uri
82
85
 
83
86
  # Returns an Array ala Rack: `[status, headers, body]`
84
87
  #
@@ -150,12 +153,11 @@ module HTTP
150
153
 
151
154
  # Parse response body with corresponding MIME type adapter.
152
155
  #
153
- # @param [#to_s] as Parse as given MIME type
154
- # instead of the one determined from headers
155
- # @raise [HTTP::Error] if adapter not found
156
+ # @param type [#to_s] Parse as given MIME type.
157
+ # @raise (see MimeType.[])
156
158
  # @return [Object]
157
- def parse(as = nil)
158
- MimeType[as || mime_type].decode to_s
159
+ def parse(type)
160
+ MimeType[type].decode to_s
159
161
  end
160
162
 
161
163
  # Inspect a response