http 5.2.0 → 6.0.2

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.
Files changed (128) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +110 -13
  4. data/http.gemspec +38 -35
  5. data/lib/http/base64.rb +22 -0
  6. data/lib/http/chainable/helpers.rb +62 -0
  7. data/lib/http/chainable/verbs.rb +136 -0
  8. data/lib/http/chainable.rb +249 -129
  9. data/lib/http/client.rb +158 -127
  10. data/lib/http/connection/internals.rb +141 -0
  11. data/lib/http/connection.rb +128 -97
  12. data/lib/http/content_type.rb +61 -6
  13. data/lib/http/errors.rb +41 -1
  14. data/lib/http/feature.rb +67 -6
  15. data/lib/http/features/auto_deflate.rb +124 -17
  16. data/lib/http/features/auto_inflate.rb +38 -15
  17. data/lib/http/features/caching/entry.rb +178 -0
  18. data/lib/http/features/caching/in_memory_store.rb +63 -0
  19. data/lib/http/features/caching.rb +216 -0
  20. data/lib/http/features/digest_auth.rb +234 -0
  21. data/lib/http/features/instrumentation.rb +97 -17
  22. data/lib/http/features/logging.rb +183 -5
  23. data/lib/http/features/normalize_uri.rb +17 -0
  24. data/lib/http/features/raise_error.rb +37 -0
  25. data/lib/http/form_data/composite_io.rb +106 -0
  26. data/lib/http/form_data/file.rb +95 -0
  27. data/lib/http/form_data/multipart/param.rb +62 -0
  28. data/lib/http/form_data/multipart.rb +106 -0
  29. data/lib/http/form_data/part.rb +52 -0
  30. data/lib/http/form_data/readable.rb +58 -0
  31. data/lib/http/form_data/urlencoded.rb +175 -0
  32. data/lib/http/form_data/version.rb +8 -0
  33. data/lib/http/form_data.rb +102 -0
  34. data/lib/http/headers/known.rb +3 -0
  35. data/lib/http/headers/normalizer.rb +50 -0
  36. data/lib/http/headers.rb +185 -92
  37. data/lib/http/mime_type/adapter.rb +24 -9
  38. data/lib/http/mime_type/json.rb +19 -4
  39. data/lib/http/mime_type.rb +21 -3
  40. data/lib/http/options/definitions.rb +189 -0
  41. data/lib/http/options.rb +172 -125
  42. data/lib/http/redirector.rb +80 -75
  43. data/lib/http/request/body.rb +87 -6
  44. data/lib/http/request/builder.rb +184 -0
  45. data/lib/http/request/proxy.rb +83 -0
  46. data/lib/http/request/writer.rb +78 -17
  47. data/lib/http/request.rb +216 -99
  48. data/lib/http/response/body.rb +103 -18
  49. data/lib/http/response/inflater.rb +35 -7
  50. data/lib/http/response/parser.rb +98 -4
  51. data/lib/http/response/status/reasons.rb +2 -4
  52. data/lib/http/response/status.rb +141 -31
  53. data/lib/http/response.rb +219 -61
  54. data/lib/http/retriable/delay_calculator.rb +91 -0
  55. data/lib/http/retriable/errors.rb +35 -0
  56. data/lib/http/retriable/performer.rb +197 -0
  57. data/lib/http/session.rb +280 -0
  58. data/lib/http/timeout/global.rb +147 -34
  59. data/lib/http/timeout/null.rb +155 -9
  60. data/lib/http/timeout/per_operation.rb +139 -18
  61. data/lib/http/uri/normalizer.rb +82 -0
  62. data/lib/http/uri/parsing.rb +182 -0
  63. data/lib/http/uri.rb +289 -124
  64. data/lib/http/version.rb +2 -1
  65. data/lib/http.rb +11 -1
  66. data/sig/http.rbs +1619 -0
  67. metadata +42 -175
  68. data/.github/workflows/ci.yml +0 -67
  69. data/.gitignore +0 -15
  70. data/.rspec +0 -1
  71. data/.rubocop/layout.yml +0 -8
  72. data/.rubocop/metrics.yml +0 -4
  73. data/.rubocop/style.yml +0 -32
  74. data/.rubocop.yml +0 -11
  75. data/.rubocop_todo.yml +0 -206
  76. data/.yardopts +0 -2
  77. data/CHANGELOG.md +0 -41
  78. data/CHANGES_OLD.md +0 -1002
  79. data/CONTRIBUTING.md +0 -26
  80. data/Gemfile +0 -50
  81. data/Guardfile +0 -18
  82. data/Rakefile +0 -64
  83. data/SECURITY.md +0 -17
  84. data/lib/http/headers/mixin.rb +0 -34
  85. data/logo.png +0 -0
  86. data/spec/lib/http/client_spec.rb +0 -556
  87. data/spec/lib/http/connection_spec.rb +0 -88
  88. data/spec/lib/http/content_type_spec.rb +0 -47
  89. data/spec/lib/http/features/auto_deflate_spec.rb +0 -77
  90. data/spec/lib/http/features/auto_inflate_spec.rb +0 -86
  91. data/spec/lib/http/features/instrumentation_spec.rb +0 -81
  92. data/spec/lib/http/features/logging_spec.rb +0 -65
  93. data/spec/lib/http/headers/mixin_spec.rb +0 -36
  94. data/spec/lib/http/headers_spec.rb +0 -527
  95. data/spec/lib/http/options/body_spec.rb +0 -15
  96. data/spec/lib/http/options/features_spec.rb +0 -33
  97. data/spec/lib/http/options/form_spec.rb +0 -15
  98. data/spec/lib/http/options/headers_spec.rb +0 -24
  99. data/spec/lib/http/options/json_spec.rb +0 -15
  100. data/spec/lib/http/options/merge_spec.rb +0 -68
  101. data/spec/lib/http/options/new_spec.rb +0 -30
  102. data/spec/lib/http/options/proxy_spec.rb +0 -20
  103. data/spec/lib/http/options_spec.rb +0 -13
  104. data/spec/lib/http/redirector_spec.rb +0 -529
  105. data/spec/lib/http/request/body_spec.rb +0 -211
  106. data/spec/lib/http/request/writer_spec.rb +0 -121
  107. data/spec/lib/http/request_spec.rb +0 -234
  108. data/spec/lib/http/response/body_spec.rb +0 -85
  109. data/spec/lib/http/response/parser_spec.rb +0 -74
  110. data/spec/lib/http/response/status_spec.rb +0 -253
  111. data/spec/lib/http/response_spec.rb +0 -262
  112. data/spec/lib/http/uri/normalizer_spec.rb +0 -95
  113. data/spec/lib/http/uri_spec.rb +0 -71
  114. data/spec/lib/http_spec.rb +0 -506
  115. data/spec/regression_specs.rb +0 -24
  116. data/spec/spec_helper.rb +0 -88
  117. data/spec/support/black_hole.rb +0 -13
  118. data/spec/support/capture_warning.rb +0 -10
  119. data/spec/support/dummy_server/servlet.rb +0 -190
  120. data/spec/support/dummy_server.rb +0 -43
  121. data/spec/support/fakeio.rb +0 -21
  122. data/spec/support/fuubar.rb +0 -21
  123. data/spec/support/http_handling_shared.rb +0 -190
  124. data/spec/support/proxy_server.rb +0 -39
  125. data/spec/support/servers/config.rb +0 -11
  126. data/spec/support/servers/runner.rb +0 -19
  127. data/spec/support/simplecov.rb +0 -19
  128. data/spec/support/ssl_helper.rb +0 -104
@@ -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 termintaor.
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 from the headers array
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 the headers to the header array for the given request body we are working
48
- # with
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[0].start_with?("#{method} ")
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 the headers specified in the request into a correctly formatted
60
- # http request header string
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 that should be sent to the socket.
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 the chunk encoded for to the specified "Transfer-Encoding" header.
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,13 +152,23 @@ module HTTP
101
152
  end
102
153
  end
103
154
 
104
- # Returns true if the request should be sent in chunked encoding.
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] == CHUNKED
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]
170
+ # @raise [SocketWriteError] when unable to write to socket
171
+ # @api private
111
172
  def write(data)
112
173
  until data.empty?
113
174
  length = @socket.write(data)
@@ -118,7 +179,7 @@ module HTTP
118
179
  rescue Errno::EPIPE
119
180
  raise
120
181
  rescue IOError, SocketError, SystemCallError => e
121
- raise ConnectionError, "error writing to socket: #{e}", e.backtrace
182
+ raise SocketWriteError, "error writing to socket: #{e}", e.backtrace
122
183
  end
123
184
  end
124
185
  end
data/lib/http/request.rb CHANGED
@@ -1,21 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "forwardable"
4
- require "base64"
5
4
  require "time"
6
5
 
6
+ 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
- include HTTP::Headers::Mixin
20
+ include HTTP::Base64
21
+ include Proxy
19
22
 
20
23
  # The method given was not understood
21
24
  class UnsupportedMethodError < RequestError; end
@@ -24,8 +27,9 @@ module HTTP
24
27
  class UnsupportedSchemeError < RequestError; end
25
28
 
26
29
  # Default User-Agent header value
27
- USER_AGENT = "http.rb/#{HTTP::VERSION}"
30
+ USER_AGENT = "http.rb/#{HTTP::VERSION}".freeze
28
31
 
32
+ # Supported HTTP methods
29
33
  METHODS = [
30
34
  # RFC 2616: Hypertext Transfer Protocol -- HTTP/1.1
31
35
  :options, :get, :head, :post, :put, :delete, :trace, :connect,
@@ -60,115 +64,176 @@ module HTTP
60
64
 
61
65
  # Default ports of supported schemes
62
66
  PORTS = {
63
- :http => 80,
64
- :https => 443,
65
- :ws => 80,
66
- :wss => 443
67
+ http: 80,
68
+ https: 443,
69
+ ws: 80,
70
+ wss: 443
67
71
  }.freeze
68
72
 
69
- # Method is given as a lowercase symbol e.g. :get, :post
73
+ # HTTP method as a lowercase symbol
74
+ #
75
+ # @example
76
+ # request.verb # => :get
77
+ #
78
+ # @return [Symbol]
79
+ # @api public
70
80
  attr_reader :verb
71
81
 
72
- # Scheme is normalized to be a lowercase symbol e.g. :http, :https
82
+ # URI scheme as a lowercase symbol
83
+ #
84
+ # @example
85
+ # request.scheme # => :https
86
+ #
87
+ # @return [Symbol]
88
+ # @api public
73
89
  attr_reader :scheme
74
90
 
91
+ # URI normalizer callable
92
+ #
93
+ # @example
94
+ # request.uri_normalizer
95
+ #
96
+ # @return [#call]
97
+ # @api public
75
98
  attr_reader :uri_normalizer
76
99
 
77
- # "Request URI" as per RFC 2616
78
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html
100
+ # Request URI
101
+ #
102
+ # @example
103
+ # request.uri # => #<HTTP::URI ...>
104
+ #
105
+ # @return [HTTP::URI]
106
+ # @api public
79
107
  attr_reader :uri
80
- attr_reader :proxy, :body, :version
81
-
82
- # @option opts [String] :version
83
- # @option opts [#to_s] :verb HTTP request method
84
- # @option opts [#call] :uri_normalizer (HTTP::URI::NORMALIZER)
85
- # @option opts [HTTP::URI, #to_s] :uri
86
- # @option opts [Hash] :headers
87
- # @option opts [Hash] :proxy
88
- # @option opts [String, Enumerable, IO, nil] :body
89
- def initialize(opts)
90
- @verb = opts.fetch(:verb).to_s.downcase.to_sym
91
- @uri_normalizer = opts[:uri_normalizer] || HTTP::URI::NORMALIZER
92
-
93
- @uri = @uri_normalizer.call(opts.fetch(:uri))
94
- @scheme = @uri.scheme.to_s.downcase.to_sym if @uri.scheme
95
108
 
96
- raise(UnsupportedMethodError, "unknown method: #{verb}") unless METHODS.include?(@verb)
97
- raise(UnsupportedSchemeError, "unknown scheme: #{scheme}") unless SCHEMES.include?(@scheme)
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
98
126
 
99
- @proxy = opts[:proxy] || {}
100
- @version = opts[:version] || "1.1"
101
- @headers = prepare_headers(opts[:headers])
102
- @body = prepare_body(opts[:body])
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)
103
171
  end
104
172
 
105
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
106
180
  def redirect(uri, verb = @verb)
107
- headers = self.headers.dup
108
- headers.delete(Headers::HOST)
109
-
110
- new_body = body.source
111
- if verb == :get
112
- # request bodies should not always be resubmitted when following a redirect
113
- # some servers will close the connection after receiving the request headers
114
- # which may cause Errno::ECONNRESET: Connection reset by peer
115
- # see https://github.com/httprb/http/issues/649
116
- # new_body = Request::Body.new(nil)
117
- new_body = nil
118
- # the CONTENT_TYPE header causes problems if set on a get request w/ an empty body
119
- # the server might assume that there should be content if it is set to multipart
120
- # rack raises EmptyContentError if this happens
121
- headers.delete(Headers::CONTENT_TYPE)
122
- end
181
+ redirect_uri = @uri.join(uri)
182
+ headers = redirect_headers(redirect_uri, verb)
183
+ new_body = verb == :get ? nil : body.source
123
184
 
124
185
  self.class.new(
125
- :verb => verb,
126
- :uri => @uri.join(uri),
127
- :headers => headers,
128
- :proxy => proxy,
129
- :body => new_body,
130
- :version => version,
131
- :uri_normalizer => uri_normalizer
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
132
193
  )
133
194
  end
134
195
 
135
196
  # Stream the request to a socket
197
+ #
198
+ # @example
199
+ # request.stream(socket)
200
+ #
201
+ # @return [void]
202
+ # @api public
136
203
  def stream(socket)
137
204
  include_proxy_headers if using_proxy? && !@uri.https?
138
205
  Request::Writer.new(socket, body, headers, headline).stream
139
206
  end
140
207
 
141
208
  # Is this request using a proxy?
209
+ #
210
+ # @example
211
+ # request.using_proxy?
212
+ #
213
+ # @return [Boolean]
214
+ # @api public
142
215
  def using_proxy?
143
216
  proxy && proxy.keys.size >= 2
144
217
  end
145
218
 
146
219
  # Is this request using an authenticated proxy?
220
+ #
221
+ # @example
222
+ # request.using_authenticated_proxy?
223
+ #
224
+ # @return [Boolean]
225
+ # @api public
147
226
  def using_authenticated_proxy?
148
227
  proxy && proxy.keys.size >= 4
149
228
  end
150
229
 
151
- def include_proxy_headers
152
- headers.merge!(proxy[:proxy_headers]) if proxy.key?(:proxy_headers)
153
- include_proxy_authorization_header if using_authenticated_proxy?
154
- end
155
-
156
- # Compute and add the Proxy-Authorization header
157
- def include_proxy_authorization_header
158
- headers[Headers::PROXY_AUTHORIZATION] = proxy_authorization_header
159
- end
160
-
161
- def proxy_authorization_header
162
- digest = Base64.strict_encode64("#{proxy[:proxy_username]}:#{proxy[:proxy_password]}")
163
- "Basic #{digest}"
164
- end
165
-
166
- # Setup tunnel through proxy for SSL request
167
- def connect_using_proxy(socket)
168
- Request::Writer.new(socket, nil, proxy_connect_headers, proxy_connect_header).connect_through_proxy
169
- end
170
-
171
230
  # Compute HTTP request header for direct or proxy request
231
+ #
232
+ # @example
233
+ # request.headline
234
+ #
235
+ # @return [String]
236
+ # @api public
172
237
  def headline
173
238
  request_uri =
174
239
  if using_proxy? && !uri.https?
@@ -182,34 +247,29 @@ module HTTP
182
247
  "#{verb.to_s.upcase} #{request_uri} HTTP/#{version}"
183
248
  end
184
249
 
185
- # Compute HTTP request header SSL proxy connection
186
- def proxy_connect_header
187
- "CONNECT #{host}:#{port} HTTP/#{version}"
188
- end
189
-
190
- # Headers to send with proxy connect request
191
- def proxy_connect_headers
192
- connect_headers = HTTP::Headers.coerce(
193
- Headers::HOST => headers[Headers::HOST],
194
- Headers::USER_AGENT => headers[Headers::USER_AGENT]
195
- )
196
-
197
- connect_headers[Headers::PROXY_AUTHORIZATION] = proxy_authorization_header if using_authenticated_proxy?
198
- connect_headers.merge!(proxy[:proxy_headers]) if proxy.key?(:proxy_headers)
199
- connect_headers
200
- end
201
-
202
250
  # Host for tcp socket
251
+ #
252
+ # @example
253
+ # request.socket_host
254
+ #
255
+ # @return [String]
256
+ # @api public
203
257
  def socket_host
204
258
  using_proxy? ? proxy[:proxy_address] : host
205
259
  end
206
260
 
207
261
  # Port for tcp socket
262
+ #
263
+ # @example
264
+ # request.socket_port
265
+ #
266
+ # @return [Integer]
267
+ # @api public
208
268
  def socket_port
209
269
  using_proxy? ? proxy[:proxy_port] : port
210
270
  end
211
271
 
212
- # Human-readable representation of base request info.
272
+ # Human-readable representation of base request info
213
273
  #
214
274
  # @example
215
275
  #
@@ -217,23 +277,54 @@ module HTTP
217
277
  # # => #<HTTP::Request/1.1 GET https://example.com>
218
278
  #
219
279
  # @return [String]
280
+ # @api public
220
281
  def inspect
221
- "#<#{self.class}/#{@version} #{verb.to_s.upcase} #{uri}>"
282
+ format("#<%s/%s %s %s>", self.class, @version, String(verb).upcase, uri)
222
283
  end
223
284
 
224
285
  private
225
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
+
226
312
  # @!attribute [r] host
313
+ # Host from the URI
227
314
  # @return [String]
315
+ # @api private
228
316
  def_delegator :@uri, :host
229
317
 
230
- # @!attribute [r] port
231
- # @return [Fixnum]
318
+ # Return the port for the request URI
319
+ # @return [Fixnum]
320
+ # @api private
232
321
  def port
233
322
  @uri.port || @uri.default_port
234
323
  end
235
324
 
236
- # @return [String] Default host (with port if needed) header value.
325
+ # Build default Host header value
326
+ # @return [String]
327
+ # @api private
237
328
  def default_host_header_value
238
329
  value = PORTS[@scheme] == port ? host : "#{host}:#{port}"
239
330
 
@@ -242,12 +333,38 @@ module HTTP
242
333
  value
243
334
  end
244
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
245
359
  def prepare_body(body)
246
- body.is_a?(Request::Body) ? body : Request::Body.new(body)
360
+ body.is_a?(Body) ? body : Body.new(body)
247
361
  end
248
362
 
363
+ # Build headers with default values
364
+ # @return [HTTP::Headers]
365
+ # @api private
249
366
  def prepare_headers(headers)
250
- headers = HTTP::Headers.coerce(headers || {})
367
+ headers = Headers.coerce(headers || {})
251
368
 
252
369
  headers[Headers::HOST] ||= default_host_header_value
253
370
  headers[Headers::USER_AGENT] ||= USER_AGENT