http 4.4.1 → 5.1.1
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 +4 -4
- data/.github/workflows/ci.yml +85 -0
- data/.gitignore +6 -10
- data/.rspec +0 -4
- data/.rubocop/layout.yml +8 -0
- data/.rubocop/style.yml +32 -0
- data/.rubocop.yml +8 -110
- data/.rubocop_todo.yml +206 -0
- data/.yardopts +1 -1
- data/CHANGES.md +200 -3
- data/Gemfile +18 -10
- data/LICENSE.txt +1 -1
- data/README.md +48 -86
- data/Rakefile +2 -10
- data/SECURITY.md +5 -0
- data/http.gemspec +9 -8
- data/lib/http/chainable.rb +23 -17
- data/lib/http/client.rb +44 -34
- data/lib/http/connection.rb +11 -7
- data/lib/http/content_type.rb +12 -7
- data/lib/http/errors.rb +3 -0
- data/lib/http/feature.rb +3 -1
- data/lib/http/features/auto_deflate.rb +6 -6
- data/lib/http/features/auto_inflate.rb +6 -7
- data/lib/http/features/instrumentation.rb +1 -1
- data/lib/http/features/logging.rb +19 -21
- data/lib/http/headers.rb +50 -13
- data/lib/http/mime_type/adapter.rb +3 -1
- data/lib/http/mime_type/json.rb +1 -0
- data/lib/http/options.rb +5 -8
- data/lib/http/redirector.rb +55 -4
- data/lib/http/request/body.rb +1 -0
- data/lib/http/request/writer.rb +9 -4
- data/lib/http/request.rb +28 -11
- data/lib/http/response/body.rb +6 -4
- data/lib/http/response/inflater.rb +1 -1
- data/lib/http/response/parser.rb +74 -62
- data/lib/http/response/status.rb +4 -3
- data/lib/http/response.rb +44 -18
- data/lib/http/timeout/global.rb +20 -36
- data/lib/http/timeout/null.rb +2 -1
- data/lib/http/timeout/per_operation.rb +32 -55
- data/lib/http/uri.rb +51 -6
- data/lib/http/version.rb +1 -1
- data/spec/lib/http/client_spec.rb +155 -30
- data/spec/lib/http/connection_spec.rb +8 -5
- data/spec/lib/http/features/auto_inflate_spec.rb +3 -2
- data/spec/lib/http/features/instrumentation_spec.rb +27 -21
- data/spec/lib/http/features/logging_spec.rb +8 -10
- data/spec/lib/http/headers_spec.rb +53 -18
- data/spec/lib/http/options/headers_spec.rb +1 -1
- data/spec/lib/http/options/merge_spec.rb +16 -16
- data/spec/lib/http/redirector_spec.rb +133 -3
- data/spec/lib/http/request/body_spec.rb +3 -3
- data/spec/lib/http/request/writer_spec.rb +25 -2
- data/spec/lib/http/request_spec.rb +5 -5
- data/spec/lib/http/response/body_spec.rb +5 -5
- data/spec/lib/http/response/parser_spec.rb +33 -4
- data/spec/lib/http/response/status_spec.rb +3 -3
- data/spec/lib/http/response_spec.rb +80 -3
- data/spec/lib/http/uri_spec.rb +39 -0
- data/spec/lib/http_spec.rb +30 -3
- data/spec/spec_helper.rb +21 -21
- data/spec/support/black_hole.rb +1 -1
- data/spec/support/dummy_server/servlet.rb +19 -6
- data/spec/support/dummy_server.rb +7 -7
- data/spec/support/fuubar.rb +21 -0
- data/spec/support/http_handling_shared.rb +5 -5
- data/spec/support/simplecov.rb +19 -0
- data/spec/support/ssl_helper.rb +4 -4
- metadata +22 -14
- data/.coveralls.yml +0 -1
- data/.travis.yml +0 -39
data/lib/http/redirector.rb
CHANGED
@@ -39,9 +39,10 @@ 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 = {})
|
43
|
-
@strict
|
44
|
-
@max_hops
|
42
|
+
def initialize(opts = {})
|
43
|
+
@strict = opts.fetch(:strict, true)
|
44
|
+
@max_hops = opts.fetch(:max_hops, 5).to_i
|
45
|
+
@on_redirect = opts.fetch(:on_redirect, nil)
|
45
46
|
end
|
46
47
|
|
47
48
|
# Follows redirects until non-redirect response found
|
@@ -49,6 +50,8 @@ module HTTP
|
|
49
50
|
@request = request
|
50
51
|
@response = response
|
51
52
|
@visited = []
|
53
|
+
collect_cookies_from_request
|
54
|
+
collect_cookies_from_response
|
52
55
|
|
53
56
|
while REDIRECT_CODES.include? @response.status.code
|
54
57
|
@visited << "#{@request.verb} #{@request.uri}"
|
@@ -59,8 +62,13 @@ module HTTP
|
|
59
62
|
@response.flush
|
60
63
|
|
61
64
|
# XXX(ixti): using `Array#inject` to return `nil` if no Location header.
|
62
|
-
@request
|
65
|
+
@request = redirect_to(@response.headers.get(Headers::LOCATION).inject(:+))
|
66
|
+
unless cookie_jar.empty?
|
67
|
+
@request.headers.set(Headers::COOKIE, cookie_jar.cookies.map { |c| "#{c.name}=#{c.value}" }.join("; "))
|
68
|
+
end
|
69
|
+
@on_redirect.call @response, @request if @on_redirect.respond_to?(:call)
|
63
70
|
@response = yield @request
|
71
|
+
collect_cookies_from_response
|
64
72
|
end
|
65
73
|
|
66
74
|
@response
|
@@ -68,6 +76,48 @@ module HTTP
|
|
68
76
|
|
69
77
|
private
|
70
78
|
|
79
|
+
# All known cookies. On the original request, this is only the original cookies, but after that,
|
80
|
+
# Set-Cookie headers can add, set or delete cookies.
|
81
|
+
def cookie_jar
|
82
|
+
# it seems that @response.cookies instance is reused between responses, so we have to "clone"
|
83
|
+
@cookie_jar ||= HTTP::CookieJar.new
|
84
|
+
end
|
85
|
+
|
86
|
+
def collect_cookies_from_request
|
87
|
+
request_cookie_header = @request.headers["Cookie"]
|
88
|
+
cookies =
|
89
|
+
if request_cookie_header
|
90
|
+
HTTP::Cookie.cookie_value_to_hash(request_cookie_header)
|
91
|
+
else
|
92
|
+
{}
|
93
|
+
end
|
94
|
+
|
95
|
+
cookies.each do |key, value|
|
96
|
+
cookie_jar.add(HTTP::Cookie.new(key, value, :path => @request.uri.path, :domain => @request.host))
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Carry cookies from one response to the next. Carrying cookies to the next response ends up
|
101
|
+
# carrying them to the next request as well.
|
102
|
+
#
|
103
|
+
# Note that this isn't part of the IETF standard, but all major browsers support setting cookies
|
104
|
+
# on redirect: https://blog.dubbelboer.com/2012/11/25/302-cookie.html
|
105
|
+
def collect_cookies_from_response
|
106
|
+
# Overwrite previous cookies
|
107
|
+
@response.cookies.each do |cookie|
|
108
|
+
if cookie.value == ""
|
109
|
+
cookie_jar.delete(cookie)
|
110
|
+
else
|
111
|
+
cookie_jar.add(cookie)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# I wish we could just do @response.cookes = cookie_jar
|
116
|
+
cookie_jar.each do |cookie|
|
117
|
+
@response.cookies.add(cookie)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
71
121
|
# Check if we reached max amount of redirect hops
|
72
122
|
# @return [Boolean]
|
73
123
|
def too_many_hops?
|
@@ -90,6 +140,7 @@ module HTTP
|
|
90
140
|
|
91
141
|
if UNSAFE_VERBS.include?(verb) && STRICT_SENSITIVE_CODES.include?(code)
|
92
142
|
raise StateError, "can't follow #{@response.status} redirect" if @strict
|
143
|
+
|
93
144
|
verb = :get
|
94
145
|
end
|
95
146
|
|
data/lib/http/request/body.rb
CHANGED
data/lib/http/request/writer.rb
CHANGED
@@ -47,7 +47,11 @@ module HTTP
|
|
47
47
|
# Adds the headers to the header array for the given request body we are working
|
48
48
|
# with
|
49
49
|
def add_body_type_headers
|
50
|
-
return if @headers[Headers::CONTENT_LENGTH] || chunked?
|
50
|
+
return if @headers[Headers::CONTENT_LENGTH] || chunked? || (
|
51
|
+
@body.source.nil? && %w[GET HEAD DELETE CONNECT].any? do |method|
|
52
|
+
@request_header[0].start_with?("#{method} ")
|
53
|
+
end
|
54
|
+
)
|
51
55
|
|
52
56
|
@request_header << "#{Headers::CONTENT_LENGTH}: #{@body.size}"
|
53
57
|
end
|
@@ -57,7 +61,7 @@ module HTTP
|
|
57
61
|
def join_headers
|
58
62
|
# join the headers array with crlfs, stick two on the end because
|
59
63
|
# that ends the request header
|
60
|
-
@request_header.join(CRLF) + CRLF * 2
|
64
|
+
@request_header.join(CRLF) + (CRLF * 2)
|
61
65
|
end
|
62
66
|
|
63
67
|
# Writes HTTP request data into the socket.
|
@@ -108,12 +112,13 @@ module HTTP
|
|
108
112
|
until data.empty?
|
109
113
|
length = @socket.write(data)
|
110
114
|
break unless data.bytesize > length
|
115
|
+
|
111
116
|
data = data.byteslice(length..-1)
|
112
117
|
end
|
113
118
|
rescue Errno::EPIPE
|
114
119
|
raise
|
115
|
-
rescue IOError, SocketError, SystemCallError =>
|
116
|
-
raise ConnectionError, "error writing to socket: #{
|
120
|
+
rescue IOError, SocketError, SystemCallError => e
|
121
|
+
raise ConnectionError, "error writing to socket: #{e}", e.backtrace
|
117
122
|
end
|
118
123
|
end
|
119
124
|
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
|
58
|
-
:https
|
59
|
-
:ws
|
60
|
-
:wss
|
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
|
@@ -101,12 +104,26 @@ module HTTP
|
|
101
104
|
headers = self.headers.dup
|
102
105
|
headers.delete(Headers::HOST)
|
103
106
|
|
107
|
+
new_body = body.source
|
108
|
+
if verb == :get
|
109
|
+
# request bodies should not always be resubmitted when following a redirect
|
110
|
+
# some servers will close the connection after receiving the request headers
|
111
|
+
# which may cause Errno::ECONNRESET: Connection reset by peer
|
112
|
+
# see https://github.com/httprb/http/issues/649
|
113
|
+
# new_body = Request::Body.new(nil)
|
114
|
+
new_body = nil
|
115
|
+
# the CONTENT_TYPE header causes problems if set on a get request w/ an empty body
|
116
|
+
# the server might assume that there should be content if it is set to multipart
|
117
|
+
# rack raises EmptyContentError if this happens
|
118
|
+
headers.delete(Headers::CONTENT_TYPE)
|
119
|
+
end
|
120
|
+
|
104
121
|
self.class.new(
|
105
122
|
:verb => verb,
|
106
123
|
:uri => @uri.join(uri),
|
107
124
|
:headers => headers,
|
108
125
|
:proxy => proxy,
|
109
|
-
:body =>
|
126
|
+
:body => new_body,
|
110
127
|
:version => version,
|
111
128
|
:uri_normalizer => uri_normalizer
|
112
129
|
)
|
@@ -168,8 +185,8 @@ module HTTP
|
|
168
185
|
# Headers to send with proxy connect request
|
169
186
|
def proxy_connect_headers
|
170
187
|
connect_headers = HTTP::Headers.coerce(
|
171
|
-
Headers::HOST
|
172
|
-
Headers::USER_AGENT
|
188
|
+
Headers::HOST => headers[Headers::HOST],
|
189
|
+
Headers::USER_AGENT => headers[Headers::USER_AGENT]
|
173
190
|
)
|
174
191
|
|
175
192
|
connect_headers[Headers::PROXY_AUTHORIZATION] = proxy_authorization_header if using_authenticated_proxy?
|
@@ -213,7 +230,7 @@ module HTTP
|
|
213
230
|
|
214
231
|
# @return [String] Default host (with port if needed) header value.
|
215
232
|
def default_host_header_value
|
216
|
-
PORTS[@scheme]
|
233
|
+
PORTS[@scheme] == port ? host : "#{host}:#{port}"
|
217
234
|
end
|
218
235
|
|
219
236
|
def prepare_body(body)
|
@@ -223,8 +240,8 @@ module HTTP
|
|
223
240
|
def prepare_headers(headers)
|
224
241
|
headers = HTTP::Headers.coerce(headers || {})
|
225
242
|
|
226
|
-
headers[Headers::HOST]
|
227
|
-
headers[Headers::USER_AGENT]
|
243
|
+
headers[Headers::HOST] ||= default_host_header_value
|
244
|
+
headers[Headers::USER_AGENT] ||= USER_AGENT
|
228
245
|
|
229
246
|
headers
|
230
247
|
end
|
data/lib/http/response/body.rb
CHANGED
@@ -28,7 +28,8 @@ module HTTP
|
|
28
28
|
def readpartial(*args)
|
29
29
|
stream!
|
30
30
|
chunk = @stream.readpartial(*args)
|
31
|
-
|
31
|
+
|
32
|
+
String.new(chunk, :encoding => @encoding) if chunk
|
32
33
|
end
|
33
34
|
|
34
35
|
# Iterate over the body, allowing it to be enumerable
|
@@ -46,11 +47,11 @@ module HTTP
|
|
46
47
|
|
47
48
|
begin
|
48
49
|
@streaming = false
|
49
|
-
@contents = String.new(""
|
50
|
+
@contents = String.new("", :encoding => @encoding)
|
50
51
|
|
51
52
|
while (chunk = @stream.readpartial)
|
52
|
-
@contents <<
|
53
|
-
chunk
|
53
|
+
@contents << String.new(chunk, :encoding => @encoding)
|
54
|
+
chunk = nil # deallocate string
|
54
55
|
end
|
55
56
|
rescue
|
56
57
|
@contents = nil
|
@@ -64,6 +65,7 @@ module HTTP
|
|
64
65
|
# Assert that the body is actively being streamed
|
65
66
|
def stream!
|
66
67
|
raise StateError, "body has already been consumed" if @streaming == false
|
68
|
+
|
67
69
|
@streaming = true
|
68
70
|
end
|
69
71
|
|
data/lib/http/response/parser.rb
CHANGED
@@ -1,69 +1,63 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
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
|
-
@
|
17
|
-
@parser =
|
18
|
-
|
12
|
+
@handler = Handler.new(self)
|
13
|
+
@parser = LLHttp::Parser.new(@handler, :type => :response)
|
19
14
|
reset
|
20
15
|
end
|
21
16
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
raise IOError, "Could not parse data"
|
17
|
+
def reset
|
18
|
+
@parser.reset
|
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
|
36
|
-
|
37
|
-
end
|
28
|
+
def add(data)
|
29
|
+
parser << data
|
38
30
|
|
39
|
-
|
40
|
-
|
31
|
+
self
|
32
|
+
rescue LLHttp::Error => e
|
33
|
+
raise IOError, e.message
|
41
34
|
end
|
42
35
|
|
43
|
-
|
44
|
-
|
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
|
-
|
49
|
-
|
44
|
+
def headers?
|
45
|
+
@header_finished
|
46
|
+
end
|
50
47
|
|
51
|
-
def
|
52
|
-
|
53
|
-
@field << field
|
48
|
+
def add_header(name, value)
|
49
|
+
@headers.add(name, value)
|
54
50
|
end
|
55
51
|
|
56
|
-
def
|
57
|
-
@
|
58
|
-
@field_value << value
|
52
|
+
def mark_message_finished
|
53
|
+
@message_finished = true
|
59
54
|
end
|
60
55
|
|
61
|
-
def
|
62
|
-
|
63
|
-
@finished[:headers] = true
|
56
|
+
def finished?
|
57
|
+
@message_finished
|
64
58
|
end
|
65
59
|
|
66
|
-
def
|
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
|
-
|
89
|
-
|
90
|
-
|
82
|
+
class Handler < LLHttp::Delegate
|
83
|
+
def initialize(target)
|
84
|
+
@target = target
|
85
|
+
super()
|
86
|
+
reset
|
87
|
+
end
|
91
88
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
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
|
-
|
104
|
-
|
105
|
-
|
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
|
-
|
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
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
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
|
data/lib/http/response/status.rb
CHANGED
@@ -58,7 +58,7 @@ module HTTP
|
|
58
58
|
# SYMBOLS[418] # => :im_a_teapot
|
59
59
|
#
|
60
60
|
# @return [Hash<Fixnum => Symbol>]
|
61
|
-
SYMBOLS =
|
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 =
|
72
|
+
SYMBOL_CODES = SYMBOLS.to_h { |k, v| [v, k] }.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 [
|
30
|
-
attr_reader :
|
28
|
+
# @return [Request]
|
29
|
+
attr_reader :request
|
31
30
|
|
32
31
|
# @return [Hash]
|
33
32
|
attr_reader :proxy_headers
|
@@ -41,10 +40,11 @@ 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 [
|
43
|
+
# @option opts [HTTP::Request] request The request this is in response to.
|
44
|
+
# @option opts [String] :uri (DEPRECATED) used to populate a missing request
|
45
45
|
def initialize(opts)
|
46
46
|
@version = opts.fetch(:version)
|
47
|
-
@
|
47
|
+
@request = init_request(opts)
|
48
48
|
@status = HTTP::Response::Status.new(opts.fetch(:status))
|
49
49
|
@headers = HTTP::Headers.coerce(opts[:headers] || {})
|
50
50
|
@proxy_headers = HTTP::Headers.coerce(opts[:proxy_headers] || {})
|
@@ -53,7 +53,7 @@ module HTTP
|
|
53
53
|
@body = opts.fetch(:body)
|
54
54
|
else
|
55
55
|
connection = opts.fetch(:connection)
|
56
|
-
encoding
|
56
|
+
encoding = opts[:encoding] || charset || default_encoding
|
57
57
|
|
58
58
|
@body = Response::Body.new(connection, :encoding => encoding)
|
59
59
|
end
|
@@ -61,24 +61,28 @@ module HTTP
|
|
61
61
|
|
62
62
|
# @!method reason
|
63
63
|
# @return (see HTTP::Response::Status#reason)
|
64
|
-
def_delegator
|
64
|
+
def_delegator :@status, :reason
|
65
65
|
|
66
66
|
# @!method code
|
67
67
|
# @return (see HTTP::Response::Status#code)
|
68
|
-
def_delegator
|
68
|
+
def_delegator :@status, :code
|
69
69
|
|
70
70
|
# @!method to_s
|
71
71
|
# (see HTTP::Response::Body#to_s)
|
72
|
-
def_delegator
|
72
|
+
def_delegator :@body, :to_s
|
73
73
|
alias to_str to_s
|
74
74
|
|
75
75
|
# @!method readpartial
|
76
76
|
# (see HTTP::Response::Body#readpartial)
|
77
|
-
def_delegator
|
77
|
+
def_delegator :@body, :readpartial
|
78
78
|
|
79
79
|
# @!method connection
|
80
80
|
# (see HTTP::Response::Body#connection)
|
81
|
-
def_delegator
|
81
|
+
def_delegator :@body, :connection
|
82
|
+
|
83
|
+
# @!method uri
|
84
|
+
# @return (see HTTP::Request#uri)
|
85
|
+
def_delegator :@request, :uri
|
82
86
|
|
83
87
|
# Returns an Array ala Rack: `[status, headers, body]`
|
84
88
|
#
|
@@ -134,8 +138,8 @@ module HTTP
|
|
134
138
|
def_delegator :content_type, :charset
|
135
139
|
|
136
140
|
def cookies
|
137
|
-
@cookies ||= headers.each_with_object CookieJar.new do |
|
138
|
-
jar.parse(v, uri)
|
141
|
+
@cookies ||= headers.get(Headers::SET_COOKIE).each_with_object CookieJar.new do |v, jar|
|
142
|
+
jar.parse(v, uri)
|
139
143
|
end
|
140
144
|
end
|
141
145
|
|
@@ -150,17 +154,39 @@ module HTTP
|
|
150
154
|
|
151
155
|
# Parse response body with corresponding MIME type adapter.
|
152
156
|
#
|
153
|
-
# @param [#to_s]
|
154
|
-
#
|
155
|
-
# @raise [HTTP::Error] if adapter not found
|
157
|
+
# @param type [#to_s] Parse as given MIME type.
|
158
|
+
# @raise (see MimeType.[])
|
156
159
|
# @return [Object]
|
157
|
-
def parse(
|
158
|
-
MimeType[
|
160
|
+
def parse(type = nil)
|
161
|
+
MimeType[type || mime_type].decode to_s
|
159
162
|
end
|
160
163
|
|
161
164
|
# Inspect a response
|
162
165
|
def inspect
|
163
166
|
"#<#{self.class}/#{@version} #{code} #{reason} #{headers.to_h.inspect}>"
|
164
167
|
end
|
168
|
+
|
169
|
+
private
|
170
|
+
|
171
|
+
def default_encoding
|
172
|
+
return Encoding::UTF_8 if mime_type == "application/json"
|
173
|
+
|
174
|
+
Encoding::BINARY
|
175
|
+
end
|
176
|
+
|
177
|
+
# Initialize an HTTP::Request from options.
|
178
|
+
#
|
179
|
+
# @return [HTTP::Request]
|
180
|
+
def init_request(opts)
|
181
|
+
raise ArgumentError, ":uri is for backwards compatibilty and conflicts with :request" \
|
182
|
+
if opts[:request] && opts[:uri]
|
183
|
+
|
184
|
+
# For backwards compatibilty
|
185
|
+
if opts[:uri]
|
186
|
+
HTTP::Request.new(:uri => opts[:uri], :verb => :get)
|
187
|
+
else
|
188
|
+
opts.fetch(:request)
|
189
|
+
end
|
190
|
+
end
|
165
191
|
end
|
166
192
|
end
|