http 4.4.1 → 5.0.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 +65 -0
- data/.gitignore +6 -10
- data/.rspec +0 -4
- data/.rubocop.yml +8 -110
- data/.rubocop/layout.yml +8 -0
- data/.rubocop/style.yml +32 -0
- data/.rubocop_todo.yml +192 -0
- data/.yardopts +1 -1
- data/CHANGES.md +112 -3
- data/Gemfile +18 -10
- data/LICENSE.txt +1 -1
- data/README.md +17 -20
- data/Rakefile +2 -10
- data/http.gemspec +3 -3
- data/lib/http/chainable.rb +23 -17
- data/lib/http/client.rb +36 -30
- data/lib/http/connection.rb +11 -7
- data/lib/http/content_type.rb +12 -7
- data/lib/http/feature.rb +3 -1
- data/lib/http/features/auto_deflate.rb +6 -6
- data/lib/http/features/auto_inflate.rb +6 -5
- 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 +2 -1
- data/lib/http/request.rb +28 -11
- data/lib/http/request/body.rb +1 -0
- data/lib/http/request/writer.rb +3 -2
- data/lib/http/response.rb +17 -15
- data/lib/http/response/body.rb +2 -2
- 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/timeout/global.rb +17 -31
- data/lib/http/timeout/null.rb +2 -1
- data/lib/http/timeout/per_operation.rb +31 -54
- data/lib/http/uri.rb +5 -5
- data/lib/http/version.rb +1 -1
- data/spec/lib/http/client_spec.rb +119 -30
- data/spec/lib/http/connection_spec.rb +8 -5
- data/spec/lib/http/features/auto_inflate_spec.rb +4 -2
- data/spec/lib/http/features/instrumentation_spec.rb +28 -21
- data/spec/lib/http/features/logging_spec.rb +8 -9
- 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 +46 -1
- data/spec/lib/http/request/writer_spec.rb +13 -1
- data/spec/lib/http/request_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 +5 -3
- 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.rb +7 -7
- data/spec/support/dummy_server/servlet.rb +17 -6
- data/spec/support/fuubar.rb +21 -0
- data/spec/support/http_handling_shared.rb +4 -4
- data/spec/support/simplecov.rb +19 -0
- data/spec/support/ssl_helper.rb +4 -4
- metadata +18 -12
- data/.coveralls.yml +0 -1
- data/.travis.yml +0 -39
@@ -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
|
data/lib/http/mime_type/json.rb
CHANGED
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 = {})
|
22
|
-
|
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 ||=
|
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 = {})
|
50
|
+
def initialize(options = {})
|
54
51
|
defaults = {
|
55
52
|
:response => :auto,
|
56
53
|
:proxy => {},
|
data/lib/http/redirector.rb
CHANGED
@@ -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 = {})
|
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
|
|
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/request/body.rb
CHANGED
data/lib/http/request/writer.rb
CHANGED
@@ -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 =>
|
116
|
-
raise ConnectionError, "error writing to socket: #{
|
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/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,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 [
|
43
|
+
# @option opts [HTTP::Request] request
|
45
44
|
def initialize(opts)
|
46
45
|
@version = opts.fetch(:version)
|
47
|
-
@
|
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
|
63
|
+
def_delegator :@status, :reason
|
65
64
|
|
66
65
|
# @!method code
|
67
66
|
# @return (see HTTP::Response::Status#code)
|
68
|
-
def_delegator
|
67
|
+
def_delegator :@status, :code
|
69
68
|
|
70
69
|
# @!method to_s
|
71
70
|
# (see HTTP::Response::Body#to_s)
|
72
|
-
def_delegator
|
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
|
76
|
+
def_delegator :@body, :readpartial
|
78
77
|
|
79
78
|
# @!method connection
|
80
79
|
# (see HTTP::Response::Body#connection)
|
81
|
-
def_delegator
|
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]
|
154
|
-
#
|
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(
|
158
|
-
MimeType[
|
159
|
+
def parse(type = nil)
|
160
|
+
MimeType[type || mime_type].decode to_s
|
159
161
|
end
|
160
162
|
|
161
163
|
# Inspect a response
|
data/lib/http/response/body.rb
CHANGED
@@ -27,8 +27,7 @@ module HTTP
|
|
27
27
|
# (see HTTP::Client#readpartial)
|
28
28
|
def readpartial(*args)
|
29
29
|
stream!
|
30
|
-
|
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
|
|
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.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
|
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
|