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.
- checksums.yaml +4 -4
- data/.rubocop.yml +46 -13
- data/.travis.yml +17 -12
- data/CHANGES.md +25 -1
- data/Gemfile +11 -4
- data/Guardfile +2 -0
- data/README.md +4 -5
- data/Rakefile +14 -13
- data/http.gemspec +3 -1
- data/lib/http.rb +1 -0
- data/lib/http/chainable.rb +15 -14
- data/lib/http/client.rb +27 -24
- data/lib/http/connection.rb +6 -4
- data/lib/http/content_type.rb +1 -0
- data/lib/http/errors.rb +3 -2
- data/lib/http/feature.rb +2 -1
- data/lib/http/features/auto_deflate.rb +77 -20
- data/lib/http/features/auto_inflate.rb +2 -1
- data/lib/http/headers.rb +3 -2
- data/lib/http/headers/known.rb +23 -22
- data/lib/http/headers/mixin.rb +1 -0
- data/lib/http/mime_type.rb +1 -0
- data/lib/http/mime_type/adapter.rb +2 -1
- data/lib/http/mime_type/json.rb +2 -1
- data/lib/http/options.rb +15 -12
- data/lib/http/redirector.rb +4 -3
- data/lib/http/request.rb +25 -10
- data/lib/http/request/body.rb +67 -0
- data/lib/http/request/writer.rb +32 -37
- data/lib/http/response.rb +17 -2
- data/lib/http/response/body.rb +16 -12
- data/lib/http/response/parser.rb +1 -0
- data/lib/http/response/status.rb +1 -0
- data/lib/http/response/status/reasons.rb +1 -0
- data/lib/http/timeout/global.rb +1 -0
- data/lib/http/timeout/null.rb +2 -1
- data/lib/http/timeout/per_operation.rb +19 -6
- data/lib/http/uri.rb +8 -2
- data/lib/http/version.rb +1 -1
- data/spec/lib/http/client_spec.rb +104 -4
- data/spec/lib/http/content_type_spec.rb +1 -0
- data/spec/lib/http/features/auto_deflate_spec.rb +32 -64
- data/spec/lib/http/features/auto_inflate_spec.rb +1 -0
- data/spec/lib/http/headers/mixin_spec.rb +1 -0
- data/spec/lib/http/headers_spec.rb +36 -35
- data/spec/lib/http/options/body_spec.rb +1 -0
- data/spec/lib/http/options/features_spec.rb +1 -0
- data/spec/lib/http/options/form_spec.rb +1 -0
- data/spec/lib/http/options/headers_spec.rb +2 -1
- data/spec/lib/http/options/json_spec.rb +1 -0
- data/spec/lib/http/options/new_spec.rb +2 -1
- data/spec/lib/http/options/proxy_spec.rb +1 -0
- data/spec/lib/http/options_spec.rb +1 -0
- data/spec/lib/http/redirector_spec.rb +1 -0
- data/spec/lib/http/request/body_spec.rb +138 -0
- data/spec/lib/http/request/writer_spec.rb +44 -74
- data/spec/lib/http/request_spec.rb +14 -0
- data/spec/lib/http/response/body_spec.rb +20 -4
- data/spec/lib/http/response/status_spec.rb +27 -26
- data/spec/lib/http/response_spec.rb +10 -0
- data/spec/lib/http/uri_spec.rb +11 -0
- data/spec/lib/http_spec.rb +18 -6
- data/spec/regression_specs.rb +1 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/support/black_hole.rb +9 -2
- data/spec/support/capture_warning.rb +1 -0
- data/spec/support/dummy_server.rb +2 -1
- data/spec/support/dummy_server/servlet.rb +1 -1
- data/spec/support/fakeio.rb +21 -0
- data/spec/support/http_handling_shared.rb +1 -0
- data/spec/support/proxy_server.rb +1 -0
- data/spec/support/servers/config.rb +1 -0
- data/spec/support/servers/runner.rb +1 -0
- data/spec/support/ssl_helper.rb +3 -2
- metadata +20 -9
data/lib/http/options.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# rubocop:disable Metrics/ClassLength, Style/RedundantSelf
|
4
|
+
|
2
5
|
require "http/headers"
|
3
6
|
require "openssl"
|
4
7
|
require "socket"
|
@@ -8,7 +11,6 @@ require "http/features/auto_inflate"
|
|
8
11
|
require "http/features/auto_deflate"
|
9
12
|
|
10
13
|
module HTTP
|
11
|
-
# rubocop:disable Metrics/ClassLength
|
12
14
|
class Options
|
13
15
|
@default_socket_class = TCPSocket
|
14
16
|
@default_ssl_socket_class = OpenSSL::SSL::SSLSocket
|
@@ -22,7 +24,7 @@ module HTTP
|
|
22
24
|
attr_accessor :default_socket_class, :default_ssl_socket_class, :default_timeout_class
|
23
25
|
attr_reader :available_features
|
24
26
|
|
25
|
-
def new(options = {})
|
27
|
+
def new(options = {}) # rubocop:disable Style/OptionHash
|
26
28
|
return options if options.is_a?(self)
|
27
29
|
super
|
28
30
|
end
|
@@ -46,7 +48,7 @@ module HTTP
|
|
46
48
|
end
|
47
49
|
end
|
48
50
|
|
49
|
-
def initialize(options = {})
|
51
|
+
def initialize(options = {}) # rubocop:disable Style/OptionHash
|
50
52
|
defaults = {
|
51
53
|
:response => :auto,
|
52
54
|
:proxy => {},
|
@@ -115,21 +117,22 @@ module HTTP
|
|
115
117
|
end
|
116
118
|
end
|
117
119
|
|
118
|
-
%w
|
120
|
+
%w[
|
119
121
|
proxy params form json body follow response
|
120
122
|
socket_class nodelay ssl_socket_class ssl_context ssl
|
121
123
|
persistent keep_alive_timeout timeout_class timeout_options
|
122
|
-
|
124
|
+
].each do |method_name|
|
123
125
|
def_option method_name
|
124
126
|
end
|
125
127
|
|
126
128
|
def follow=(value)
|
127
|
-
@follow =
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
129
|
+
@follow =
|
130
|
+
case
|
131
|
+
when !value then nil
|
132
|
+
when true == value then {}
|
133
|
+
when value.respond_to?(:fetch) then value
|
134
|
+
else argument_error! "Unsupported follow options: #{value}"
|
135
|
+
end
|
133
136
|
end
|
134
137
|
|
135
138
|
def persistent=(value)
|
@@ -182,7 +185,7 @@ module HTTP
|
|
182
185
|
private
|
183
186
|
|
184
187
|
def argument_error!(message)
|
185
|
-
raise(Error, message, caller
|
188
|
+
raise(Error, message, caller(1..-1))
|
186
189
|
end
|
187
190
|
end
|
188
191
|
end
|
data/lib/http/redirector.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require "set"
|
3
4
|
|
4
5
|
require "http/headers"
|
@@ -20,10 +21,10 @@ module HTTP
|
|
20
21
|
|
21
22
|
# Insecure http verbs, which should trigger StateError in strict mode
|
22
23
|
# upon {STRICT_SENSITIVE_CODES}
|
23
|
-
UNSAFE_VERBS = [
|
24
|
+
UNSAFE_VERBS = %i[put delete post].to_set.freeze
|
24
25
|
|
25
26
|
# Verbs which will remain unchanged upon See Other response.
|
26
|
-
SEE_OTHER_ALLOWED_VERBS = [
|
27
|
+
SEE_OTHER_ALLOWED_VERBS = %i[get head].to_set.freeze
|
27
28
|
|
28
29
|
# @!attribute [r] strict
|
29
30
|
# Returns redirector policy.
|
@@ -38,7 +39,7 @@ module HTTP
|
|
38
39
|
# @param [Hash] opts
|
39
40
|
# @option opts [Boolean] :strict (true) redirector hops policy
|
40
41
|
# @option opts [#to_i] :max_hops (5) maximum allowed amount of hops
|
41
|
-
def initialize(opts = {})
|
42
|
+
def initialize(opts = {}) # rubocop:disable Style/OptionHash
|
42
43
|
@strict = opts.fetch(:strict, true)
|
43
44
|
@max_hops = opts.fetch(:max_hops, 5).to_i
|
44
45
|
end
|
data/lib/http/request.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require "forwardable"
|
3
4
|
require "base64"
|
4
5
|
require "time"
|
5
6
|
|
6
7
|
require "http/errors"
|
7
8
|
require "http/headers"
|
9
|
+
require "http/request/body"
|
8
10
|
require "http/request/writer"
|
9
11
|
require "http/version"
|
10
12
|
require "http/uri"
|
@@ -22,7 +24,7 @@ module HTTP
|
|
22
24
|
class UnsupportedSchemeError < RequestError; end
|
23
25
|
|
24
26
|
# Default User-Agent header value
|
25
|
-
USER_AGENT = "http.rb/#{HTTP::VERSION}"
|
27
|
+
USER_AGENT = "http.rb/#{HTTP::VERSION}"
|
26
28
|
|
27
29
|
METHODS = [
|
28
30
|
# RFC 2616: Hypertext Transfer Protocol -- HTTP/1.1
|
@@ -48,7 +50,7 @@ module HTTP
|
|
48
50
|
].freeze
|
49
51
|
|
50
52
|
# Allowed schemes
|
51
|
-
SCHEMES = [
|
53
|
+
SCHEMES = %i[http https ws wss].freeze
|
52
54
|
|
53
55
|
# Default ports of supported schemes
|
54
56
|
PORTS = {
|
@@ -74,7 +76,7 @@ module HTTP
|
|
74
76
|
# @option opts [HTTP::URI, #to_s] :uri
|
75
77
|
# @option opts [Hash] :headers
|
76
78
|
# @option opts [Hash] :proxy
|
77
|
-
# @option opts [String] :body
|
79
|
+
# @option opts [String, Enumerable, IO, nil] :body
|
78
80
|
def initialize(opts)
|
79
81
|
@verb = opts.fetch(:verb).to_s.downcase.to_sym
|
80
82
|
@uri = normalize_uri(opts.fetch(:uri))
|
@@ -84,7 +86,7 @@ module HTTP
|
|
84
86
|
raise(UnsupportedSchemeError, "unknown scheme: #{scheme}") unless SCHEMES.include?(@scheme)
|
85
87
|
|
86
88
|
@proxy = opts[:proxy] || {}
|
87
|
-
@body = opts[:body]
|
89
|
+
@body = request_body(opts[:body], opts)
|
88
90
|
@version = opts[:version] || "1.1"
|
89
91
|
@headers = HTTP::Headers.coerce(opts[:headers] || {})
|
90
92
|
|
@@ -94,7 +96,10 @@ module HTTP
|
|
94
96
|
|
95
97
|
# Returns new Request with updated uri
|
96
98
|
def redirect(uri, verb = @verb)
|
97
|
-
|
99
|
+
headers = self.headers.dup
|
100
|
+
headers.delete(Headers::HOST)
|
101
|
+
|
102
|
+
self.class.new(
|
98
103
|
:verb => verb,
|
99
104
|
:uri => @uri.join(uri),
|
100
105
|
:headers => headers,
|
@@ -102,9 +107,6 @@ module HTTP
|
|
102
107
|
:body => body,
|
103
108
|
:version => version
|
104
109
|
)
|
105
|
-
|
106
|
-
req[Headers::HOST] = req.uri.host
|
107
|
-
req
|
108
110
|
end
|
109
111
|
|
110
112
|
# Stream the request to a socket
|
@@ -145,8 +147,14 @@ module HTTP
|
|
145
147
|
|
146
148
|
# Compute HTTP request header for direct or proxy request
|
147
149
|
def headline
|
148
|
-
request_uri =
|
149
|
-
|
150
|
+
request_uri =
|
151
|
+
if using_proxy? && !uri.https?
|
152
|
+
uri.omit(:fragment)
|
153
|
+
else
|
154
|
+
uri.omit(:scheme, :authority, :fragment)
|
155
|
+
end
|
156
|
+
|
157
|
+
"#{verb.to_s.upcase} #{request_uri} HTTP/#{version}"
|
150
158
|
end
|
151
159
|
|
152
160
|
# Compute HTTP request header SSL proxy connection
|
@@ -178,6 +186,13 @@ module HTTP
|
|
178
186
|
|
179
187
|
private
|
180
188
|
|
189
|
+
# Transforms body to an object suitable for streaming.
|
190
|
+
def request_body(body, opts)
|
191
|
+
body = Request::Body.new(body) unless body.is_a?(Request::Body)
|
192
|
+
body = opts[:auto_deflate].deflated_body(body) if opts[:auto_deflate]
|
193
|
+
body
|
194
|
+
end
|
195
|
+
|
181
196
|
# @!attribute [r] host
|
182
197
|
# @return [String]
|
183
198
|
def_delegator :@uri, :host
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HTTP
|
4
|
+
class Request
|
5
|
+
class Body
|
6
|
+
def initialize(body)
|
7
|
+
@body = body
|
8
|
+
|
9
|
+
validate_body_type!
|
10
|
+
end
|
11
|
+
|
12
|
+
# Returns size which should be used for the "Content-Length" header.
|
13
|
+
#
|
14
|
+
# @return [Integer]
|
15
|
+
def size
|
16
|
+
if @body.is_a?(String)
|
17
|
+
@body.bytesize
|
18
|
+
elsif @body.respond_to?(:read)
|
19
|
+
raise RequestError, "IO object must respond to #size" unless @body.respond_to?(:size)
|
20
|
+
@body.size
|
21
|
+
elsif @body.nil?
|
22
|
+
0
|
23
|
+
else
|
24
|
+
raise RequestError, "cannot determine size of body: #{@body.inspect}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Yields chunks of content to be streamed to the request body.
|
29
|
+
#
|
30
|
+
# @yieldparam [String]
|
31
|
+
def each(&block)
|
32
|
+
if @body.is_a?(String)
|
33
|
+
yield @body
|
34
|
+
elsif @body.respond_to?(:read)
|
35
|
+
IO.copy_stream(@body, ProcIO.new(block))
|
36
|
+
elsif @body.is_a?(Enumerable)
|
37
|
+
@body.each(&block)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def validate_body_type!
|
44
|
+
return if @body.is_a?(String)
|
45
|
+
return if @body.respond_to?(:read)
|
46
|
+
return if @body.is_a?(Enumerable)
|
47
|
+
return if @body.nil?
|
48
|
+
|
49
|
+
raise RequestError, "body of wrong type: #{@body.class}"
|
50
|
+
end
|
51
|
+
|
52
|
+
# This class provides a "writable IO" wrapper around a proc object, with
|
53
|
+
# #write simply calling the proc, which we can pass in as the
|
54
|
+
# "destination IO" in IO.copy_stream.
|
55
|
+
class ProcIO
|
56
|
+
def initialize(block)
|
57
|
+
@block = block
|
58
|
+
end
|
59
|
+
|
60
|
+
def write(data)
|
61
|
+
@block.call(data)
|
62
|
+
data.bytesize
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/http/request/writer.rb
CHANGED
@@ -1,31 +1,27 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require "http/headers"
|
3
4
|
|
4
5
|
module HTTP
|
5
6
|
class Request
|
6
7
|
class Writer
|
7
8
|
# CRLF is the universal HTTP delimiter
|
8
|
-
CRLF = "\r\n"
|
9
|
+
CRLF = "\r\n"
|
9
10
|
|
10
11
|
# Chunked data termintaor.
|
11
|
-
ZERO = "0"
|
12
|
+
ZERO = "0"
|
12
13
|
|
13
14
|
# Chunked transfer encoding
|
14
|
-
CHUNKED = "chunked"
|
15
|
+
CHUNKED = "chunked"
|
15
16
|
|
16
17
|
# End of a chunked transfer
|
17
|
-
CHUNKED_END = "#{ZERO}#{CRLF}#{CRLF}"
|
18
|
-
|
19
|
-
# Types valid to be used as body source
|
20
|
-
VALID_BODY_TYPES = [String, NilClass, Enumerable].freeze
|
18
|
+
CHUNKED_END = "#{ZERO}#{CRLF}#{CRLF}"
|
21
19
|
|
22
20
|
def initialize(socket, body, headers, headline)
|
23
21
|
@body = body
|
24
22
|
@socket = socket
|
25
23
|
@headers = headers
|
26
24
|
@request_header = [headline]
|
27
|
-
|
28
|
-
validate_body_type!
|
29
25
|
end
|
30
26
|
|
31
27
|
# Adds headers to the request header from the headers array
|
@@ -51,13 +47,9 @@ module HTTP
|
|
51
47
|
# Adds the headers to the header array for the given request body we are working
|
52
48
|
# with
|
53
49
|
def add_body_type_headers
|
54
|
-
if @
|
55
|
-
|
56
|
-
|
57
|
-
@request_header << "#{Headers::CONTENT_LENGTH}: 0"
|
58
|
-
elsif @body.is_a?(Enumerable) && CHUNKED != @headers[Headers::TRANSFER_ENCODING]
|
59
|
-
raise(RequestError, "invalid transfer encoding")
|
60
|
-
end
|
50
|
+
return if @headers[Headers::CONTENT_LENGTH] || chunked?
|
51
|
+
|
52
|
+
@request_header << "#{Headers::CONTENT_LENGTH}: #{@body.size}"
|
61
53
|
end
|
62
54
|
|
63
55
|
# Joins the headers specified in the request into a correctly formatted
|
@@ -69,29 +61,37 @@ module HTTP
|
|
69
61
|
end
|
70
62
|
|
71
63
|
def send_request
|
72
|
-
headers = join_headers
|
73
|
-
|
74
64
|
# It's important to send the request in a single write call when
|
75
65
|
# possible in order to play nicely with Nagle's algorithm. Making
|
76
66
|
# two writes in a row triggers a pathological case where Nagle is
|
77
67
|
# expecting a third write that never happens.
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
write(
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
68
|
+
data = join_headers
|
69
|
+
|
70
|
+
@body.each do |chunk|
|
71
|
+
data << encode_chunk(chunk)
|
72
|
+
write(data)
|
73
|
+
data.clear
|
74
|
+
end
|
75
|
+
|
76
|
+
write(data) unless data.empty?
|
77
|
+
|
78
|
+
write(CHUNKED_END) if chunked?
|
79
|
+
end
|
80
|
+
|
81
|
+
# Returns the chunk encoded for to the specified "Transfer-Encoding" header.
|
82
|
+
def encode_chunk(chunk)
|
83
|
+
if chunked?
|
84
|
+
chunk.bytesize.to_s(16) << CRLF << chunk << CRLF
|
85
|
+
else
|
86
|
+
chunk
|
92
87
|
end
|
93
88
|
end
|
94
89
|
|
90
|
+
# Returns true if the request should be sent in chunked encoding.
|
91
|
+
def chunked?
|
92
|
+
@headers[Headers::TRANSFER_ENCODING] == CHUNKED
|
93
|
+
end
|
94
|
+
|
95
95
|
private
|
96
96
|
|
97
97
|
def write(data)
|
@@ -101,11 +101,6 @@ module HTTP
|
|
101
101
|
data = data.byteslice(length..-1)
|
102
102
|
end
|
103
103
|
end
|
104
|
-
|
105
|
-
def validate_body_type!
|
106
|
-
return if VALID_BODY_TYPES.any? { |type| @body.is_a? type }
|
107
|
-
raise RequestError, "body of wrong type: #{@body.class}"
|
108
|
-
end
|
109
104
|
end
|
110
105
|
end
|
111
106
|
end
|
data/lib/http/response.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require "forwardable"
|
3
4
|
|
4
5
|
require "http/headers"
|
@@ -50,7 +51,7 @@ module HTTP
|
|
50
51
|
encoding = opts[:encoding] || charset || Encoding::BINARY
|
51
52
|
stream = body_stream_for(connection, opts)
|
52
53
|
|
53
|
-
@body = Response::Body.new(stream, encoding)
|
54
|
+
@body = Response::Body.new(stream, :encoding => encoding)
|
54
55
|
else
|
55
56
|
@body = opts.fetch(:body)
|
56
57
|
end
|
@@ -98,8 +99,13 @@ module HTTP
|
|
98
99
|
# (not an integer, e.g. empty string or string with non-digits).
|
99
100
|
# @return [Integer] otherwise
|
100
101
|
def content_length
|
102
|
+
# http://greenbytes.de/tech/webdav/rfc7230.html#rfc.section.3.3.3
|
103
|
+
# Clause 3: "If a message is received with both a Transfer-Encoding
|
104
|
+
# and a Content-Length header field, the Transfer-Encoding overrides the Content-Length.
|
105
|
+
return nil if @headers.include?(Headers::TRANSFER_ENCODING)
|
106
|
+
|
101
107
|
value = @headers[Headers::CONTENT_LENGTH]
|
102
|
-
return unless value
|
108
|
+
return nil unless value
|
103
109
|
|
104
110
|
begin
|
105
111
|
Integer(value)
|
@@ -131,6 +137,15 @@ module HTTP
|
|
131
137
|
end
|
132
138
|
end
|
133
139
|
|
140
|
+
def chunked?
|
141
|
+
return false unless @headers.include?(Headers::TRANSFER_ENCODING)
|
142
|
+
|
143
|
+
encoding = @headers.get(Headers::TRANSFER_ENCODING)
|
144
|
+
|
145
|
+
# TODO: "chunked" is frozen in the request writer. How about making it accessible?
|
146
|
+
encoding.last == "chunked"
|
147
|
+
end
|
148
|
+
|
134
149
|
# Parse response body with corresponding MIME type adapter.
|
135
150
|
#
|
136
151
|
# @param [#to_s] as Parse as given MIME type
|
data/lib/http/response/body.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require "forwardable"
|
3
4
|
require "http/client"
|
4
5
|
|
@@ -15,18 +16,19 @@ module HTTP
|
|
15
16
|
# @return [HTTP::Connection]
|
16
17
|
attr_reader :connection
|
17
18
|
|
18
|
-
def initialize(stream, encoding
|
19
|
+
def initialize(stream, encoding: Encoding::BINARY)
|
19
20
|
@stream = stream
|
20
21
|
@connection = stream.is_a?(Inflater) ? stream.connection : stream
|
21
22
|
@streaming = nil
|
22
23
|
@contents = nil
|
23
|
-
@encoding = encoding
|
24
|
+
@encoding = find_encoding(encoding)
|
24
25
|
end
|
25
26
|
|
26
27
|
# (see HTTP::Client#readpartial)
|
27
28
|
def readpartial(*args)
|
28
29
|
stream!
|
29
|
-
@stream.readpartial(*args)
|
30
|
+
chunk = @stream.readpartial(*args)
|
31
|
+
chunk.force_encoding(@encoding) if chunk
|
30
32
|
end
|
31
33
|
|
32
34
|
# Iterate over the body, allowing it to be enumerable
|
@@ -42,19 +44,12 @@ module HTTP
|
|
42
44
|
|
43
45
|
raise StateError, "body is being streamed" unless @streaming.nil?
|
44
46
|
|
45
|
-
# see issue 312
|
46
|
-
begin
|
47
|
-
encoding = Encoding.find @encoding
|
48
|
-
rescue ArgumentError
|
49
|
-
encoding = Encoding::BINARY
|
50
|
-
end
|
51
|
-
|
52
47
|
begin
|
53
48
|
@streaming = false
|
54
|
-
@contents = String.new("").force_encoding(encoding)
|
49
|
+
@contents = String.new("").force_encoding(@encoding)
|
55
50
|
|
56
51
|
while (chunk = @stream.readpartial)
|
57
|
-
@contents << chunk.force_encoding(encoding)
|
52
|
+
@contents << chunk.force_encoding(@encoding)
|
58
53
|
end
|
59
54
|
rescue
|
60
55
|
@contents = nil
|
@@ -75,6 +70,15 @@ module HTTP
|
|
75
70
|
def inspect
|
76
71
|
"#<#{self.class}:#{object_id.to_s(16)} @streaming=#{!!@streaming}>"
|
77
72
|
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
# Retrieve encoding by name. If encoding cannot be found, default to binary.
|
77
|
+
def find_encoding(encoding)
|
78
|
+
Encoding.find encoding
|
79
|
+
rescue ArgumentError
|
80
|
+
Encoding::BINARY
|
81
|
+
end
|
78
82
|
end
|
79
83
|
end
|
80
84
|
end
|