http 4.4.0 → 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.
- 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 +90 -0
- data/Gemfile +18 -10
- 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 +13 -10
- 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 +75 -49
- 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 +2 -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 +74 -0
- data/spec/lib/http/response/status_spec.rb +3 -3
- data/spec/lib/http/response_spec.rb +11 -22
- 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 +21 -14
- data/.coveralls.yml +0 -1
- data/.travis.yml +0 -39
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
|
@@ -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
|
172
|
-
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]
|
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]
|
227
|
-
headers[Headers::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
|
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)
|
160
|
+
MimeType[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,66 +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
|
-
@
|
48
|
+
def add_header(name, value)
|
49
|
+
@headers.add(name, value)
|
53
50
|
end
|
54
51
|
|
55
|
-
def
|
56
|
-
@
|
52
|
+
def mark_message_finished
|
53
|
+
@message_finished = true
|
57
54
|
end
|
58
55
|
|
59
|
-
def
|
60
|
-
@
|
56
|
+
def finished?
|
57
|
+
@message_finished
|
61
58
|
end
|
62
59
|
|
63
|
-
def
|
60
|
+
def add_body(chunk)
|
64
61
|
if @chunk
|
65
62
|
@chunk << chunk
|
66
63
|
else
|
@@ -82,21 +79,50 @@ module HTTP
|
|
82
79
|
chunk
|
83
80
|
end
|
84
81
|
|
85
|
-
|
86
|
-
|
87
|
-
|
82
|
+
class Handler < LLHttp::Delegate
|
83
|
+
def initialize(target)
|
84
|
+
@target = target
|
85
|
+
super()
|
86
|
+
reset
|
87
|
+
end
|
88
88
|
|
89
|
-
|
90
|
-
|
89
|
+
def reset
|
90
|
+
@reading_header_value = false
|
91
|
+
@field_value = +""
|
92
|
+
@field = +""
|
93
|
+
end
|
91
94
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
end
|
95
|
+
def on_header_field(field)
|
96
|
+
append_header if @reading_header_value
|
97
|
+
@field << field
|
98
|
+
end
|
97
99
|
|
98
|
-
|
99
|
-
|
100
|
+
def on_header_value(value)
|
101
|
+
@reading_header_value = true
|
102
|
+
@field_value << value
|
103
|
+
end
|
104
|
+
|
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
|
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
|
100
126
|
end
|
101
127
|
end
|
102
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.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/timeout/global.rb
CHANGED
@@ -59,22 +59,12 @@ module HTTP
|
|
59
59
|
|
60
60
|
private
|
61
61
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
end
|
66
|
-
|
67
|
-
def write_nonblock(data)
|
68
|
-
@socket.write_nonblock(data)
|
69
|
-
end
|
70
|
-
else
|
71
|
-
def read_nonblock(size, buffer = nil)
|
72
|
-
@socket.read_nonblock(size, buffer, :exception => false)
|
73
|
-
end
|
62
|
+
def read_nonblock(size, buffer = nil)
|
63
|
+
@socket.read_nonblock(size, buffer, :exception => false)
|
64
|
+
end
|
74
65
|
|
75
|
-
|
76
|
-
|
77
|
-
end
|
66
|
+
def write_nonblock(data)
|
67
|
+
@socket.write_nonblock(data, :exception => false)
|
78
68
|
end
|
79
69
|
|
80
70
|
# Perform the given I/O operation with the given argument
|
@@ -82,20 +72,18 @@ module HTTP
|
|
82
72
|
reset_timer
|
83
73
|
|
84
74
|
loop do
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
else return result
|
93
|
-
end
|
94
|
-
rescue IO::WaitReadable
|
95
|
-
wait_readable_or_timeout
|
96
|
-
rescue IO::WaitWritable
|
97
|
-
wait_writable_or_timeout
|
75
|
+
result = yield
|
76
|
+
|
77
|
+
case result
|
78
|
+
when :wait_readable then wait_readable_or_timeout
|
79
|
+
when :wait_writable then wait_writable_or_timeout
|
80
|
+
when NilClass then return :eof
|
81
|
+
else return result
|
98
82
|
end
|
83
|
+
rescue IO::WaitReadable
|
84
|
+
wait_readable_or_timeout
|
85
|
+
rescue IO::WaitWritable
|
86
|
+
wait_writable_or_timeout
|
99
87
|
end
|
100
88
|
rescue EOFError
|
101
89
|
:eof
|
@@ -121,9 +109,7 @@ module HTTP
|
|
121
109
|
|
122
110
|
def log_time
|
123
111
|
@time_left -= (Time.now - @started)
|
124
|
-
if @time_left <= 0
|
125
|
-
raise TimeoutError, "Timed out after using the allocated #{@timeout} seconds"
|
126
|
-
end
|
112
|
+
raise TimeoutError, "Timed out after using the allocated #{@timeout} seconds" if @time_left <= 0
|
127
113
|
|
128
114
|
reset_timer
|
129
115
|
end
|