http 5.3.1 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +241 -41
- data/LICENSE.txt +1 -1
- data/README.md +110 -13
- data/UPGRADING.md +491 -0
- data/http.gemspec +32 -29
- data/lib/http/base64.rb +11 -1
- data/lib/http/chainable/helpers.rb +62 -0
- data/lib/http/chainable/verbs.rb +136 -0
- data/lib/http/chainable.rb +232 -136
- data/lib/http/client.rb +158 -127
- data/lib/http/connection/internals.rb +141 -0
- data/lib/http/connection.rb +126 -97
- data/lib/http/content_type.rb +61 -6
- data/lib/http/errors.rb +25 -1
- data/lib/http/feature.rb +65 -5
- data/lib/http/features/auto_deflate.rb +124 -17
- data/lib/http/features/auto_inflate.rb +38 -15
- data/lib/http/features/caching/entry.rb +178 -0
- data/lib/http/features/caching/in_memory_store.rb +63 -0
- data/lib/http/features/caching.rb +216 -0
- data/lib/http/features/digest_auth.rb +234 -0
- data/lib/http/features/instrumentation.rb +97 -17
- data/lib/http/features/logging.rb +183 -5
- data/lib/http/features/normalize_uri.rb +17 -0
- data/lib/http/features/raise_error.rb +18 -3
- data/lib/http/form_data/composite_io.rb +106 -0
- data/lib/http/form_data/file.rb +95 -0
- data/lib/http/form_data/multipart/param.rb +62 -0
- data/lib/http/form_data/multipart.rb +106 -0
- data/lib/http/form_data/part.rb +52 -0
- data/lib/http/form_data/readable.rb +58 -0
- data/lib/http/form_data/urlencoded.rb +175 -0
- data/lib/http/form_data/version.rb +8 -0
- data/lib/http/form_data.rb +102 -0
- data/lib/http/headers/known.rb +3 -0
- data/lib/http/headers/normalizer.rb +17 -36
- data/lib/http/headers.rb +172 -65
- data/lib/http/mime_type/adapter.rb +24 -9
- data/lib/http/mime_type/json.rb +19 -4
- data/lib/http/mime_type.rb +21 -3
- data/lib/http/options/definitions.rb +189 -0
- data/lib/http/options.rb +172 -125
- data/lib/http/redirector.rb +80 -75
- data/lib/http/request/body.rb +87 -6
- data/lib/http/request/builder.rb +184 -0
- data/lib/http/request/proxy.rb +83 -0
- data/lib/http/request/writer.rb +76 -16
- data/lib/http/request.rb +214 -98
- data/lib/http/response/body.rb +103 -18
- data/lib/http/response/inflater.rb +35 -7
- data/lib/http/response/parser.rb +98 -4
- data/lib/http/response/status/reasons.rb +2 -4
- data/lib/http/response/status.rb +141 -31
- data/lib/http/response.rb +219 -61
- data/lib/http/retriable/delay_calculator.rb +38 -11
- data/lib/http/retriable/errors.rb +21 -0
- data/lib/http/retriable/performer.rb +82 -38
- data/lib/http/session.rb +280 -0
- data/lib/http/timeout/global.rb +147 -34
- data/lib/http/timeout/null.rb +155 -9
- data/lib/http/timeout/per_operation.rb +139 -18
- data/lib/http/uri/normalizer.rb +82 -0
- data/lib/http/uri/parsing.rb +182 -0
- data/lib/http/uri.rb +289 -124
- data/lib/http/version.rb +2 -1
- data/lib/http.rb +11 -2
- data/sig/deps.rbs +122 -0
- data/sig/http.rbs +1619 -0
- data/test/http/base64_test.rb +28 -0
- data/test/http/client_test.rb +739 -0
- data/test/http/connection_test.rb +1533 -0
- data/test/http/content_type_test.rb +190 -0
- data/test/http/errors_test.rb +28 -0
- data/test/http/feature_test.rb +49 -0
- data/test/http/features/auto_deflate_test.rb +317 -0
- data/test/http/features/auto_inflate_test.rb +213 -0
- data/test/http/features/caching_test.rb +942 -0
- data/test/http/features/digest_auth_test.rb +996 -0
- data/test/http/features/instrumentation_test.rb +246 -0
- data/test/http/features/logging_test.rb +654 -0
- data/test/http/features/normalize_uri_test.rb +41 -0
- data/test/http/features/raise_error_test.rb +77 -0
- data/test/http/form_data/composite_io_test.rb +215 -0
- data/test/http/form_data/file_test.rb +255 -0
- data/test/http/form_data/fixtures/the-http-gem.info +1 -0
- data/test/http/form_data/multipart_test.rb +303 -0
- data/test/http/form_data/part_test.rb +90 -0
- data/test/http/form_data/urlencoded_test.rb +164 -0
- data/test/http/form_data_test.rb +232 -0
- data/test/http/headers/normalizer_test.rb +93 -0
- data/test/http/headers_test.rb +888 -0
- data/test/http/mime_type/json_test.rb +39 -0
- data/test/http/mime_type_test.rb +150 -0
- data/test/http/options/base_uri_test.rb +148 -0
- data/test/http/options/body_test.rb +21 -0
- data/test/http/options/features_test.rb +38 -0
- data/test/http/options/form_test.rb +21 -0
- data/test/http/options/headers_test.rb +32 -0
- data/test/http/options/json_test.rb +21 -0
- data/test/http/options/merge_test.rb +78 -0
- data/test/http/options/new_test.rb +37 -0
- data/test/http/options/proxy_test.rb +32 -0
- data/test/http/options_test.rb +575 -0
- data/test/http/redirector_test.rb +639 -0
- data/test/http/request/body_test.rb +318 -0
- data/test/http/request/builder_test.rb +623 -0
- data/test/http/request/writer_test.rb +391 -0
- data/test/http/request_test.rb +1733 -0
- data/test/http/response/body_test.rb +292 -0
- data/test/http/response/parser_test.rb +105 -0
- data/test/http/response/status_test.rb +322 -0
- data/test/http/response_test.rb +502 -0
- data/test/http/retriable/delay_calculator_test.rb +194 -0
- data/test/http/retriable/errors_test.rb +71 -0
- data/test/http/retriable/performer_test.rb +551 -0
- data/test/http/session_test.rb +424 -0
- data/test/http/timeout/global_test.rb +239 -0
- data/test/http/timeout/null_test.rb +218 -0
- data/test/http/timeout/per_operation_test.rb +220 -0
- data/test/http/uri/normalizer_test.rb +89 -0
- data/test/http/uri_test.rb +1140 -0
- data/test/http/version_test.rb +15 -0
- data/test/http_test.rb +818 -0
- data/test/regression_tests.rb +27 -0
- data/test/support/dummy_server/encoding_routes.rb +47 -0
- data/test/support/dummy_server/routes.rb +201 -0
- data/test/support/dummy_server/servlet.rb +81 -0
- data/test/support/dummy_server.rb +200 -0
- data/{spec → test}/support/fakeio.rb +2 -2
- data/test/support/http_handling_shared/connection_reuse_tests.rb +97 -0
- data/test/support/http_handling_shared/timeout_tests.rb +134 -0
- data/test/support/http_handling_shared.rb +11 -0
- data/test/support/proxy_server.rb +207 -0
- data/test/support/servers/runner.rb +67 -0
- data/{spec → test}/support/simplecov.rb +11 -2
- data/test/support/ssl_helper.rb +108 -0
- data/test/test_helper.rb +38 -0
- metadata +108 -168
- data/.github/workflows/ci.yml +0 -67
- data/.gitignore +0 -15
- data/.rspec +0 -1
- data/.rubocop/layout.yml +0 -8
- data/.rubocop/metrics.yml +0 -4
- data/.rubocop/rspec.yml +0 -9
- data/.rubocop/style.yml +0 -32
- data/.rubocop.yml +0 -11
- data/.rubocop_todo.yml +0 -219
- data/.yardopts +0 -2
- data/CHANGES_OLD.md +0 -1002
- data/Gemfile +0 -51
- data/Guardfile +0 -18
- data/Rakefile +0 -64
- data/lib/http/headers/mixin.rb +0 -34
- data/lib/http/retriable/client.rb +0 -37
- data/logo.png +0 -0
- data/spec/lib/http/client_spec.rb +0 -556
- data/spec/lib/http/connection_spec.rb +0 -88
- data/spec/lib/http/content_type_spec.rb +0 -47
- data/spec/lib/http/features/auto_deflate_spec.rb +0 -77
- data/spec/lib/http/features/auto_inflate_spec.rb +0 -86
- data/spec/lib/http/features/instrumentation_spec.rb +0 -81
- data/spec/lib/http/features/logging_spec.rb +0 -65
- data/spec/lib/http/features/raise_error_spec.rb +0 -62
- data/spec/lib/http/headers/mixin_spec.rb +0 -36
- data/spec/lib/http/headers/normalizer_spec.rb +0 -52
- data/spec/lib/http/headers_spec.rb +0 -527
- data/spec/lib/http/options/body_spec.rb +0 -15
- data/spec/lib/http/options/features_spec.rb +0 -33
- data/spec/lib/http/options/form_spec.rb +0 -15
- data/spec/lib/http/options/headers_spec.rb +0 -24
- data/spec/lib/http/options/json_spec.rb +0 -15
- data/spec/lib/http/options/merge_spec.rb +0 -68
- data/spec/lib/http/options/new_spec.rb +0 -30
- data/spec/lib/http/options/proxy_spec.rb +0 -20
- data/spec/lib/http/options_spec.rb +0 -13
- data/spec/lib/http/redirector_spec.rb +0 -530
- data/spec/lib/http/request/body_spec.rb +0 -211
- data/spec/lib/http/request/writer_spec.rb +0 -121
- data/spec/lib/http/request_spec.rb +0 -234
- data/spec/lib/http/response/body_spec.rb +0 -85
- data/spec/lib/http/response/parser_spec.rb +0 -74
- data/spec/lib/http/response/status_spec.rb +0 -253
- data/spec/lib/http/response_spec.rb +0 -262
- data/spec/lib/http/retriable/delay_calculator_spec.rb +0 -69
- data/spec/lib/http/retriable/performer_spec.rb +0 -302
- data/spec/lib/http/uri/normalizer_spec.rb +0 -95
- data/spec/lib/http/uri_spec.rb +0 -71
- data/spec/lib/http_spec.rb +0 -535
- data/spec/regression_specs.rb +0 -24
- data/spec/spec_helper.rb +0 -89
- data/spec/support/black_hole.rb +0 -13
- data/spec/support/dummy_server/servlet.rb +0 -203
- data/spec/support/dummy_server.rb +0 -44
- data/spec/support/fuubar.rb +0 -21
- data/spec/support/http_handling_shared.rb +0 -190
- data/spec/support/proxy_server.rb +0 -39
- data/spec/support/servers/config.rb +0 -11
- data/spec/support/servers/runner.rb +0 -19
- data/spec/support/ssl_helper.rb +0 -104
- /data/{spec → test}/support/capture_warning.rb +0 -0
|
@@ -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
|
data/lib/http/headers/known.rb
CHANGED
|
@@ -72,6 +72,9 @@ module HTTP
|
|
|
72
72
|
# Currently defined methods are: chunked, compress, deflate, gzip, identity.
|
|
73
73
|
TRANSFER_ENCODING = "Transfer-Encoding"
|
|
74
74
|
|
|
75
|
+
# Chunked transfer coding value for Transfer-Encoding
|
|
76
|
+
CHUNKED = "chunked"
|
|
77
|
+
|
|
75
78
|
# Indicates what additional content codings have been applied to the
|
|
76
79
|
# entity-body.
|
|
77
80
|
CONTENT_ENCODING = "Content-Encoding"
|
|
@@ -2,62 +2,43 @@
|
|
|
2
2
|
|
|
3
3
|
module HTTP
|
|
4
4
|
class Headers
|
|
5
|
+
# Normalizes HTTP header names to canonical form
|
|
5
6
|
class Normalizer
|
|
6
|
-
# Matches HTTP header names when in "Canonical-Http-Format"
|
|
7
|
-
CANONICAL_NAME_RE = /\A[A-Z][a-z]*(?:-[A-Z][a-z]*)*\z/
|
|
8
|
-
|
|
9
7
|
# Matches valid header field name according to RFC.
|
|
10
8
|
# @see http://tools.ietf.org/html/rfc7230#section-3.2
|
|
11
9
|
COMPLIANT_NAME_RE = /\A[A-Za-z0-9!#$%&'*+\-.^_`|~]+\z/
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
# @private
|
|
16
|
-
# Normalized header names cache
|
|
17
|
-
class Cache
|
|
18
|
-
MAX_SIZE = 200
|
|
19
|
-
|
|
20
|
-
def initialize
|
|
21
|
-
@store = {}
|
|
22
|
-
end
|
|
11
|
+
# Pattern matching header name part separators (hyphens and underscores)
|
|
12
|
+
NAME_PARTS_SEPARATOR_RE = /[-_]/
|
|
23
13
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
end
|
|
27
|
-
alias [] get
|
|
28
|
-
|
|
29
|
-
def set(key, value)
|
|
30
|
-
# Maintain cache size
|
|
31
|
-
@store.delete(@store.each_key.first) while MAX_SIZE <= @store.size
|
|
32
|
-
|
|
33
|
-
@store[key] = value
|
|
34
|
-
end
|
|
35
|
-
alias []= set
|
|
36
|
-
end
|
|
14
|
+
# Thread-local cache key for normalized header names
|
|
15
|
+
CACHE_KEY = :http_headers_normalizer_cache
|
|
37
16
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
#
|
|
17
|
+
# Normalizes a header name to canonical form
|
|
18
|
+
#
|
|
19
|
+
# @example
|
|
20
|
+
# normalizer.call("content-type")
|
|
21
|
+
#
|
|
22
|
+
# @return [String]
|
|
23
|
+
# @api public
|
|
43
24
|
def call(name)
|
|
44
|
-
name =
|
|
45
|
-
|
|
25
|
+
name = name.to_s
|
|
26
|
+
cache = (Thread.current[CACHE_KEY] ||= {})
|
|
27
|
+
value = (cache[name] ||= normalize_header(name))
|
|
46
28
|
|
|
47
29
|
value.dup
|
|
48
30
|
end
|
|
49
31
|
|
|
50
32
|
private
|
|
51
33
|
|
|
52
|
-
# Transforms
|
|
34
|
+
# Transforms name to canonical HTTP header capitalization
|
|
53
35
|
#
|
|
54
36
|
# @param [String] name
|
|
55
37
|
# @raise [HeaderError] if normalized name does not
|
|
56
38
|
# match {COMPLIANT_NAME_RE}
|
|
57
39
|
# @return [String] canonical HTTP header name
|
|
40
|
+
# @api private
|
|
58
41
|
def normalize_header(name)
|
|
59
|
-
return name if CANONICAL_NAME_RE.match?(name)
|
|
60
|
-
|
|
61
42
|
normalized = name.split(NAME_PARTS_SEPARATOR_RE).each(&:capitalize!).join("-")
|
|
62
43
|
|
|
63
44
|
return normalized if COMPLIANT_NAME_RE.match?(normalized)
|