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.
Files changed (201) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +241 -41
  3. data/LICENSE.txt +1 -1
  4. data/README.md +110 -13
  5. data/UPGRADING.md +491 -0
  6. data/http.gemspec +32 -29
  7. data/lib/http/base64.rb +11 -1
  8. data/lib/http/chainable/helpers.rb +62 -0
  9. data/lib/http/chainable/verbs.rb +136 -0
  10. data/lib/http/chainable.rb +232 -136
  11. data/lib/http/client.rb +158 -127
  12. data/lib/http/connection/internals.rb +141 -0
  13. data/lib/http/connection.rb +126 -97
  14. data/lib/http/content_type.rb +61 -6
  15. data/lib/http/errors.rb +25 -1
  16. data/lib/http/feature.rb +65 -5
  17. data/lib/http/features/auto_deflate.rb +124 -17
  18. data/lib/http/features/auto_inflate.rb +38 -15
  19. data/lib/http/features/caching/entry.rb +178 -0
  20. data/lib/http/features/caching/in_memory_store.rb +63 -0
  21. data/lib/http/features/caching.rb +216 -0
  22. data/lib/http/features/digest_auth.rb +234 -0
  23. data/lib/http/features/instrumentation.rb +97 -17
  24. data/lib/http/features/logging.rb +183 -5
  25. data/lib/http/features/normalize_uri.rb +17 -0
  26. data/lib/http/features/raise_error.rb +18 -3
  27. data/lib/http/form_data/composite_io.rb +106 -0
  28. data/lib/http/form_data/file.rb +95 -0
  29. data/lib/http/form_data/multipart/param.rb +62 -0
  30. data/lib/http/form_data/multipart.rb +106 -0
  31. data/lib/http/form_data/part.rb +52 -0
  32. data/lib/http/form_data/readable.rb +58 -0
  33. data/lib/http/form_data/urlencoded.rb +175 -0
  34. data/lib/http/form_data/version.rb +8 -0
  35. data/lib/http/form_data.rb +102 -0
  36. data/lib/http/headers/known.rb +3 -0
  37. data/lib/http/headers/normalizer.rb +17 -36
  38. data/lib/http/headers.rb +172 -65
  39. data/lib/http/mime_type/adapter.rb +24 -9
  40. data/lib/http/mime_type/json.rb +19 -4
  41. data/lib/http/mime_type.rb +21 -3
  42. data/lib/http/options/definitions.rb +189 -0
  43. data/lib/http/options.rb +172 -125
  44. data/lib/http/redirector.rb +80 -75
  45. data/lib/http/request/body.rb +87 -6
  46. data/lib/http/request/builder.rb +184 -0
  47. data/lib/http/request/proxy.rb +83 -0
  48. data/lib/http/request/writer.rb +76 -16
  49. data/lib/http/request.rb +214 -98
  50. data/lib/http/response/body.rb +103 -18
  51. data/lib/http/response/inflater.rb +35 -7
  52. data/lib/http/response/parser.rb +98 -4
  53. data/lib/http/response/status/reasons.rb +2 -4
  54. data/lib/http/response/status.rb +141 -31
  55. data/lib/http/response.rb +219 -61
  56. data/lib/http/retriable/delay_calculator.rb +38 -11
  57. data/lib/http/retriable/errors.rb +21 -0
  58. data/lib/http/retriable/performer.rb +82 -38
  59. data/lib/http/session.rb +280 -0
  60. data/lib/http/timeout/global.rb +147 -34
  61. data/lib/http/timeout/null.rb +155 -9
  62. data/lib/http/timeout/per_operation.rb +139 -18
  63. data/lib/http/uri/normalizer.rb +82 -0
  64. data/lib/http/uri/parsing.rb +182 -0
  65. data/lib/http/uri.rb +289 -124
  66. data/lib/http/version.rb +2 -1
  67. data/lib/http.rb +11 -2
  68. data/sig/deps.rbs +122 -0
  69. data/sig/http.rbs +1619 -0
  70. data/test/http/base64_test.rb +28 -0
  71. data/test/http/client_test.rb +739 -0
  72. data/test/http/connection_test.rb +1533 -0
  73. data/test/http/content_type_test.rb +190 -0
  74. data/test/http/errors_test.rb +28 -0
  75. data/test/http/feature_test.rb +49 -0
  76. data/test/http/features/auto_deflate_test.rb +317 -0
  77. data/test/http/features/auto_inflate_test.rb +213 -0
  78. data/test/http/features/caching_test.rb +942 -0
  79. data/test/http/features/digest_auth_test.rb +996 -0
  80. data/test/http/features/instrumentation_test.rb +246 -0
  81. data/test/http/features/logging_test.rb +654 -0
  82. data/test/http/features/normalize_uri_test.rb +41 -0
  83. data/test/http/features/raise_error_test.rb +77 -0
  84. data/test/http/form_data/composite_io_test.rb +215 -0
  85. data/test/http/form_data/file_test.rb +255 -0
  86. data/test/http/form_data/fixtures/the-http-gem.info +1 -0
  87. data/test/http/form_data/multipart_test.rb +303 -0
  88. data/test/http/form_data/part_test.rb +90 -0
  89. data/test/http/form_data/urlencoded_test.rb +164 -0
  90. data/test/http/form_data_test.rb +232 -0
  91. data/test/http/headers/normalizer_test.rb +93 -0
  92. data/test/http/headers_test.rb +888 -0
  93. data/test/http/mime_type/json_test.rb +39 -0
  94. data/test/http/mime_type_test.rb +150 -0
  95. data/test/http/options/base_uri_test.rb +148 -0
  96. data/test/http/options/body_test.rb +21 -0
  97. data/test/http/options/features_test.rb +38 -0
  98. data/test/http/options/form_test.rb +21 -0
  99. data/test/http/options/headers_test.rb +32 -0
  100. data/test/http/options/json_test.rb +21 -0
  101. data/test/http/options/merge_test.rb +78 -0
  102. data/test/http/options/new_test.rb +37 -0
  103. data/test/http/options/proxy_test.rb +32 -0
  104. data/test/http/options_test.rb +575 -0
  105. data/test/http/redirector_test.rb +639 -0
  106. data/test/http/request/body_test.rb +318 -0
  107. data/test/http/request/builder_test.rb +623 -0
  108. data/test/http/request/writer_test.rb +391 -0
  109. data/test/http/request_test.rb +1733 -0
  110. data/test/http/response/body_test.rb +292 -0
  111. data/test/http/response/parser_test.rb +105 -0
  112. data/test/http/response/status_test.rb +322 -0
  113. data/test/http/response_test.rb +502 -0
  114. data/test/http/retriable/delay_calculator_test.rb +194 -0
  115. data/test/http/retriable/errors_test.rb +71 -0
  116. data/test/http/retriable/performer_test.rb +551 -0
  117. data/test/http/session_test.rb +424 -0
  118. data/test/http/timeout/global_test.rb +239 -0
  119. data/test/http/timeout/null_test.rb +218 -0
  120. data/test/http/timeout/per_operation_test.rb +220 -0
  121. data/test/http/uri/normalizer_test.rb +89 -0
  122. data/test/http/uri_test.rb +1140 -0
  123. data/test/http/version_test.rb +15 -0
  124. data/test/http_test.rb +818 -0
  125. data/test/regression_tests.rb +27 -0
  126. data/test/support/dummy_server/encoding_routes.rb +47 -0
  127. data/test/support/dummy_server/routes.rb +201 -0
  128. data/test/support/dummy_server/servlet.rb +81 -0
  129. data/test/support/dummy_server.rb +200 -0
  130. data/{spec → test}/support/fakeio.rb +2 -2
  131. data/test/support/http_handling_shared/connection_reuse_tests.rb +97 -0
  132. data/test/support/http_handling_shared/timeout_tests.rb +134 -0
  133. data/test/support/http_handling_shared.rb +11 -0
  134. data/test/support/proxy_server.rb +207 -0
  135. data/test/support/servers/runner.rb +67 -0
  136. data/{spec → test}/support/simplecov.rb +11 -2
  137. data/test/support/ssl_helper.rb +108 -0
  138. data/test/test_helper.rb +38 -0
  139. metadata +108 -168
  140. data/.github/workflows/ci.yml +0 -67
  141. data/.gitignore +0 -15
  142. data/.rspec +0 -1
  143. data/.rubocop/layout.yml +0 -8
  144. data/.rubocop/metrics.yml +0 -4
  145. data/.rubocop/rspec.yml +0 -9
  146. data/.rubocop/style.yml +0 -32
  147. data/.rubocop.yml +0 -11
  148. data/.rubocop_todo.yml +0 -219
  149. data/.yardopts +0 -2
  150. data/CHANGES_OLD.md +0 -1002
  151. data/Gemfile +0 -51
  152. data/Guardfile +0 -18
  153. data/Rakefile +0 -64
  154. data/lib/http/headers/mixin.rb +0 -34
  155. data/lib/http/retriable/client.rb +0 -37
  156. data/logo.png +0 -0
  157. data/spec/lib/http/client_spec.rb +0 -556
  158. data/spec/lib/http/connection_spec.rb +0 -88
  159. data/spec/lib/http/content_type_spec.rb +0 -47
  160. data/spec/lib/http/features/auto_deflate_spec.rb +0 -77
  161. data/spec/lib/http/features/auto_inflate_spec.rb +0 -86
  162. data/spec/lib/http/features/instrumentation_spec.rb +0 -81
  163. data/spec/lib/http/features/logging_spec.rb +0 -65
  164. data/spec/lib/http/features/raise_error_spec.rb +0 -62
  165. data/spec/lib/http/headers/mixin_spec.rb +0 -36
  166. data/spec/lib/http/headers/normalizer_spec.rb +0 -52
  167. data/spec/lib/http/headers_spec.rb +0 -527
  168. data/spec/lib/http/options/body_spec.rb +0 -15
  169. data/spec/lib/http/options/features_spec.rb +0 -33
  170. data/spec/lib/http/options/form_spec.rb +0 -15
  171. data/spec/lib/http/options/headers_spec.rb +0 -24
  172. data/spec/lib/http/options/json_spec.rb +0 -15
  173. data/spec/lib/http/options/merge_spec.rb +0 -68
  174. data/spec/lib/http/options/new_spec.rb +0 -30
  175. data/spec/lib/http/options/proxy_spec.rb +0 -20
  176. data/spec/lib/http/options_spec.rb +0 -13
  177. data/spec/lib/http/redirector_spec.rb +0 -530
  178. data/spec/lib/http/request/body_spec.rb +0 -211
  179. data/spec/lib/http/request/writer_spec.rb +0 -121
  180. data/spec/lib/http/request_spec.rb +0 -234
  181. data/spec/lib/http/response/body_spec.rb +0 -85
  182. data/spec/lib/http/response/parser_spec.rb +0 -74
  183. data/spec/lib/http/response/status_spec.rb +0 -253
  184. data/spec/lib/http/response_spec.rb +0 -262
  185. data/spec/lib/http/retriable/delay_calculator_spec.rb +0 -69
  186. data/spec/lib/http/retriable/performer_spec.rb +0 -302
  187. data/spec/lib/http/uri/normalizer_spec.rb +0 -95
  188. data/spec/lib/http/uri_spec.rb +0 -71
  189. data/spec/lib/http_spec.rb +0 -535
  190. data/spec/regression_specs.rb +0 -24
  191. data/spec/spec_helper.rb +0 -89
  192. data/spec/support/black_hole.rb +0 -13
  193. data/spec/support/dummy_server/servlet.rb +0 -203
  194. data/spec/support/dummy_server.rb +0 -44
  195. data/spec/support/fuubar.rb +0 -21
  196. data/spec/support/http_handling_shared.rb +0 -190
  197. data/spec/support/proxy_server.rb +0 -39
  198. data/spec/support/servers/config.rb +0 -11
  199. data/spec/support/servers/runner.rb +0 -19
  200. data/spec/support/ssl_helper.rb +0 -104
  201. /data/{spec → test}/support/capture_warning.rb +0 -0
@@ -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,14 +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]
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 HTTP::Headers::Mixin
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
- :http => 80,
65
- :https => 443,
66
- :ws => 80,
67
- :wss => 443
67
+ http: 80,
68
+ https: 443,
69
+ ws: 80,
70
+ wss: 443
68
71
  }.freeze
69
72
 
70
- # 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
71
80
  attr_reader :verb
72
81
 
73
- # 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
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
- # "Request URI" as per RFC 2616
79
- # 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
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
- raise(UnsupportedMethodError, "unknown method: #{verb}") unless METHODS.include?(@verb)
98
- 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
99
126
 
100
- @proxy = opts[:proxy] || {}
101
- @version = opts[:version] || "1.1"
102
- @headers = prepare_headers(opts[:headers])
103
- @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)
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
- headers = self.headers.dup
109
- headers.delete(Headers::HOST)
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
- :verb => verb,
127
- :uri => @uri.join(uri),
128
- :headers => headers,
129
- :proxy => proxy,
130
- :body => new_body,
131
- :version => version,
132
- :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
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
- "#<#{self.class}/#{@version} #{verb.to_s.upcase} #{uri}>"
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
- # @!attribute [r] port
232
- # @return [Fixnum]
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
- # @return [String] Default host (with port if needed) header value.
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?(Request::Body) ? body : Request::Body.new(body)
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 = HTTP::Headers.coerce(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