http 5.3.1 → 6.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/CHANGELOG.md +241 -41
- data/LICENSE.txt +1 -1
- data/README.md +110 -13
- data/UPGRADING.md +491 -0
- data/http.gemspec +32 -29
- data/lib/http/base64.rb +11 -1
- data/lib/http/chainable/helpers.rb +62 -0
- data/lib/http/chainable/verbs.rb +136 -0
- data/lib/http/chainable.rb +232 -136
- data/lib/http/client.rb +158 -127
- data/lib/http/connection/internals.rb +141 -0
- data/lib/http/connection.rb +126 -97
- data/lib/http/content_type.rb +61 -6
- data/lib/http/errors.rb +25 -1
- data/lib/http/feature.rb +65 -5
- data/lib/http/features/auto_deflate.rb +124 -17
- data/lib/http/features/auto_inflate.rb +38 -15
- data/lib/http/features/caching/entry.rb +178 -0
- data/lib/http/features/caching/in_memory_store.rb +63 -0
- data/lib/http/features/caching.rb +216 -0
- data/lib/http/features/digest_auth.rb +234 -0
- data/lib/http/features/instrumentation.rb +97 -17
- data/lib/http/features/logging.rb +183 -5
- data/lib/http/features/normalize_uri.rb +17 -0
- data/lib/http/features/raise_error.rb +18 -3
- data/lib/http/form_data/composite_io.rb +106 -0
- data/lib/http/form_data/file.rb +95 -0
- data/lib/http/form_data/multipart/param.rb +62 -0
- data/lib/http/form_data/multipart.rb +106 -0
- data/lib/http/form_data/part.rb +52 -0
- data/lib/http/form_data/readable.rb +58 -0
- data/lib/http/form_data/urlencoded.rb +175 -0
- data/lib/http/form_data/version.rb +8 -0
- data/lib/http/form_data.rb +102 -0
- data/lib/http/headers/known.rb +3 -0
- data/lib/http/headers/normalizer.rb +17 -36
- data/lib/http/headers.rb +172 -65
- data/lib/http/mime_type/adapter.rb +24 -9
- data/lib/http/mime_type/json.rb +19 -4
- data/lib/http/mime_type.rb +21 -3
- data/lib/http/options/definitions.rb +189 -0
- data/lib/http/options.rb +172 -125
- data/lib/http/redirector.rb +80 -75
- data/lib/http/request/body.rb +87 -6
- data/lib/http/request/builder.rb +184 -0
- data/lib/http/request/proxy.rb +83 -0
- data/lib/http/request/writer.rb +76 -16
- data/lib/http/request.rb +214 -98
- data/lib/http/response/body.rb +103 -18
- data/lib/http/response/inflater.rb +35 -7
- data/lib/http/response/parser.rb +98 -4
- data/lib/http/response/status/reasons.rb +2 -4
- data/lib/http/response/status.rb +141 -31
- data/lib/http/response.rb +219 -61
- data/lib/http/retriable/delay_calculator.rb +38 -11
- data/lib/http/retriable/errors.rb +21 -0
- data/lib/http/retriable/performer.rb +82 -38
- data/lib/http/session.rb +280 -0
- data/lib/http/timeout/global.rb +147 -34
- data/lib/http/timeout/null.rb +155 -9
- data/lib/http/timeout/per_operation.rb +139 -18
- data/lib/http/uri/normalizer.rb +82 -0
- data/lib/http/uri/parsing.rb +182 -0
- data/lib/http/uri.rb +289 -124
- data/lib/http/version.rb +2 -1
- data/lib/http.rb +11 -2
- data/sig/deps.rbs +122 -0
- data/sig/http.rbs +1619 -0
- data/test/http/base64_test.rb +28 -0
- data/test/http/client_test.rb +739 -0
- data/test/http/connection_test.rb +1533 -0
- data/test/http/content_type_test.rb +190 -0
- data/test/http/errors_test.rb +28 -0
- data/test/http/feature_test.rb +49 -0
- data/test/http/features/auto_deflate_test.rb +317 -0
- data/test/http/features/auto_inflate_test.rb +213 -0
- data/test/http/features/caching_test.rb +942 -0
- data/test/http/features/digest_auth_test.rb +996 -0
- data/test/http/features/instrumentation_test.rb +246 -0
- data/test/http/features/logging_test.rb +654 -0
- data/test/http/features/normalize_uri_test.rb +41 -0
- data/test/http/features/raise_error_test.rb +77 -0
- data/test/http/form_data/composite_io_test.rb +215 -0
- data/test/http/form_data/file_test.rb +255 -0
- data/test/http/form_data/fixtures/the-http-gem.info +1 -0
- data/test/http/form_data/multipart_test.rb +303 -0
- data/test/http/form_data/part_test.rb +90 -0
- data/test/http/form_data/urlencoded_test.rb +164 -0
- data/test/http/form_data_test.rb +232 -0
- data/test/http/headers/normalizer_test.rb +93 -0
- data/test/http/headers_test.rb +888 -0
- data/test/http/mime_type/json_test.rb +39 -0
- data/test/http/mime_type_test.rb +150 -0
- data/test/http/options/base_uri_test.rb +148 -0
- data/test/http/options/body_test.rb +21 -0
- data/test/http/options/features_test.rb +38 -0
- data/test/http/options/form_test.rb +21 -0
- data/test/http/options/headers_test.rb +32 -0
- data/test/http/options/json_test.rb +21 -0
- data/test/http/options/merge_test.rb +78 -0
- data/test/http/options/new_test.rb +37 -0
- data/test/http/options/proxy_test.rb +32 -0
- data/test/http/options_test.rb +575 -0
- data/test/http/redirector_test.rb +639 -0
- data/test/http/request/body_test.rb +318 -0
- data/test/http/request/builder_test.rb +623 -0
- data/test/http/request/writer_test.rb +391 -0
- data/test/http/request_test.rb +1733 -0
- data/test/http/response/body_test.rb +292 -0
- data/test/http/response/parser_test.rb +105 -0
- data/test/http/response/status_test.rb +322 -0
- data/test/http/response_test.rb +502 -0
- data/test/http/retriable/delay_calculator_test.rb +194 -0
- data/test/http/retriable/errors_test.rb +71 -0
- data/test/http/retriable/performer_test.rb +551 -0
- data/test/http/session_test.rb +424 -0
- data/test/http/timeout/global_test.rb +239 -0
- data/test/http/timeout/null_test.rb +218 -0
- data/test/http/timeout/per_operation_test.rb +220 -0
- data/test/http/uri/normalizer_test.rb +89 -0
- data/test/http/uri_test.rb +1140 -0
- data/test/http/version_test.rb +15 -0
- data/test/http_test.rb +818 -0
- data/test/regression_tests.rb +27 -0
- data/test/support/dummy_server/encoding_routes.rb +47 -0
- data/test/support/dummy_server/routes.rb +201 -0
- data/test/support/dummy_server/servlet.rb +81 -0
- data/test/support/dummy_server.rb +200 -0
- data/{spec → test}/support/fakeio.rb +2 -2
- data/test/support/http_handling_shared/connection_reuse_tests.rb +97 -0
- data/test/support/http_handling_shared/timeout_tests.rb +134 -0
- data/test/support/http_handling_shared.rb +11 -0
- data/test/support/proxy_server.rb +207 -0
- data/test/support/servers/runner.rb +67 -0
- data/{spec → test}/support/simplecov.rb +11 -2
- data/test/support/ssl_helper.rb +108 -0
- data/test/test_helper.rb +38 -0
- metadata +108 -168
- data/.github/workflows/ci.yml +0 -67
- data/.gitignore +0 -15
- data/.rspec +0 -1
- data/.rubocop/layout.yml +0 -8
- data/.rubocop/metrics.yml +0 -4
- data/.rubocop/rspec.yml +0 -9
- data/.rubocop/style.yml +0 -32
- data/.rubocop.yml +0 -11
- data/.rubocop_todo.yml +0 -219
- data/.yardopts +0 -2
- data/CHANGES_OLD.md +0 -1002
- data/Gemfile +0 -51
- data/Guardfile +0 -18
- data/Rakefile +0 -64
- data/lib/http/headers/mixin.rb +0 -34
- data/lib/http/retriable/client.rb +0 -37
- data/logo.png +0 -0
- data/spec/lib/http/client_spec.rb +0 -556
- data/spec/lib/http/connection_spec.rb +0 -88
- data/spec/lib/http/content_type_spec.rb +0 -47
- data/spec/lib/http/features/auto_deflate_spec.rb +0 -77
- data/spec/lib/http/features/auto_inflate_spec.rb +0 -86
- data/spec/lib/http/features/instrumentation_spec.rb +0 -81
- data/spec/lib/http/features/logging_spec.rb +0 -65
- data/spec/lib/http/features/raise_error_spec.rb +0 -62
- data/spec/lib/http/headers/mixin_spec.rb +0 -36
- data/spec/lib/http/headers/normalizer_spec.rb +0 -52
- data/spec/lib/http/headers_spec.rb +0 -527
- data/spec/lib/http/options/body_spec.rb +0 -15
- data/spec/lib/http/options/features_spec.rb +0 -33
- data/spec/lib/http/options/form_spec.rb +0 -15
- data/spec/lib/http/options/headers_spec.rb +0 -24
- data/spec/lib/http/options/json_spec.rb +0 -15
- data/spec/lib/http/options/merge_spec.rb +0 -68
- data/spec/lib/http/options/new_spec.rb +0 -30
- data/spec/lib/http/options/proxy_spec.rb +0 -20
- data/spec/lib/http/options_spec.rb +0 -13
- data/spec/lib/http/redirector_spec.rb +0 -530
- data/spec/lib/http/request/body_spec.rb +0 -211
- data/spec/lib/http/request/writer_spec.rb +0 -121
- data/spec/lib/http/request_spec.rb +0 -234
- data/spec/lib/http/response/body_spec.rb +0 -85
- data/spec/lib/http/response/parser_spec.rb +0 -74
- data/spec/lib/http/response/status_spec.rb +0 -253
- data/spec/lib/http/response_spec.rb +0 -262
- data/spec/lib/http/retriable/delay_calculator_spec.rb +0 -69
- data/spec/lib/http/retriable/performer_spec.rb +0 -302
- data/spec/lib/http/uri/normalizer_spec.rb +0 -95
- data/spec/lib/http/uri_spec.rb +0 -71
- data/spec/lib/http_spec.rb +0 -535
- data/spec/regression_specs.rb +0 -24
- data/spec/spec_helper.rb +0 -89
- data/spec/support/black_hole.rb +0 -13
- data/spec/support/dummy_server/servlet.rb +0 -203
- data/spec/support/dummy_server.rb +0 -44
- data/spec/support/fuubar.rb +0 -21
- data/spec/support/http_handling_shared.rb +0 -190
- data/spec/support/proxy_server.rb +0 -39
- data/spec/support/servers/config.rb +0 -11
- data/spec/support/servers/runner.rb +0 -19
- data/spec/support/ssl_helper.rb +0 -104
- /data/{spec → test}/support/capture_warning.rb +0 -0
data/lib/http/request/writer.rb
CHANGED
|
@@ -4,19 +4,24 @@ require "http/headers"
|
|
|
4
4
|
|
|
5
5
|
module HTTP
|
|
6
6
|
class Request
|
|
7
|
+
# Streams HTTP requests to a socket
|
|
7
8
|
class Writer
|
|
8
9
|
# CRLF is the universal HTTP delimiter
|
|
9
10
|
CRLF = "\r\n"
|
|
10
11
|
|
|
11
|
-
# Chunked data
|
|
12
|
+
# Chunked data terminator
|
|
12
13
|
ZERO = "0"
|
|
13
14
|
|
|
14
|
-
# Chunked transfer encoding
|
|
15
|
-
CHUNKED = "chunked"
|
|
16
|
-
|
|
17
15
|
# End of a chunked transfer
|
|
18
|
-
CHUNKED_END = "#{ZERO}#{CRLF}#{CRLF}"
|
|
16
|
+
CHUNKED_END = "#{ZERO}#{CRLF}#{CRLF}".freeze
|
|
19
17
|
|
|
18
|
+
# Initialize a new request writer
|
|
19
|
+
#
|
|
20
|
+
# @example
|
|
21
|
+
# Writer.new(socket, body, headers, "GET / HTTP/1.1")
|
|
22
|
+
#
|
|
23
|
+
# @return [HTTP::Request::Writer]
|
|
24
|
+
# @api public
|
|
20
25
|
def initialize(socket, body, headers, headline)
|
|
21
26
|
@body = body
|
|
22
27
|
@socket = socket
|
|
@@ -24,7 +29,13 @@ module HTTP
|
|
|
24
29
|
@request_header = [headline]
|
|
25
30
|
end
|
|
26
31
|
|
|
27
|
-
# Adds headers to the request header
|
|
32
|
+
# Adds headers to the request header array
|
|
33
|
+
#
|
|
34
|
+
# @example
|
|
35
|
+
# writer.add_headers
|
|
36
|
+
#
|
|
37
|
+
# @return [void]
|
|
38
|
+
# @api public
|
|
28
39
|
def add_headers
|
|
29
40
|
@headers.each do |field, value|
|
|
30
41
|
@request_header << "#{field}: #{value}"
|
|
@@ -32,6 +43,12 @@ module HTTP
|
|
|
32
43
|
end
|
|
33
44
|
|
|
34
45
|
# Stream the request to a socket
|
|
46
|
+
#
|
|
47
|
+
# @example
|
|
48
|
+
# writer.stream
|
|
49
|
+
#
|
|
50
|
+
# @return [void]
|
|
51
|
+
# @api public
|
|
35
52
|
def stream
|
|
36
53
|
add_headers
|
|
37
54
|
add_body_type_headers
|
|
@@ -39,32 +56,54 @@ module HTTP
|
|
|
39
56
|
end
|
|
40
57
|
|
|
41
58
|
# Send headers needed to connect through proxy
|
|
59
|
+
#
|
|
60
|
+
# @example
|
|
61
|
+
# writer.connect_through_proxy
|
|
62
|
+
#
|
|
63
|
+
# @return [void]
|
|
64
|
+
# @api public
|
|
42
65
|
def connect_through_proxy
|
|
43
66
|
add_headers
|
|
44
67
|
write(join_headers)
|
|
45
68
|
end
|
|
46
69
|
|
|
47
|
-
# Adds
|
|
48
|
-
#
|
|
70
|
+
# Adds content length or transfer encoding headers
|
|
71
|
+
#
|
|
72
|
+
# @example
|
|
73
|
+
# writer.add_body_type_headers
|
|
74
|
+
#
|
|
75
|
+
# @return [void]
|
|
76
|
+
# @api public
|
|
49
77
|
def add_body_type_headers
|
|
50
78
|
return if @headers[Headers::CONTENT_LENGTH] || chunked? || (
|
|
51
79
|
@body.source.nil? && %w[GET HEAD DELETE CONNECT].any? do |method|
|
|
52
|
-
@request_header
|
|
80
|
+
@request_header.fetch(0).start_with?("#{method} ")
|
|
53
81
|
end
|
|
54
82
|
)
|
|
55
83
|
|
|
56
84
|
@request_header << "#{Headers::CONTENT_LENGTH}: #{@body.size}"
|
|
57
85
|
end
|
|
58
86
|
|
|
59
|
-
# Joins
|
|
60
|
-
#
|
|
87
|
+
# Joins headers into an HTTP request header string
|
|
88
|
+
#
|
|
89
|
+
# @example
|
|
90
|
+
# writer.join_headers
|
|
91
|
+
#
|
|
92
|
+
# @return [String]
|
|
93
|
+
# @api public
|
|
61
94
|
def join_headers
|
|
62
95
|
# join the headers array with crlfs, stick two on the end because
|
|
63
96
|
# that ends the request header
|
|
64
97
|
@request_header.join(CRLF) + (CRLF * 2)
|
|
65
98
|
end
|
|
66
99
|
|
|
67
|
-
# Writes HTTP request data into the socket
|
|
100
|
+
# Writes HTTP request data into the socket
|
|
101
|
+
#
|
|
102
|
+
# @example
|
|
103
|
+
# writer.send_request
|
|
104
|
+
#
|
|
105
|
+
# @return [void]
|
|
106
|
+
# @api public
|
|
68
107
|
def send_request
|
|
69
108
|
each_chunk { |chunk| write chunk }
|
|
70
109
|
rescue Errno::EPIPE
|
|
@@ -72,12 +111,18 @@ module HTTP
|
|
|
72
111
|
nil
|
|
73
112
|
end
|
|
74
113
|
|
|
75
|
-
# Yields chunks of request data
|
|
114
|
+
# Yields chunks of request data for the socket
|
|
76
115
|
#
|
|
77
116
|
# It's important to send the request in a single write call when possible
|
|
78
117
|
# in order to play nicely with Nagle's algorithm. Making two writes in a
|
|
79
118
|
# row triggers a pathological case where Nagle is expecting a third write
|
|
80
119
|
# that never happens.
|
|
120
|
+
#
|
|
121
|
+
# @example
|
|
122
|
+
# writer.each_chunk { |chunk| socket.write(chunk) }
|
|
123
|
+
#
|
|
124
|
+
# @return [void]
|
|
125
|
+
# @api public
|
|
81
126
|
def each_chunk
|
|
82
127
|
data = join_headers
|
|
83
128
|
|
|
@@ -92,7 +137,13 @@ module HTTP
|
|
|
92
137
|
yield CHUNKED_END if chunked?
|
|
93
138
|
end
|
|
94
139
|
|
|
95
|
-
# Returns
|
|
140
|
+
# Returns chunk encoded per Transfer-Encoding header
|
|
141
|
+
#
|
|
142
|
+
# @example
|
|
143
|
+
# writer.encode_chunk("hello")
|
|
144
|
+
#
|
|
145
|
+
# @return [String]
|
|
146
|
+
# @api public
|
|
96
147
|
def encode_chunk(chunk)
|
|
97
148
|
if chunked?
|
|
98
149
|
chunk.bytesize.to_s(16) << CRLF << chunk << CRLF
|
|
@@ -101,14 +152,23 @@ module HTTP
|
|
|
101
152
|
end
|
|
102
153
|
end
|
|
103
154
|
|
|
104
|
-
# Returns true if
|
|
155
|
+
# Returns true if using chunked transfer encoding
|
|
156
|
+
#
|
|
157
|
+
# @example
|
|
158
|
+
# writer.chunked?
|
|
159
|
+
#
|
|
160
|
+
# @return [Boolean]
|
|
161
|
+
# @api public
|
|
105
162
|
def chunked?
|
|
106
|
-
@headers[Headers::TRANSFER_ENCODING]
|
|
163
|
+
@headers[Headers::TRANSFER_ENCODING].eql?(Headers::CHUNKED)
|
|
107
164
|
end
|
|
108
165
|
|
|
109
166
|
private
|
|
110
167
|
|
|
168
|
+
# Write data to the underlying socket
|
|
169
|
+
# @return [void]
|
|
111
170
|
# @raise [SocketWriteError] when unable to write to socket
|
|
171
|
+
# @api private
|
|
112
172
|
def write(data)
|
|
113
173
|
until data.empty?
|
|
114
174
|
length = @socket.write(data)
|
data/lib/http/request.rb
CHANGED
|
@@ -7,16 +7,18 @@ require "http/base64"
|
|
|
7
7
|
require "http/errors"
|
|
8
8
|
require "http/headers"
|
|
9
9
|
require "http/request/body"
|
|
10
|
+
require "http/request/proxy"
|
|
10
11
|
require "http/request/writer"
|
|
11
12
|
require "http/version"
|
|
12
13
|
require "http/uri"
|
|
13
14
|
|
|
14
15
|
module HTTP
|
|
16
|
+
# Represents an HTTP request with verb, URI, headers, and body
|
|
15
17
|
class Request
|
|
16
18
|
extend Forwardable
|
|
17
19
|
|
|
18
20
|
include HTTP::Base64
|
|
19
|
-
include
|
|
21
|
+
include Proxy
|
|
20
22
|
|
|
21
23
|
# The method given was not understood
|
|
22
24
|
class UnsupportedMethodError < RequestError; end
|
|
@@ -25,8 +27,9 @@ module HTTP
|
|
|
25
27
|
class UnsupportedSchemeError < RequestError; end
|
|
26
28
|
|
|
27
29
|
# Default User-Agent header value
|
|
28
|
-
USER_AGENT = "http.rb/#{HTTP::VERSION}"
|
|
30
|
+
USER_AGENT = "http.rb/#{HTTP::VERSION}".freeze
|
|
29
31
|
|
|
32
|
+
# Supported HTTP methods
|
|
30
33
|
METHODS = [
|
|
31
34
|
# RFC 2616: Hypertext Transfer Protocol -- HTTP/1.1
|
|
32
35
|
:options, :get, :head, :post, :put, :delete, :trace, :connect,
|
|
@@ -61,115 +64,176 @@ module HTTP
|
|
|
61
64
|
|
|
62
65
|
# Default ports of supported schemes
|
|
63
66
|
PORTS = {
|
|
64
|
-
:
|
|
65
|
-
:
|
|
66
|
-
:
|
|
67
|
-
:
|
|
67
|
+
http: 80,
|
|
68
|
+
https: 443,
|
|
69
|
+
ws: 80,
|
|
70
|
+
wss: 443
|
|
68
71
|
}.freeze
|
|
69
72
|
|
|
70
|
-
#
|
|
73
|
+
# HTTP method as a lowercase symbol
|
|
74
|
+
#
|
|
75
|
+
# @example
|
|
76
|
+
# request.verb # => :get
|
|
77
|
+
#
|
|
78
|
+
# @return [Symbol]
|
|
79
|
+
# @api public
|
|
71
80
|
attr_reader :verb
|
|
72
81
|
|
|
73
|
-
#
|
|
82
|
+
# URI scheme as a lowercase symbol
|
|
83
|
+
#
|
|
84
|
+
# @example
|
|
85
|
+
# request.scheme # => :https
|
|
86
|
+
#
|
|
87
|
+
# @return [Symbol]
|
|
88
|
+
# @api public
|
|
74
89
|
attr_reader :scheme
|
|
75
90
|
|
|
91
|
+
# URI normalizer callable
|
|
92
|
+
#
|
|
93
|
+
# @example
|
|
94
|
+
# request.uri_normalizer
|
|
95
|
+
#
|
|
96
|
+
# @return [#call]
|
|
97
|
+
# @api public
|
|
76
98
|
attr_reader :uri_normalizer
|
|
77
99
|
|
|
78
|
-
#
|
|
79
|
-
#
|
|
100
|
+
# Request URI
|
|
101
|
+
#
|
|
102
|
+
# @example
|
|
103
|
+
# request.uri # => #<HTTP::URI ...>
|
|
104
|
+
#
|
|
105
|
+
# @return [HTTP::URI]
|
|
106
|
+
# @api public
|
|
80
107
|
attr_reader :uri
|
|
81
|
-
attr_reader :proxy, :body, :version
|
|
82
|
-
|
|
83
|
-
# @option opts [String] :version
|
|
84
|
-
# @option opts [#to_s] :verb HTTP request method
|
|
85
|
-
# @option opts [#call] :uri_normalizer (HTTP::URI::NORMALIZER)
|
|
86
|
-
# @option opts [HTTP::URI, #to_s] :uri
|
|
87
|
-
# @option opts [Hash] :headers
|
|
88
|
-
# @option opts [Hash] :proxy
|
|
89
|
-
# @option opts [String, Enumerable, IO, nil] :body
|
|
90
|
-
def initialize(opts)
|
|
91
|
-
@verb = opts.fetch(:verb).to_s.downcase.to_sym
|
|
92
|
-
@uri_normalizer = opts[:uri_normalizer] || HTTP::URI::NORMALIZER
|
|
93
|
-
|
|
94
|
-
@uri = @uri_normalizer.call(opts.fetch(:uri))
|
|
95
|
-
@scheme = @uri.scheme.to_s.downcase.to_sym if @uri.scheme
|
|
96
108
|
|
|
97
|
-
|
|
98
|
-
|
|
109
|
+
# Proxy configuration hash
|
|
110
|
+
#
|
|
111
|
+
# @example
|
|
112
|
+
# request.proxy
|
|
113
|
+
#
|
|
114
|
+
# @return [Hash]
|
|
115
|
+
# @api public
|
|
116
|
+
attr_reader :proxy
|
|
117
|
+
|
|
118
|
+
# Request body object
|
|
119
|
+
#
|
|
120
|
+
# @example
|
|
121
|
+
# request.body
|
|
122
|
+
#
|
|
123
|
+
# @return [HTTP::Request::Body]
|
|
124
|
+
# @api public
|
|
125
|
+
attr_reader :body
|
|
99
126
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
127
|
+
# The HTTP headers collection
|
|
128
|
+
#
|
|
129
|
+
# @example
|
|
130
|
+
# request.headers
|
|
131
|
+
#
|
|
132
|
+
# @return [HTTP::Headers]
|
|
133
|
+
# @api public
|
|
134
|
+
attr_reader :headers
|
|
135
|
+
|
|
136
|
+
# HTTP version string
|
|
137
|
+
#
|
|
138
|
+
# @example
|
|
139
|
+
# request.version # => "1.1"
|
|
140
|
+
#
|
|
141
|
+
# @return [String]
|
|
142
|
+
# @api public
|
|
143
|
+
attr_reader :version
|
|
144
|
+
|
|
145
|
+
# Create a new HTTP request
|
|
146
|
+
#
|
|
147
|
+
# @param [#to_s] verb HTTP request method
|
|
148
|
+
# @param [HTTP::URI, #to_s] uri
|
|
149
|
+
# @param [Hash] headers
|
|
150
|
+
# @param [Hash] proxy
|
|
151
|
+
# @param [String, Enumerable, IO, nil] body
|
|
152
|
+
# @param [String] version
|
|
153
|
+
# @param [#call] uri_normalizer
|
|
154
|
+
#
|
|
155
|
+
# @example
|
|
156
|
+
# Request.new(verb: :get, uri: "https://example.com")
|
|
157
|
+
#
|
|
158
|
+
# @return [HTTP::Request]
|
|
159
|
+
# @api public
|
|
160
|
+
def initialize(verb:, uri:, headers: nil, proxy: {}, body: nil, version: "1.1",
|
|
161
|
+
uri_normalizer: nil)
|
|
162
|
+
@uri_normalizer = uri_normalizer || HTTP::URI::NORMALIZER
|
|
163
|
+
@verb = verb.to_s.downcase.to_sym
|
|
164
|
+
parse_uri!(uri)
|
|
165
|
+
validate_method_and_scheme!
|
|
166
|
+
|
|
167
|
+
@proxy = proxy
|
|
168
|
+
@version = version
|
|
169
|
+
@headers = prepare_headers(headers)
|
|
170
|
+
@body = prepare_body(body)
|
|
104
171
|
end
|
|
105
172
|
|
|
106
173
|
# Returns new Request with updated uri
|
|
174
|
+
#
|
|
175
|
+
# @example
|
|
176
|
+
# request.redirect("https://example.com/new")
|
|
177
|
+
#
|
|
178
|
+
# @return [HTTP::Request]
|
|
179
|
+
# @api public
|
|
107
180
|
def redirect(uri, verb = @verb)
|
|
108
|
-
|
|
109
|
-
headers
|
|
110
|
-
|
|
111
|
-
new_body = body.source
|
|
112
|
-
if verb == :get
|
|
113
|
-
# request bodies should not always be resubmitted when following a redirect
|
|
114
|
-
# some servers will close the connection after receiving the request headers
|
|
115
|
-
# which may cause Errno::ECONNRESET: Connection reset by peer
|
|
116
|
-
# see https://github.com/httprb/http/issues/649
|
|
117
|
-
# new_body = Request::Body.new(nil)
|
|
118
|
-
new_body = nil
|
|
119
|
-
# the CONTENT_TYPE header causes problems if set on a get request w/ an empty body
|
|
120
|
-
# the server might assume that there should be content if it is set to multipart
|
|
121
|
-
# rack raises EmptyContentError if this happens
|
|
122
|
-
headers.delete(Headers::CONTENT_TYPE)
|
|
123
|
-
end
|
|
181
|
+
redirect_uri = @uri.join(uri)
|
|
182
|
+
headers = redirect_headers(redirect_uri, verb)
|
|
183
|
+
new_body = verb == :get ? nil : body.source
|
|
124
184
|
|
|
125
185
|
self.class.new(
|
|
126
|
-
:
|
|
127
|
-
:
|
|
128
|
-
:
|
|
129
|
-
:
|
|
130
|
-
:
|
|
131
|
-
:
|
|
132
|
-
:
|
|
186
|
+
verb: verb,
|
|
187
|
+
uri: redirect_uri,
|
|
188
|
+
headers: headers,
|
|
189
|
+
proxy: proxy,
|
|
190
|
+
body: new_body,
|
|
191
|
+
version: version,
|
|
192
|
+
uri_normalizer: uri_normalizer
|
|
133
193
|
)
|
|
134
194
|
end
|
|
135
195
|
|
|
136
196
|
# Stream the request to a socket
|
|
197
|
+
#
|
|
198
|
+
# @example
|
|
199
|
+
# request.stream(socket)
|
|
200
|
+
#
|
|
201
|
+
# @return [void]
|
|
202
|
+
# @api public
|
|
137
203
|
def stream(socket)
|
|
138
204
|
include_proxy_headers if using_proxy? && !@uri.https?
|
|
139
205
|
Request::Writer.new(socket, body, headers, headline).stream
|
|
140
206
|
end
|
|
141
207
|
|
|
142
208
|
# Is this request using a proxy?
|
|
209
|
+
#
|
|
210
|
+
# @example
|
|
211
|
+
# request.using_proxy?
|
|
212
|
+
#
|
|
213
|
+
# @return [Boolean]
|
|
214
|
+
# @api public
|
|
143
215
|
def using_proxy?
|
|
144
216
|
proxy && proxy.keys.size >= 2
|
|
145
217
|
end
|
|
146
218
|
|
|
147
219
|
# Is this request using an authenticated proxy?
|
|
220
|
+
#
|
|
221
|
+
# @example
|
|
222
|
+
# request.using_authenticated_proxy?
|
|
223
|
+
#
|
|
224
|
+
# @return [Boolean]
|
|
225
|
+
# @api public
|
|
148
226
|
def using_authenticated_proxy?
|
|
149
227
|
proxy && proxy.keys.size >= 4
|
|
150
228
|
end
|
|
151
229
|
|
|
152
|
-
def include_proxy_headers
|
|
153
|
-
headers.merge!(proxy[:proxy_headers]) if proxy.key?(:proxy_headers)
|
|
154
|
-
include_proxy_authorization_header if using_authenticated_proxy?
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
# Compute and add the Proxy-Authorization header
|
|
158
|
-
def include_proxy_authorization_header
|
|
159
|
-
headers[Headers::PROXY_AUTHORIZATION] = proxy_authorization_header
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
def proxy_authorization_header
|
|
163
|
-
digest = encode64("#{proxy[:proxy_username]}:#{proxy[:proxy_password]}")
|
|
164
|
-
"Basic #{digest}"
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
# Setup tunnel through proxy for SSL request
|
|
168
|
-
def connect_using_proxy(socket)
|
|
169
|
-
Request::Writer.new(socket, nil, proxy_connect_headers, proxy_connect_header).connect_through_proxy
|
|
170
|
-
end
|
|
171
|
-
|
|
172
230
|
# Compute HTTP request header for direct or proxy request
|
|
231
|
+
#
|
|
232
|
+
# @example
|
|
233
|
+
# request.headline
|
|
234
|
+
#
|
|
235
|
+
# @return [String]
|
|
236
|
+
# @api public
|
|
173
237
|
def headline
|
|
174
238
|
request_uri =
|
|
175
239
|
if using_proxy? && !uri.https?
|
|
@@ -183,34 +247,29 @@ module HTTP
|
|
|
183
247
|
"#{verb.to_s.upcase} #{request_uri} HTTP/#{version}"
|
|
184
248
|
end
|
|
185
249
|
|
|
186
|
-
# Compute HTTP request header SSL proxy connection
|
|
187
|
-
def proxy_connect_header
|
|
188
|
-
"CONNECT #{host}:#{port} HTTP/#{version}"
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
# Headers to send with proxy connect request
|
|
192
|
-
def proxy_connect_headers
|
|
193
|
-
connect_headers = HTTP::Headers.coerce(
|
|
194
|
-
Headers::HOST => headers[Headers::HOST],
|
|
195
|
-
Headers::USER_AGENT => headers[Headers::USER_AGENT]
|
|
196
|
-
)
|
|
197
|
-
|
|
198
|
-
connect_headers[Headers::PROXY_AUTHORIZATION] = proxy_authorization_header if using_authenticated_proxy?
|
|
199
|
-
connect_headers.merge!(proxy[:proxy_headers]) if proxy.key?(:proxy_headers)
|
|
200
|
-
connect_headers
|
|
201
|
-
end
|
|
202
|
-
|
|
203
250
|
# Host for tcp socket
|
|
251
|
+
#
|
|
252
|
+
# @example
|
|
253
|
+
# request.socket_host
|
|
254
|
+
#
|
|
255
|
+
# @return [String]
|
|
256
|
+
# @api public
|
|
204
257
|
def socket_host
|
|
205
258
|
using_proxy? ? proxy[:proxy_address] : host
|
|
206
259
|
end
|
|
207
260
|
|
|
208
261
|
# Port for tcp socket
|
|
262
|
+
#
|
|
263
|
+
# @example
|
|
264
|
+
# request.socket_port
|
|
265
|
+
#
|
|
266
|
+
# @return [Integer]
|
|
267
|
+
# @api public
|
|
209
268
|
def socket_port
|
|
210
269
|
using_proxy? ? proxy[:proxy_port] : port
|
|
211
270
|
end
|
|
212
271
|
|
|
213
|
-
# Human-readable representation of base request info
|
|
272
|
+
# Human-readable representation of base request info
|
|
214
273
|
#
|
|
215
274
|
# @example
|
|
216
275
|
#
|
|
@@ -218,23 +277,54 @@ module HTTP
|
|
|
218
277
|
# # => #<HTTP::Request/1.1 GET https://example.com>
|
|
219
278
|
#
|
|
220
279
|
# @return [String]
|
|
280
|
+
# @api public
|
|
221
281
|
def inspect
|
|
222
|
-
"
|
|
282
|
+
format("#<%s/%s %s %s>", self.class, @version, String(verb).upcase, uri)
|
|
223
283
|
end
|
|
224
284
|
|
|
225
285
|
private
|
|
226
286
|
|
|
287
|
+
# Build headers for a redirect request
|
|
288
|
+
#
|
|
289
|
+
# Strips the Host header (it will be regenerated), sensitive credentials
|
|
290
|
+
# on cross-origin redirects, and Content-Type on GET verb changes.
|
|
291
|
+
#
|
|
292
|
+
# @param [HTTP::URI] redirect_uri the target URI
|
|
293
|
+
# @param [Symbol] verb the HTTP verb for the redirected request
|
|
294
|
+
# @return [HTTP::Headers] headers for the redirect
|
|
295
|
+
# @api private
|
|
296
|
+
def redirect_headers(redirect_uri, verb)
|
|
297
|
+
headers = self.headers.dup
|
|
298
|
+
headers.delete(Headers::HOST)
|
|
299
|
+
|
|
300
|
+
# Strip sensitive headers when redirecting to a different origin
|
|
301
|
+
# (scheme + host + port) to prevent credential leakage.
|
|
302
|
+
unless @uri.origin == redirect_uri.origin
|
|
303
|
+
headers.delete(Headers::AUTHORIZATION)
|
|
304
|
+
headers.delete(Headers::COOKIE)
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
headers.delete(Headers::CONTENT_TYPE) if verb == :get
|
|
308
|
+
|
|
309
|
+
headers
|
|
310
|
+
end
|
|
311
|
+
|
|
227
312
|
# @!attribute [r] host
|
|
313
|
+
# Host from the URI
|
|
228
314
|
# @return [String]
|
|
315
|
+
# @api private
|
|
229
316
|
def_delegator :@uri, :host
|
|
230
317
|
|
|
231
|
-
#
|
|
232
|
-
#
|
|
318
|
+
# Return the port for the request URI
|
|
319
|
+
# @return [Fixnum]
|
|
320
|
+
# @api private
|
|
233
321
|
def port
|
|
234
322
|
@uri.port || @uri.default_port
|
|
235
323
|
end
|
|
236
324
|
|
|
237
|
-
#
|
|
325
|
+
# Build default Host header value
|
|
326
|
+
# @return [String]
|
|
327
|
+
# @api private
|
|
238
328
|
def default_host_header_value
|
|
239
329
|
value = PORTS[@scheme] == port ? host : "#{host}:#{port}"
|
|
240
330
|
|
|
@@ -243,12 +333,38 @@ module HTTP
|
|
|
243
333
|
value
|
|
244
334
|
end
|
|
245
335
|
|
|
336
|
+
# Parse and normalize the URI, setting scheme
|
|
337
|
+
# @return [void]
|
|
338
|
+
# @api private
|
|
339
|
+
def parse_uri!(uri)
|
|
340
|
+
raise ArgumentError, "uri is nil" if uri.nil?
|
|
341
|
+
raise ArgumentError, "uri is empty" if uri.is_a?(String) && uri.empty?
|
|
342
|
+
|
|
343
|
+
@uri = @uri_normalizer.call(uri)
|
|
344
|
+
@scheme = String(@uri.scheme).downcase.to_sym if @uri.scheme
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
# Validate HTTP method and URI scheme
|
|
348
|
+
# @return [void]
|
|
349
|
+
# @api private
|
|
350
|
+
def validate_method_and_scheme!
|
|
351
|
+
raise(UnsupportedMethodError, "unknown method: #{verb}") unless METHODS.include?(@verb)
|
|
352
|
+
raise(HTTP::URI::InvalidError, "invalid URI: #{@uri}") unless @scheme
|
|
353
|
+
raise(UnsupportedSchemeError, "unknown scheme: #{scheme}") unless SCHEMES.include?(@scheme)
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
# Coerce input into a Body object
|
|
357
|
+
# @return [HTTP::Request::Body]
|
|
358
|
+
# @api private
|
|
246
359
|
def prepare_body(body)
|
|
247
|
-
body.is_a?(
|
|
360
|
+
body.is_a?(Body) ? body : Body.new(body)
|
|
248
361
|
end
|
|
249
362
|
|
|
363
|
+
# Build headers with default values
|
|
364
|
+
# @return [HTTP::Headers]
|
|
365
|
+
# @api private
|
|
250
366
|
def prepare_headers(headers)
|
|
251
|
-
headers =
|
|
367
|
+
headers = Headers.coerce(headers || {})
|
|
252
368
|
|
|
253
369
|
headers[Headers::HOST] ||= default_host_header_value
|
|
254
370
|
headers[Headers::USER_AGENT] ||= USER_AGENT
|