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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +267 -0
- data/CONTRIBUTING.md +26 -0
- data/LICENSE.txt +20 -0
- data/README.md +263 -0
- data/SECURITY.md +17 -0
- data/UPGRADING.md +491 -0
- data/http.gemspec +48 -0
- data/lib/http/base64.rb +22 -0
- data/lib/http/chainable/helpers.rb +62 -0
- data/lib/http/chainable/verbs.rb +136 -0
- data/lib/http/chainable.rb +377 -0
- data/lib/http/client.rb +230 -0
- data/lib/http/connection/internals.rb +141 -0
- data/lib/http/connection.rb +265 -0
- data/lib/http/content_type.rb +89 -0
- data/lib/http/errors.rb +67 -0
- data/lib/http/feature.rb +86 -0
- data/lib/http/features/auto_deflate.rb +230 -0
- data/lib/http/features/auto_inflate.rb +64 -0
- 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 +149 -0
- data/lib/http/features/logging.rb +231 -0
- data/lib/http/features/normalize_uri.rb +34 -0
- data/lib/http/features/raise_error.rb +37 -0
- 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 +90 -0
- data/lib/http/headers/normalizer.rb +50 -0
- data/lib/http/headers.rb +343 -0
- data/lib/http/mime_type/adapter.rb +43 -0
- data/lib/http/mime_type/json.rb +41 -0
- data/lib/http/mime_type.rb +96 -0
- data/lib/http/options/definitions.rb +189 -0
- data/lib/http/options.rb +241 -0
- data/lib/http/redirector.rb +157 -0
- data/lib/http/request/body.rb +181 -0
- data/lib/http/request/builder.rb +184 -0
- data/lib/http/request/proxy.rb +83 -0
- data/lib/http/request/writer.rb +186 -0
- data/lib/http/request.rb +375 -0
- data/lib/http/response/body.rb +172 -0
- data/lib/http/response/inflater.rb +60 -0
- data/lib/http/response/parser.rb +223 -0
- data/lib/http/response/status/reasons.rb +79 -0
- data/lib/http/response/status.rb +263 -0
- data/lib/http/response.rb +350 -0
- data/lib/http/retriable/delay_calculator.rb +91 -0
- data/lib/http/retriable/errors.rb +35 -0
- data/lib/http/retriable/performer.rb +197 -0
- data/lib/http/session.rb +280 -0
- data/lib/http/timeout/global.rb +229 -0
- data/lib/http/timeout/null.rb +225 -0
- data/lib/http/timeout/per_operation.rb +197 -0
- data/lib/http/uri/normalizer.rb +82 -0
- data/lib/http/uri/parsing.rb +182 -0
- data/lib/http/uri.rb +376 -0
- data/lib/http/version.rb +6 -0
- data/lib/http.rb +36 -0
- 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/capture_warning.rb +10 -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/test/support/fakeio.rb +21 -0
- 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/test/support/simplecov.rb +28 -0
- data/test/support/ssl_helper.rb +108 -0
- data/test/test_helper.rb +38 -0
- metadata +218 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "stringio"
|
|
4
|
+
|
|
5
|
+
require "http/form_data/readable"
|
|
6
|
+
|
|
7
|
+
module HTTP
|
|
8
|
+
module FormData
|
|
9
|
+
# Represents a body part of multipart/form-data request.
|
|
10
|
+
#
|
|
11
|
+
# @example Usage with String
|
|
12
|
+
#
|
|
13
|
+
# body = "Message"
|
|
14
|
+
# FormData::Part.new body, content_type: 'foobar.txt; charset="UTF-8"'
|
|
15
|
+
class Part
|
|
16
|
+
include Readable
|
|
17
|
+
|
|
18
|
+
# Returns the content type of this part
|
|
19
|
+
#
|
|
20
|
+
# @example
|
|
21
|
+
# part.content_type # => "application/json"
|
|
22
|
+
#
|
|
23
|
+
# @api public
|
|
24
|
+
# @return [String, nil]
|
|
25
|
+
attr_reader :content_type
|
|
26
|
+
|
|
27
|
+
# Returns the filename of this part
|
|
28
|
+
#
|
|
29
|
+
# @example
|
|
30
|
+
# part.filename # => "avatar.png"
|
|
31
|
+
#
|
|
32
|
+
# @api public
|
|
33
|
+
# @return [String, nil]
|
|
34
|
+
attr_reader :filename
|
|
35
|
+
|
|
36
|
+
# Creates a new Part with the given body and options
|
|
37
|
+
#
|
|
38
|
+
# @example
|
|
39
|
+
# Part.new("hello", content_type: "text/plain")
|
|
40
|
+
#
|
|
41
|
+
# @api public
|
|
42
|
+
# @param [#to_s] body
|
|
43
|
+
# @param [String] content_type Value of Content-Type header
|
|
44
|
+
# @param [String] filename Value of filename parameter
|
|
45
|
+
def initialize(body, content_type: nil, filename: nil)
|
|
46
|
+
@io = StringIO.new(body.to_s)
|
|
47
|
+
@content_type = content_type
|
|
48
|
+
@filename = filename
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HTTP
|
|
4
|
+
module FormData
|
|
5
|
+
# Common behaviour for objects defined by an IO object.
|
|
6
|
+
module Readable
|
|
7
|
+
# Returns IO content as a String
|
|
8
|
+
#
|
|
9
|
+
# @example
|
|
10
|
+
# readable.to_s # => "content"
|
|
11
|
+
#
|
|
12
|
+
# @api public
|
|
13
|
+
# @return [String]
|
|
14
|
+
def to_s
|
|
15
|
+
rewind
|
|
16
|
+
content = read #: String
|
|
17
|
+
rewind
|
|
18
|
+
content
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Reads and returns part of IO content
|
|
22
|
+
#
|
|
23
|
+
# @example
|
|
24
|
+
# readable.read # => "full content"
|
|
25
|
+
# readable.read(5) # => "full "
|
|
26
|
+
#
|
|
27
|
+
# @api public
|
|
28
|
+
# @param [Integer] length Number of bytes to retrieve
|
|
29
|
+
# @param [String] outbuf String to be replaced with retrieved data
|
|
30
|
+
# @return [String, nil]
|
|
31
|
+
def read(length = nil, outbuf = nil)
|
|
32
|
+
@io.read(length, outbuf)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Returns IO size in bytes
|
|
36
|
+
#
|
|
37
|
+
# @example
|
|
38
|
+
# readable.size # => 42
|
|
39
|
+
#
|
|
40
|
+
# @api public
|
|
41
|
+
# @return [Integer]
|
|
42
|
+
def size
|
|
43
|
+
@io.size
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Rewinds the IO to the beginning
|
|
47
|
+
#
|
|
48
|
+
# @example
|
|
49
|
+
# readable.rewind
|
|
50
|
+
#
|
|
51
|
+
# @api public
|
|
52
|
+
# @return [void]
|
|
53
|
+
def rewind
|
|
54
|
+
@io.rewind
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "http/form_data/readable"
|
|
4
|
+
|
|
5
|
+
require "uri"
|
|
6
|
+
require "stringio"
|
|
7
|
+
|
|
8
|
+
module HTTP
|
|
9
|
+
module FormData
|
|
10
|
+
# `application/x-www-form-urlencoded` form data.
|
|
11
|
+
class Urlencoded
|
|
12
|
+
include Readable
|
|
13
|
+
|
|
14
|
+
class << self
|
|
15
|
+
# Sets custom form data encoder implementation
|
|
16
|
+
#
|
|
17
|
+
# @example
|
|
18
|
+
#
|
|
19
|
+
# module CustomFormDataEncoder
|
|
20
|
+
# UNESCAPED_CHARS = /[^a-z0-9\-\.\_\~]/i
|
|
21
|
+
#
|
|
22
|
+
# def self.escape(s)
|
|
23
|
+
# ::URI::DEFAULT_PARSER.escape(s.to_s, UNESCAPED_CHARS)
|
|
24
|
+
# end
|
|
25
|
+
#
|
|
26
|
+
# def self.call(data)
|
|
27
|
+
# parts = []
|
|
28
|
+
#
|
|
29
|
+
# data.each do |k, v|
|
|
30
|
+
# k = escape(k)
|
|
31
|
+
#
|
|
32
|
+
# if v.nil?
|
|
33
|
+
# parts << k
|
|
34
|
+
# elsif v.respond_to?(:to_ary)
|
|
35
|
+
# v.to_ary.each { |vv| parts << "#{k}=#{escape vv}" }
|
|
36
|
+
# else
|
|
37
|
+
# parts << "#{k}=#{escape v}"
|
|
38
|
+
# end
|
|
39
|
+
# end
|
|
40
|
+
#
|
|
41
|
+
# parts.join("&")
|
|
42
|
+
# end
|
|
43
|
+
# end
|
|
44
|
+
#
|
|
45
|
+
# HTTP::FormData::Urlencoded.encoder = CustomFormDataEncoder
|
|
46
|
+
#
|
|
47
|
+
# @api public
|
|
48
|
+
# @raise [ArgumentError] if implementation does not respond to `#call`
|
|
49
|
+
# @param implementation [#call]
|
|
50
|
+
# @return [void]
|
|
51
|
+
def encoder=(implementation)
|
|
52
|
+
raise ArgumentError unless implementation.respond_to? :call
|
|
53
|
+
|
|
54
|
+
@encoder = implementation
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Returns form data encoder implementation
|
|
58
|
+
#
|
|
59
|
+
# @example
|
|
60
|
+
# Urlencoded.encoder # => #<Method: DefaultEncoder.encode>
|
|
61
|
+
#
|
|
62
|
+
# @api public
|
|
63
|
+
# @see .encoder=
|
|
64
|
+
# @return [#call]
|
|
65
|
+
def encoder
|
|
66
|
+
@encoder || DefaultEncoder
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Default encoder for urlencoded form data
|
|
70
|
+
module DefaultEncoder
|
|
71
|
+
class << self
|
|
72
|
+
# Recursively encodes form data value
|
|
73
|
+
#
|
|
74
|
+
# @example
|
|
75
|
+
# DefaultEncoder.encode({ foo: "bar" }) # => "foo=bar"
|
|
76
|
+
#
|
|
77
|
+
# @api public
|
|
78
|
+
# @param [Hash, Array, String, nil] value
|
|
79
|
+
# @param [String, nil] prefix
|
|
80
|
+
# @return [String]
|
|
81
|
+
def encode(value, prefix = nil)
|
|
82
|
+
case value
|
|
83
|
+
when Hash then encode_hash(value, prefix)
|
|
84
|
+
when Array then encode_array(value, prefix)
|
|
85
|
+
when nil then prefix.to_s
|
|
86
|
+
else
|
|
87
|
+
raise ArgumentError, "value must be a Hash" if prefix.nil?
|
|
88
|
+
|
|
89
|
+
"#{prefix}=#{escape(value)}"
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
alias call encode
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
|
|
97
|
+
# Encodes an Array value
|
|
98
|
+
#
|
|
99
|
+
# @api private
|
|
100
|
+
# @return [String]
|
|
101
|
+
def encode_array(value, prefix)
|
|
102
|
+
if prefix
|
|
103
|
+
value.map { |v| encode(v, "#{prefix}[]") }.join("&")
|
|
104
|
+
else
|
|
105
|
+
encode_pairs(value)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Encodes an Array of key-value pairs
|
|
110
|
+
#
|
|
111
|
+
# @api private
|
|
112
|
+
# @return [String]
|
|
113
|
+
def encode_pairs(pairs)
|
|
114
|
+
pairs.map { |k, v| encode(v, escape(k)) }.reject(&:empty?).join("&")
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Encodes a Hash value
|
|
118
|
+
#
|
|
119
|
+
# @api private
|
|
120
|
+
# @return [String]
|
|
121
|
+
def encode_hash(hash, prefix)
|
|
122
|
+
hash.map do |k, v|
|
|
123
|
+
encode(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
|
|
124
|
+
end.reject(&:empty?).join("&")
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# URL-encodes a value
|
|
128
|
+
#
|
|
129
|
+
# @api private
|
|
130
|
+
# @return [String]
|
|
131
|
+
def escape(value)
|
|
132
|
+
::URI.encode_www_form_component(value)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
private_constant :DefaultEncoder
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Creates a new Urlencoded form data instance
|
|
141
|
+
#
|
|
142
|
+
# @example
|
|
143
|
+
# Urlencoded.new({ "foo" => "bar" })
|
|
144
|
+
#
|
|
145
|
+
# @api public
|
|
146
|
+
# @param [Enumerable, Hash, #to_h] data form data key-value pairs
|
|
147
|
+
# @param [#call] encoder custom encoder implementation
|
|
148
|
+
def initialize(data, encoder: nil)
|
|
149
|
+
encoder ||= self.class.encoder
|
|
150
|
+
@io = StringIO.new(encoder.call(FormData.ensure_data(data)))
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Returns MIME type for the Content-Type header
|
|
154
|
+
#
|
|
155
|
+
# @example
|
|
156
|
+
# urlencoded.content_type
|
|
157
|
+
# # => "application/x-www-form-urlencoded"
|
|
158
|
+
#
|
|
159
|
+
# @api public
|
|
160
|
+
# @return [String]
|
|
161
|
+
def content_type
|
|
162
|
+
"application/x-www-form-urlencoded"
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Returns form data content size for Content-Length
|
|
166
|
+
#
|
|
167
|
+
# @example
|
|
168
|
+
# urlencoded.content_length # => 17
|
|
169
|
+
#
|
|
170
|
+
# @api public
|
|
171
|
+
# @return [Integer]
|
|
172
|
+
alias content_length size
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "http/form_data/part"
|
|
4
|
+
require "http/form_data/file"
|
|
5
|
+
require "http/form_data/multipart"
|
|
6
|
+
require "http/form_data/urlencoded"
|
|
7
|
+
require "http/form_data/version"
|
|
8
|
+
|
|
9
|
+
# http gem namespace.
|
|
10
|
+
# @see https://github.com/httprb/http
|
|
11
|
+
module HTTP
|
|
12
|
+
# Utility-belt to build form data request bodies.
|
|
13
|
+
# Provides support for `application/x-www-form-urlencoded` and
|
|
14
|
+
# `multipart/form-data` types.
|
|
15
|
+
#
|
|
16
|
+
# @example Usage
|
|
17
|
+
#
|
|
18
|
+
# form = FormData.create({
|
|
19
|
+
# username: "ixti",
|
|
20
|
+
# avatar_file: FormData::File.new("/home/ixti/avatar.png")
|
|
21
|
+
# })
|
|
22
|
+
#
|
|
23
|
+
# # Assuming socket is an open socket to some HTTP server
|
|
24
|
+
# socket << "POST /some-url HTTP/1.1\r\n"
|
|
25
|
+
# socket << "Host: example.com\r\n"
|
|
26
|
+
# socket << "Content-Type: #{form.content_type}\r\n"
|
|
27
|
+
# socket << "Content-Length: #{form.content_length}\r\n"
|
|
28
|
+
# socket << "\r\n"
|
|
29
|
+
# socket << form.to_s
|
|
30
|
+
module FormData
|
|
31
|
+
# CRLF
|
|
32
|
+
CRLF = "\r\n"
|
|
33
|
+
|
|
34
|
+
# Generic FormData error.
|
|
35
|
+
class Error < StandardError; end
|
|
36
|
+
|
|
37
|
+
class << self
|
|
38
|
+
# Selects encoder type based on given data
|
|
39
|
+
#
|
|
40
|
+
# @example
|
|
41
|
+
# FormData.create({ username: "ixti" })
|
|
42
|
+
#
|
|
43
|
+
# @api public
|
|
44
|
+
# @param [Enumerable, Hash, #to_h] data
|
|
45
|
+
# @return [Multipart] if any of values is a {FormData::File}
|
|
46
|
+
# @return [Urlencoded] otherwise
|
|
47
|
+
def create(data, encoder: nil)
|
|
48
|
+
data = ensure_data data
|
|
49
|
+
|
|
50
|
+
if multipart?(data)
|
|
51
|
+
Multipart.new(data)
|
|
52
|
+
else
|
|
53
|
+
Urlencoded.new(data, encoder: encoder)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Coerces obj to Hash
|
|
58
|
+
#
|
|
59
|
+
# @example
|
|
60
|
+
# FormData.ensure_hash({ foo: :bar }) # => { foo: :bar }
|
|
61
|
+
#
|
|
62
|
+
# @api public
|
|
63
|
+
# @raise [Error] `obj` can't be coerced
|
|
64
|
+
# @return [Hash]
|
|
65
|
+
def ensure_hash(obj)
|
|
66
|
+
if obj.is_a?(Hash) then obj
|
|
67
|
+
elsif obj.respond_to?(:to_h) then obj.to_h
|
|
68
|
+
else raise Error, "#{obj.inspect} is neither Hash nor responds to :to_h"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Coerces obj to an Enumerable of key-value pairs
|
|
73
|
+
#
|
|
74
|
+
# @example
|
|
75
|
+
# FormData.ensure_data([[:foo, :bar]]) # => [[:foo, :bar]]
|
|
76
|
+
#
|
|
77
|
+
# @api public
|
|
78
|
+
# @raise [Error] `obj` can't be coerced
|
|
79
|
+
# @return [Enumerable]
|
|
80
|
+
def ensure_data(obj)
|
|
81
|
+
if obj.nil? then []
|
|
82
|
+
elsif obj.is_a?(Enumerable) then obj
|
|
83
|
+
elsif obj.respond_to?(:to_h) then obj.to_h
|
|
84
|
+
else raise Error, "#{obj.inspect} is neither Enumerable nor responds to :to_h"
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
# Checks if data contains multipart data
|
|
91
|
+
#
|
|
92
|
+
# @api private
|
|
93
|
+
# @param [Enumerable] data
|
|
94
|
+
# @return [Boolean]
|
|
95
|
+
def multipart?(data)
|
|
96
|
+
data.any? do |_, v|
|
|
97
|
+
v.is_a?(Part) || (v.respond_to?(:to_ary) && v.to_ary.any?(Part))
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HTTP
|
|
4
|
+
class Headers
|
|
5
|
+
# Content-Types that are acceptable for the response.
|
|
6
|
+
ACCEPT = "Accept"
|
|
7
|
+
|
|
8
|
+
# Content-codings that are acceptable in the response.
|
|
9
|
+
ACCEPT_ENCODING = "Accept-Encoding"
|
|
10
|
+
|
|
11
|
+
# The age the object has been in a proxy cache in seconds.
|
|
12
|
+
AGE = "Age"
|
|
13
|
+
|
|
14
|
+
# Authentication credentials for HTTP authentication.
|
|
15
|
+
AUTHORIZATION = "Authorization"
|
|
16
|
+
|
|
17
|
+
# Used to specify directives that must be obeyed by all caching mechanisms
|
|
18
|
+
# along the request-response chain.
|
|
19
|
+
CACHE_CONTROL = "Cache-Control"
|
|
20
|
+
|
|
21
|
+
# An HTTP cookie previously sent by the server with Set-Cookie.
|
|
22
|
+
COOKIE = "Cookie"
|
|
23
|
+
|
|
24
|
+
# Control options for the current connection and list
|
|
25
|
+
# of hop-by-hop request fields.
|
|
26
|
+
CONNECTION = "Connection"
|
|
27
|
+
|
|
28
|
+
# The length of the request body in octets (8-bit bytes).
|
|
29
|
+
CONTENT_LENGTH = "Content-Length"
|
|
30
|
+
|
|
31
|
+
# The MIME type of the body of the request
|
|
32
|
+
# (used with POST and PUT requests).
|
|
33
|
+
CONTENT_TYPE = "Content-Type"
|
|
34
|
+
|
|
35
|
+
# The date and time that the message was sent (in "HTTP-date" format as
|
|
36
|
+
# defined by RFC 7231 Date/Time Formats).
|
|
37
|
+
DATE = "Date"
|
|
38
|
+
|
|
39
|
+
# An identifier for a specific version of a resource,
|
|
40
|
+
# often a message digest.
|
|
41
|
+
ETAG = "ETag"
|
|
42
|
+
|
|
43
|
+
# Gives the date/time after which the response is considered stale (in
|
|
44
|
+
# "HTTP-date" format as defined by RFC 7231).
|
|
45
|
+
EXPIRES = "Expires"
|
|
46
|
+
|
|
47
|
+
# The domain name of the server (for virtual hosting), and the TCP port
|
|
48
|
+
# number on which the server is listening. The port number may be omitted
|
|
49
|
+
# if the port is the standard port for the service requested.
|
|
50
|
+
HOST = "Host"
|
|
51
|
+
|
|
52
|
+
# Allows a 304 Not Modified to be returned if content is unchanged.
|
|
53
|
+
IF_MODIFIED_SINCE = "If-Modified-Since"
|
|
54
|
+
|
|
55
|
+
# Allows a 304 Not Modified to be returned if content is unchanged.
|
|
56
|
+
IF_NONE_MATCH = "If-None-Match"
|
|
57
|
+
|
|
58
|
+
# The last modified date for the requested object (in "HTTP-date" format as
|
|
59
|
+
# defined by RFC 7231).
|
|
60
|
+
LAST_MODIFIED = "Last-Modified"
|
|
61
|
+
|
|
62
|
+
# Used in redirection, or when a new resource has been created.
|
|
63
|
+
LOCATION = "Location"
|
|
64
|
+
|
|
65
|
+
# Authorization credentials for connecting to a proxy.
|
|
66
|
+
PROXY_AUTHORIZATION = "Proxy-Authorization"
|
|
67
|
+
|
|
68
|
+
# An HTTP cookie.
|
|
69
|
+
SET_COOKIE = "Set-Cookie"
|
|
70
|
+
|
|
71
|
+
# The form of encoding used to safely transfer the entity to the user.
|
|
72
|
+
# Currently defined methods are: chunked, compress, deflate, gzip, identity.
|
|
73
|
+
TRANSFER_ENCODING = "Transfer-Encoding"
|
|
74
|
+
|
|
75
|
+
# Chunked transfer coding value for Transfer-Encoding
|
|
76
|
+
CHUNKED = "chunked"
|
|
77
|
+
|
|
78
|
+
# Indicates what additional content codings have been applied to the
|
|
79
|
+
# entity-body.
|
|
80
|
+
CONTENT_ENCODING = "Content-Encoding"
|
|
81
|
+
|
|
82
|
+
# The user agent string of the user agent.
|
|
83
|
+
USER_AGENT = "User-Agent"
|
|
84
|
+
|
|
85
|
+
# Tells downstream proxies how to match future request headers to decide
|
|
86
|
+
# whether the cached response can be used rather than requesting a fresh
|
|
87
|
+
# one from the origin server.
|
|
88
|
+
VARY = "Vary"
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HTTP
|
|
4
|
+
class Headers
|
|
5
|
+
# Normalizes HTTP header names to canonical form
|
|
6
|
+
class Normalizer
|
|
7
|
+
# Matches valid header field name according to RFC.
|
|
8
|
+
# @see http://tools.ietf.org/html/rfc7230#section-3.2
|
|
9
|
+
COMPLIANT_NAME_RE = /\A[A-Za-z0-9!#$%&'*+\-.^_`|~]+\z/
|
|
10
|
+
|
|
11
|
+
# Pattern matching header name part separators (hyphens and underscores)
|
|
12
|
+
NAME_PARTS_SEPARATOR_RE = /[-_]/
|
|
13
|
+
|
|
14
|
+
# Thread-local cache key for normalized header names
|
|
15
|
+
CACHE_KEY = :http_headers_normalizer_cache
|
|
16
|
+
|
|
17
|
+
# Normalizes a header name to canonical form
|
|
18
|
+
#
|
|
19
|
+
# @example
|
|
20
|
+
# normalizer.call("content-type")
|
|
21
|
+
#
|
|
22
|
+
# @return [String]
|
|
23
|
+
# @api public
|
|
24
|
+
def call(name)
|
|
25
|
+
name = name.to_s
|
|
26
|
+
cache = (Thread.current[CACHE_KEY] ||= {})
|
|
27
|
+
value = (cache[name] ||= normalize_header(name))
|
|
28
|
+
|
|
29
|
+
value.dup
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
# Transforms name to canonical HTTP header capitalization
|
|
35
|
+
#
|
|
36
|
+
# @param [String] name
|
|
37
|
+
# @raise [HeaderError] if normalized name does not
|
|
38
|
+
# match {COMPLIANT_NAME_RE}
|
|
39
|
+
# @return [String] canonical HTTP header name
|
|
40
|
+
# @api private
|
|
41
|
+
def normalize_header(name)
|
|
42
|
+
normalized = name.split(NAME_PARTS_SEPARATOR_RE).each(&:capitalize!).join("-")
|
|
43
|
+
|
|
44
|
+
return normalized if COMPLIANT_NAME_RE.match?(normalized)
|
|
45
|
+
|
|
46
|
+
raise HeaderError, "Invalid HTTP header field name: #{name.inspect}"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|