http 6.0.0-java

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 (142) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +267 -0
  3. data/CONTRIBUTING.md +26 -0
  4. data/LICENSE.txt +20 -0
  5. data/README.md +263 -0
  6. data/SECURITY.md +17 -0
  7. data/UPGRADING.md +491 -0
  8. data/http.gemspec +48 -0
  9. data/lib/http/base64.rb +22 -0
  10. data/lib/http/chainable/helpers.rb +62 -0
  11. data/lib/http/chainable/verbs.rb +136 -0
  12. data/lib/http/chainable.rb +377 -0
  13. data/lib/http/client.rb +230 -0
  14. data/lib/http/connection/internals.rb +141 -0
  15. data/lib/http/connection.rb +265 -0
  16. data/lib/http/content_type.rb +89 -0
  17. data/lib/http/errors.rb +67 -0
  18. data/lib/http/feature.rb +86 -0
  19. data/lib/http/features/auto_deflate.rb +230 -0
  20. data/lib/http/features/auto_inflate.rb +64 -0
  21. data/lib/http/features/caching/entry.rb +178 -0
  22. data/lib/http/features/caching/in_memory_store.rb +63 -0
  23. data/lib/http/features/caching.rb +216 -0
  24. data/lib/http/features/digest_auth.rb +234 -0
  25. data/lib/http/features/instrumentation.rb +149 -0
  26. data/lib/http/features/logging.rb +231 -0
  27. data/lib/http/features/normalize_uri.rb +34 -0
  28. data/lib/http/features/raise_error.rb +37 -0
  29. data/lib/http/form_data/composite_io.rb +106 -0
  30. data/lib/http/form_data/file.rb +95 -0
  31. data/lib/http/form_data/multipart/param.rb +62 -0
  32. data/lib/http/form_data/multipart.rb +106 -0
  33. data/lib/http/form_data/part.rb +52 -0
  34. data/lib/http/form_data/readable.rb +58 -0
  35. data/lib/http/form_data/urlencoded.rb +175 -0
  36. data/lib/http/form_data/version.rb +8 -0
  37. data/lib/http/form_data.rb +102 -0
  38. data/lib/http/headers/known.rb +90 -0
  39. data/lib/http/headers/normalizer.rb +50 -0
  40. data/lib/http/headers.rb +343 -0
  41. data/lib/http/mime_type/adapter.rb +43 -0
  42. data/lib/http/mime_type/json.rb +41 -0
  43. data/lib/http/mime_type.rb +96 -0
  44. data/lib/http/options/definitions.rb +189 -0
  45. data/lib/http/options.rb +241 -0
  46. data/lib/http/redirector.rb +157 -0
  47. data/lib/http/request/body.rb +181 -0
  48. data/lib/http/request/builder.rb +184 -0
  49. data/lib/http/request/proxy.rb +83 -0
  50. data/lib/http/request/writer.rb +186 -0
  51. data/lib/http/request.rb +375 -0
  52. data/lib/http/response/body.rb +172 -0
  53. data/lib/http/response/inflater.rb +60 -0
  54. data/lib/http/response/parser.rb +223 -0
  55. data/lib/http/response/status/reasons.rb +79 -0
  56. data/lib/http/response/status.rb +263 -0
  57. data/lib/http/response.rb +350 -0
  58. data/lib/http/retriable/delay_calculator.rb +91 -0
  59. data/lib/http/retriable/errors.rb +35 -0
  60. data/lib/http/retriable/performer.rb +197 -0
  61. data/lib/http/session.rb +280 -0
  62. data/lib/http/timeout/global.rb +229 -0
  63. data/lib/http/timeout/null.rb +225 -0
  64. data/lib/http/timeout/per_operation.rb +197 -0
  65. data/lib/http/uri/normalizer.rb +82 -0
  66. data/lib/http/uri/parsing.rb +182 -0
  67. data/lib/http/uri.rb +376 -0
  68. data/lib/http/version.rb +6 -0
  69. data/lib/http.rb +36 -0
  70. data/sig/deps.rbs +122 -0
  71. data/sig/http.rbs +1619 -0
  72. data/test/http/base64_test.rb +28 -0
  73. data/test/http/client_test.rb +739 -0
  74. data/test/http/connection_test.rb +1533 -0
  75. data/test/http/content_type_test.rb +190 -0
  76. data/test/http/errors_test.rb +28 -0
  77. data/test/http/feature_test.rb +49 -0
  78. data/test/http/features/auto_deflate_test.rb +317 -0
  79. data/test/http/features/auto_inflate_test.rb +213 -0
  80. data/test/http/features/caching_test.rb +942 -0
  81. data/test/http/features/digest_auth_test.rb +996 -0
  82. data/test/http/features/instrumentation_test.rb +246 -0
  83. data/test/http/features/logging_test.rb +654 -0
  84. data/test/http/features/normalize_uri_test.rb +41 -0
  85. data/test/http/features/raise_error_test.rb +77 -0
  86. data/test/http/form_data/composite_io_test.rb +215 -0
  87. data/test/http/form_data/file_test.rb +255 -0
  88. data/test/http/form_data/fixtures/the-http-gem.info +1 -0
  89. data/test/http/form_data/multipart_test.rb +303 -0
  90. data/test/http/form_data/part_test.rb +90 -0
  91. data/test/http/form_data/urlencoded_test.rb +164 -0
  92. data/test/http/form_data_test.rb +232 -0
  93. data/test/http/headers/normalizer_test.rb +93 -0
  94. data/test/http/headers_test.rb +888 -0
  95. data/test/http/mime_type/json_test.rb +39 -0
  96. data/test/http/mime_type_test.rb +150 -0
  97. data/test/http/options/base_uri_test.rb +148 -0
  98. data/test/http/options/body_test.rb +21 -0
  99. data/test/http/options/features_test.rb +38 -0
  100. data/test/http/options/form_test.rb +21 -0
  101. data/test/http/options/headers_test.rb +32 -0
  102. data/test/http/options/json_test.rb +21 -0
  103. data/test/http/options/merge_test.rb +78 -0
  104. data/test/http/options/new_test.rb +37 -0
  105. data/test/http/options/proxy_test.rb +32 -0
  106. data/test/http/options_test.rb +575 -0
  107. data/test/http/redirector_test.rb +639 -0
  108. data/test/http/request/body_test.rb +318 -0
  109. data/test/http/request/builder_test.rb +623 -0
  110. data/test/http/request/writer_test.rb +391 -0
  111. data/test/http/request_test.rb +1733 -0
  112. data/test/http/response/body_test.rb +292 -0
  113. data/test/http/response/parser_test.rb +105 -0
  114. data/test/http/response/status_test.rb +322 -0
  115. data/test/http/response_test.rb +502 -0
  116. data/test/http/retriable/delay_calculator_test.rb +194 -0
  117. data/test/http/retriable/errors_test.rb +71 -0
  118. data/test/http/retriable/performer_test.rb +551 -0
  119. data/test/http/session_test.rb +424 -0
  120. data/test/http/timeout/global_test.rb +239 -0
  121. data/test/http/timeout/null_test.rb +218 -0
  122. data/test/http/timeout/per_operation_test.rb +220 -0
  123. data/test/http/uri/normalizer_test.rb +89 -0
  124. data/test/http/uri_test.rb +1140 -0
  125. data/test/http/version_test.rb +15 -0
  126. data/test/http_test.rb +818 -0
  127. data/test/regression_tests.rb +27 -0
  128. data/test/support/capture_warning.rb +10 -0
  129. data/test/support/dummy_server/encoding_routes.rb +47 -0
  130. data/test/support/dummy_server/routes.rb +201 -0
  131. data/test/support/dummy_server/servlet.rb +81 -0
  132. data/test/support/dummy_server.rb +200 -0
  133. data/test/support/fakeio.rb +21 -0
  134. data/test/support/http_handling_shared/connection_reuse_tests.rb +97 -0
  135. data/test/support/http_handling_shared/timeout_tests.rb +134 -0
  136. data/test/support/http_handling_shared.rb +11 -0
  137. data/test/support/proxy_server.rb +207 -0
  138. data/test/support/servers/runner.rb +67 -0
  139. data/test/support/simplecov.rb +28 -0
  140. data/test/support/ssl_helper.rb +108 -0
  141. data/test/test_helper.rb +38 -0
  142. metadata +218 -0
@@ -0,0 +1,184 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+
5
+ require "http/form_data"
6
+ require "http/headers"
7
+ require "http/connection"
8
+ require "http/uri"
9
+
10
+ module HTTP
11
+ class Request
12
+ # Builds HTTP::Request objects from resolved options
13
+ #
14
+ # @example Build a request from options
15
+ # options = HTTP::Options.new(headers: {"Accept" => "application/json"})
16
+ # builder = HTTP::Request::Builder.new(options)
17
+ # request = builder.build(:get, "https://example.com")
18
+ #
19
+ # @see Options
20
+ class Builder
21
+ # Pattern matching HTTP or HTTPS URI schemes
22
+ HTTP_OR_HTTPS_RE = %r{\Ahttps?://}i
23
+
24
+ # Initialize a new Request Builder
25
+ #
26
+ # @example
27
+ # HTTP::Request::Builder.new(HTTP::Options.new)
28
+ #
29
+ # @param options [HTTP::Options] resolved request options
30
+ # @return [HTTP::Request::Builder]
31
+ # @api public
32
+ def initialize(options)
33
+ @options = options
34
+ end
35
+
36
+ # Build an HTTP request
37
+ #
38
+ # @example
39
+ # builder.build(:get, "https://example.com")
40
+ #
41
+ # @param verb [Symbol] the HTTP method
42
+ # @param uri [#to_s] the URI to request
43
+ # @return [HTTP::Request] the built request object
44
+ # @api public
45
+ def build(verb, uri)
46
+ uri = make_request_uri(uri)
47
+ headers = make_request_headers
48
+ body = make_request_body(headers)
49
+
50
+ req = HTTP::Request.new(
51
+ verb: verb,
52
+ uri: uri,
53
+ uri_normalizer: @options.feature(:normalize_uri)&.normalizer,
54
+ proxy: @options.proxy,
55
+ headers: headers,
56
+ body: body
57
+ )
58
+
59
+ wrap(req)
60
+ end
61
+
62
+ # Wrap a request through feature middleware
63
+ #
64
+ # @example
65
+ # builder.wrap(redirect_request)
66
+ #
67
+ # @param request [HTTP::Request] the request to wrap
68
+ # @return [HTTP::Request] the wrapped request
69
+ # @api public
70
+ def wrap(request)
71
+ @options.features.inject(request) do |req, (_name, feature)|
72
+ feature.wrap_request(req)
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ # Merges query params if needed
79
+ #
80
+ # @param uri [#to_s] the URI to process
81
+ # @return [HTTP::URI] the constructed URI
82
+ # @api private
83
+ def make_request_uri(uri)
84
+ uri = uri.to_s
85
+
86
+ if @options.base_uri? && uri !~ HTTP_OR_HTTPS_RE
87
+ uri = resolve_against_base(uri)
88
+ elsif @options.persistent? && uri !~ HTTP_OR_HTTPS_RE
89
+ uri = "#{@options.persistent}#{uri}"
90
+ end
91
+
92
+ uri = HTTP::URI.parse uri
93
+
94
+ merge_query_params!(uri)
95
+
96
+ # Some proxies (seen on WEBrick) fail if URL has
97
+ # empty path (e.g. `http://example.com`) while it's RFC-compliant:
98
+ # http://tools.ietf.org/html/rfc1738#section-3.1
99
+ uri.path = "/" if uri.path.empty?
100
+
101
+ uri
102
+ end
103
+
104
+ # Resolve a relative URI against the configured base URI
105
+ #
106
+ # Ensures the base URI path has a trailing slash so that relative
107
+ # paths are appended rather than replacing the last path segment,
108
+ # per the convention described in RFC 3986 Section 5.
109
+ #
110
+ # @param uri [String] the relative URI to resolve
111
+ # @return [String] the resolved absolute URI
112
+ # @api private
113
+ def resolve_against_base(uri)
114
+ base = @options.base_uri or raise Error, "base_uri is not set"
115
+
116
+ unless base.path.end_with?("/")
117
+ base = base.dup
118
+ base.path = "#{base.path}/"
119
+ end
120
+
121
+ String(base.join(uri))
122
+ end
123
+
124
+ # Merge query parameters into URI
125
+ #
126
+ # @return [void]
127
+ # @api private
128
+ def merge_query_params!(uri)
129
+ return unless @options.params && !@options.params.empty?
130
+
131
+ existing = ::URI.decode_www_form(uri.query || "")
132
+ uri.query = ::URI.encode_www_form(existing.concat(@options.params.to_a))
133
+ end
134
+
135
+ # Creates request headers
136
+ #
137
+ # @return [HTTP::Headers] the constructed headers
138
+ # @api private
139
+ def make_request_headers
140
+ headers = @options.headers
141
+
142
+ # Tell the server to keep the conn open
143
+ headers[Headers::CONNECTION] = @options.persistent? ? Connection::KEEP_ALIVE : Connection::CLOSE
144
+
145
+ headers
146
+ end
147
+
148
+ # Create the request body object to send
149
+ #
150
+ # @return [String, HTTP::FormData, nil] the request body
151
+ # @api private
152
+ def make_request_body(headers)
153
+ if @options.body
154
+ @options.body
155
+ elsif @options.form
156
+ form = make_form_data(@options.form)
157
+ headers[Headers::CONTENT_TYPE] ||= form.content_type
158
+ form
159
+ elsif @options.json
160
+ make_json_body(@options.json, headers)
161
+ end
162
+ end
163
+
164
+ # Encode JSON body and set content type header
165
+ # @return [String] the encoded JSON body
166
+ # @api private
167
+ def make_json_body(data, headers)
168
+ body = MimeType[:json].encode data
169
+ headers[Headers::CONTENT_TYPE] ||= "application/json; charset=#{body.encoding.name.downcase}"
170
+ body
171
+ end
172
+
173
+ # Coerce form data into an HTTP::FormData object
174
+ # @return [HTTP::FormData::Multipart, HTTP::FormData::Urlencoded] form data
175
+ # @api private
176
+ def make_form_data(form)
177
+ return form if form.is_a? FormData::Multipart
178
+ return form if form.is_a? FormData::Urlencoded
179
+
180
+ FormData.create(form)
181
+ end
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTP
4
+ class Request
5
+ # Proxy-related methods for HTTP requests
6
+ module Proxy
7
+ # Merges proxy headers into the request headers
8
+ #
9
+ # @example
10
+ # request.include_proxy_headers
11
+ #
12
+ # @return [void]
13
+ # @api public
14
+ def include_proxy_headers
15
+ headers.merge!(proxy[:proxy_headers]) if proxy.key?(:proxy_headers)
16
+ include_proxy_authorization_header if using_authenticated_proxy?
17
+ end
18
+
19
+ # Compute and add the Proxy-Authorization header
20
+ #
21
+ # @example
22
+ # request.include_proxy_authorization_header
23
+ #
24
+ # @return [void]
25
+ # @api public
26
+ def include_proxy_authorization_header
27
+ headers[Headers::PROXY_AUTHORIZATION] = proxy_authorization_header
28
+ end
29
+
30
+ # Build the Proxy-Authorization header value
31
+ #
32
+ # @example
33
+ # request.proxy_authorization_header
34
+ #
35
+ # @return [String]
36
+ # @api public
37
+ def proxy_authorization_header
38
+ digest = encode64(format("%s:%s", proxy.fetch(:proxy_username), proxy.fetch(:proxy_password)))
39
+ "Basic #{digest}"
40
+ end
41
+
42
+ # Setup tunnel through proxy for SSL request
43
+ #
44
+ # @example
45
+ # request.connect_using_proxy(socket)
46
+ #
47
+ # @return [void]
48
+ # @api public
49
+ def connect_using_proxy(socket)
50
+ Writer.new(socket, nil, proxy_connect_headers, proxy_connect_header).connect_through_proxy
51
+ end
52
+
53
+ # Compute HTTP request header SSL proxy connection
54
+ #
55
+ # @example
56
+ # request.proxy_connect_header
57
+ #
58
+ # @return [String]
59
+ # @api public
60
+ def proxy_connect_header
61
+ "CONNECT #{host}:#{port} HTTP/#{version}"
62
+ end
63
+
64
+ # Headers to send with proxy connect request
65
+ #
66
+ # @example
67
+ # request.proxy_connect_headers
68
+ #
69
+ # @return [HTTP::Headers]
70
+ # @api public
71
+ def proxy_connect_headers
72
+ connect_headers = Headers.coerce(
73
+ Headers::HOST => headers[Headers::HOST],
74
+ Headers::USER_AGENT => headers[Headers::USER_AGENT]
75
+ )
76
+
77
+ connect_headers[Headers::PROXY_AUTHORIZATION] = proxy_authorization_header if using_authenticated_proxy?
78
+ connect_headers.merge!(proxy[:proxy_headers]) if proxy.key?(:proxy_headers)
79
+ connect_headers
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,186 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "http/headers"
4
+
5
+ module HTTP
6
+ class Request
7
+ # Streams HTTP requests to a socket
8
+ class Writer
9
+ # CRLF is the universal HTTP delimiter
10
+ CRLF = "\r\n"
11
+
12
+ # Chunked data terminator
13
+ ZERO = "0"
14
+
15
+ # End of a chunked transfer
16
+ CHUNKED_END = "#{ZERO}#{CRLF}#{CRLF}".freeze
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
25
+ def initialize(socket, body, headers, headline)
26
+ @body = body
27
+ @socket = socket
28
+ @headers = headers
29
+ @request_header = [headline]
30
+ end
31
+
32
+ # Adds headers to the request header array
33
+ #
34
+ # @example
35
+ # writer.add_headers
36
+ #
37
+ # @return [void]
38
+ # @api public
39
+ def add_headers
40
+ @headers.each do |field, value|
41
+ @request_header << "#{field}: #{value}"
42
+ end
43
+ end
44
+
45
+ # Stream the request to a socket
46
+ #
47
+ # @example
48
+ # writer.stream
49
+ #
50
+ # @return [void]
51
+ # @api public
52
+ def stream
53
+ add_headers
54
+ add_body_type_headers
55
+ send_request
56
+ end
57
+
58
+ # Send headers needed to connect through proxy
59
+ #
60
+ # @example
61
+ # writer.connect_through_proxy
62
+ #
63
+ # @return [void]
64
+ # @api public
65
+ def connect_through_proxy
66
+ add_headers
67
+ write(join_headers)
68
+ end
69
+
70
+ # Adds content length or transfer encoding headers
71
+ #
72
+ # @example
73
+ # writer.add_body_type_headers
74
+ #
75
+ # @return [void]
76
+ # @api public
77
+ def add_body_type_headers
78
+ return if @headers[Headers::CONTENT_LENGTH] || chunked? || (
79
+ @body.source.nil? && %w[GET HEAD DELETE CONNECT].any? do |method|
80
+ @request_header.fetch(0).start_with?("#{method} ")
81
+ end
82
+ )
83
+
84
+ @request_header << "#{Headers::CONTENT_LENGTH}: #{@body.size}"
85
+ end
86
+
87
+ # Joins headers into an HTTP request header string
88
+ #
89
+ # @example
90
+ # writer.join_headers
91
+ #
92
+ # @return [String]
93
+ # @api public
94
+ def join_headers
95
+ # join the headers array with crlfs, stick two on the end because
96
+ # that ends the request header
97
+ @request_header.join(CRLF) + (CRLF * 2)
98
+ end
99
+
100
+ # Writes HTTP request data into the socket
101
+ #
102
+ # @example
103
+ # writer.send_request
104
+ #
105
+ # @return [void]
106
+ # @api public
107
+ def send_request
108
+ each_chunk { |chunk| write chunk }
109
+ rescue Errno::EPIPE
110
+ # server doesn't need any more data
111
+ nil
112
+ end
113
+
114
+ # Yields chunks of request data for the socket
115
+ #
116
+ # It's important to send the request in a single write call when possible
117
+ # in order to play nicely with Nagle's algorithm. Making two writes in a
118
+ # row triggers a pathological case where Nagle is expecting a third write
119
+ # that never happens.
120
+ #
121
+ # @example
122
+ # writer.each_chunk { |chunk| socket.write(chunk) }
123
+ #
124
+ # @return [void]
125
+ # @api public
126
+ def each_chunk
127
+ data = join_headers
128
+
129
+ @body.each do |chunk|
130
+ data << encode_chunk(chunk)
131
+ yield data
132
+ data.clear
133
+ end
134
+
135
+ yield data unless data.empty?
136
+
137
+ yield CHUNKED_END if chunked?
138
+ end
139
+
140
+ # Returns chunk encoded per Transfer-Encoding header
141
+ #
142
+ # @example
143
+ # writer.encode_chunk("hello")
144
+ #
145
+ # @return [String]
146
+ # @api public
147
+ def encode_chunk(chunk)
148
+ if chunked?
149
+ chunk.bytesize.to_s(16) << CRLF << chunk << CRLF
150
+ else
151
+ chunk
152
+ end
153
+ end
154
+
155
+ # Returns true if using chunked transfer encoding
156
+ #
157
+ # @example
158
+ # writer.chunked?
159
+ #
160
+ # @return [Boolean]
161
+ # @api public
162
+ def chunked?
163
+ @headers[Headers::TRANSFER_ENCODING].eql?(Headers::CHUNKED)
164
+ end
165
+
166
+ private
167
+
168
+ # Write data to the underlying socket
169
+ # @return [void]
170
+ # @raise [SocketWriteError] when unable to write to socket
171
+ # @api private
172
+ def write(data)
173
+ until data.empty?
174
+ length = @socket.write(data)
175
+ break unless data.bytesize > length
176
+
177
+ data = data.byteslice(length..-1)
178
+ end
179
+ rescue Errno::EPIPE
180
+ raise
181
+ rescue IOError, SocketError, SystemCallError => e
182
+ raise SocketWriteError, "error writing to socket: #{e}", e.backtrace
183
+ end
184
+ end
185
+ end
186
+ end