hearth 1.0.0.pre2 → 1.0.0.pre3
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 +5 -0
- data/VERSION +1 -1
- data/lib/hearth/anonymous_auth_resolver.rb +11 -0
- data/lib/hearth/auth_schemes/anonymous.rb +3 -3
- data/lib/hearth/auth_schemes.rb +3 -3
- data/lib/hearth/client.rb +66 -0
- data/lib/hearth/client_stubs.rb +1 -3
- data/lib/hearth/config/resolver.rb +6 -5
- data/lib/hearth/context.rb +1 -0
- data/lib/hearth/dns/host_address.rb +20 -16
- data/lib/hearth/endpoint_rules.rb +154 -0
- data/lib/hearth/http/client.rb +5 -7
- data/lib/hearth/http/error_inspector.rb +2 -2
- data/lib/hearth/http/field.rb +4 -19
- data/lib/hearth/http/header_list_builder.rb +42 -0
- data/lib/hearth/http/header_list_parser.rb +92 -0
- data/lib/hearth/http/middleware/content_length.rb +3 -3
- data/lib/hearth/http/middleware/content_md5.rb +0 -1
- data/lib/hearth/http/middleware/request_compression.rb +7 -10
- data/lib/hearth/http.rb +2 -0
- data/lib/hearth/{identity_resolver.rb → identity_provider.rb} +1 -1
- data/lib/hearth/interceptor_context.rb +8 -4
- data/lib/hearth/interceptors.rb +2 -1
- data/lib/hearth/json.rb +4 -4
- data/lib/hearth/middleware/auth.rb +9 -6
- data/lib/hearth/middleware/build.rb +0 -1
- data/lib/hearth/middleware/endpoint.rb +79 -0
- data/lib/hearth/middleware/host_prefix.rb +1 -2
- data/lib/hearth/middleware/initialize.rb +0 -1
- data/lib/hearth/middleware/parse.rb +0 -1
- data/lib/hearth/middleware/retry.rb +9 -2
- data/lib/hearth/middleware/send.rb +0 -1
- data/lib/hearth/middleware.rb +1 -0
- data/lib/hearth/middleware_stack.rb +1 -1
- data/lib/hearth/number_helper.rb +1 -1
- data/lib/hearth/query/param.rb +7 -3
- data/lib/hearth/query/param_matcher.rb +5 -6
- data/lib/hearth/{refreshing_identity_resolver.rb → refreshing_identity_provider.rb} +2 -2
- data/lib/hearth/request.rb +2 -2
- data/lib/hearth/response.rb +5 -2
- data/lib/hearth/retry/adaptive.rb +2 -2
- data/lib/hearth/retry/client_rate_limiter.rb +8 -6
- data/lib/hearth/retry/exponential_backoff.rb +1 -1
- data/lib/hearth/retry/standard.rb +2 -2
- data/lib/hearth/retry.rb +16 -3
- data/lib/hearth/structure.rb +7 -3
- data/lib/hearth/stubs.rb +12 -4
- data/lib/hearth/time_helper.rb +1 -2
- data/lib/hearth/validator.rb +37 -21
- data/lib/hearth/waiters/poller.rb +4 -2
- data/lib/hearth/waiters/waiter.rb +6 -5
- data/lib/hearth/xml/node.rb +0 -1
- data/lib/hearth/xml/node_matcher.rb +0 -1
- data/lib/hearth.rb +8 -4
- data/sig/lib/hearth/aliases.rbs +5 -3
- data/sig/lib/hearth/anonymous_auth_resolver.rbs +5 -0
- data/sig/lib/hearth/auth_schemes.rbs +1 -1
- data/sig/lib/hearth/client.rbs +9 -0
- data/sig/lib/hearth/configuration.rbs +2 -2
- data/sig/lib/hearth/dns/host_address.rbs +1 -3
- data/sig/lib/hearth/dns/host_resolver.rbs +3 -3
- data/sig/lib/hearth/endpoint_rules.rbs +17 -0
- data/sig/lib/hearth/http/field.rbs +1 -1
- data/sig/lib/hearth/http/fields.rbs +1 -1
- data/sig/lib/hearth/http/header_list_builder.rbs +15 -0
- data/sig/lib/hearth/http/header_list_parser.rbs +19 -0
- data/sig/lib/hearth/http/networking_error.rbs +6 -0
- data/sig/lib/hearth/http/response.rbs +1 -1
- data/sig/lib/hearth/identities.rbs +1 -1
- data/sig/lib/hearth/{identity_resolver.rbs → identity_provider.rbs} +1 -1
- data/sig/lib/hearth/interceptor_context.rbs +4 -2
- data/sig/lib/hearth/interfaces.rbs +52 -30
- data/sig/lib/hearth/json/parse_error.rbs +9 -0
- data/sig/lib/hearth/networking_error.rbs +7 -0
- data/sig/lib/hearth/output.rbs +4 -4
- data/sig/lib/hearth/plugin_list.rbs +5 -7
- data/sig/lib/hearth/query/param.rbs +2 -2
- data/sig/lib/hearth/refreshing_identity_provider.rbs +10 -0
- data/sig/lib/hearth/request.rbs +2 -2
- data/sig/lib/hearth/response.rbs +2 -2
- data/sig/lib/hearth/retry/exponential_backoff.rbs +1 -1
- data/sig/lib/hearth/retry.rbs +1 -1
- data/sig/lib/hearth/structure.rbs +1 -2
- data/sig/lib/hearth/stubs.rbs +9 -0
- data/sig/lib/hearth/union.rbs +1 -1
- data/sig/lib/hearth/xml/parse_error.rbs +9 -0
- metadata +26 -10
- data/lib/hearth/retry/strategy.rb +0 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5a6aa5be0acf82ba6ba9b2d399c702fd0da402764dd4769cc3d72a632074dbb4
|
4
|
+
data.tar.gz: f7b14b1778860d004cb9fcea31c1fb0d230760c330447136414342f37cbd3a52
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dd6f92a2e98ab0942ddd68a6edb86cd4e274389c4d1595ec2ff360a6c6a7a9887a5534a58271009eef0697018bf8327b5c144cc47a541e102d57a0f3fe198be2
|
7
|
+
data.tar.gz: 7ae01e2405d9d7301ea2b9f6b8805fcd438de16d498b9e394f7e0ba5452a3acc6e681b5a32e7371b3cbe9fced1984ded25e4b16bb3512d1bf282f19079490614
|
data/CHANGELOG.md
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.0.0.
|
1
|
+
1.0.0.pre3
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hearth
|
4
|
+
# Always returns the Anonymous/noAuth auth scheme.
|
5
|
+
# Can be used to effectively disable/skip auth.
|
6
|
+
class AnonymousAuthResolver
|
7
|
+
def resolve(_params)
|
8
|
+
[Hearth::AuthOption.new(scheme_id: 'smithy.api#noAuth')]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -12,9 +12,9 @@ module Hearth
|
|
12
12
|
)
|
13
13
|
end
|
14
14
|
|
15
|
-
# @return [
|
16
|
-
def
|
17
|
-
Hearth::
|
15
|
+
# @return [IdentityProvider, nil]
|
16
|
+
def identity_provider(_identity_providers = {})
|
17
|
+
Hearth::IdentityProvider.new(proc { Identities::Anonymous.new })
|
18
18
|
end
|
19
19
|
end
|
20
20
|
end
|
data/lib/hearth/auth_schemes.rb
CHANGED
@@ -14,9 +14,9 @@ module Hearth
|
|
14
14
|
# @return [String]
|
15
15
|
attr_reader :scheme_id
|
16
16
|
|
17
|
-
# @return [
|
18
|
-
def
|
19
|
-
|
17
|
+
# @return [IdentityProvider, nil]
|
18
|
+
def identity_provider(identity_provider = {})
|
19
|
+
identity_provider[@identity_type]
|
20
20
|
end
|
21
21
|
|
22
22
|
# @return [Signers::Base]
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'client_stubs'
|
4
|
+
|
5
|
+
module Hearth
|
6
|
+
# Base Client class for all generated SDK clients.
|
7
|
+
class Client
|
8
|
+
include ClientStubs
|
9
|
+
|
10
|
+
# Plugins applied to all instances of this client.
|
11
|
+
# @return [Hearth::PluginList]
|
12
|
+
def self.plugins
|
13
|
+
@plugins ||= PluginList.new
|
14
|
+
end
|
15
|
+
|
16
|
+
# @param [Hash] options
|
17
|
+
# Options used to construct an instance of {Config}
|
18
|
+
# @param [Class] config_class
|
19
|
+
# The configuration class to use.
|
20
|
+
def initialize(options, config_class)
|
21
|
+
@config = initialize_config(options, config_class)
|
22
|
+
end
|
23
|
+
|
24
|
+
# @return [Configuration]
|
25
|
+
attr_reader :config
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def initialize_config(options, config_class)
|
30
|
+
client_interceptors = options.delete(:interceptors) || []
|
31
|
+
config = config_class.new(**options)
|
32
|
+
config.validate!
|
33
|
+
self.class.plugins.each { |p| p.call(config) }
|
34
|
+
config.plugins.each { |p| p.call(config) }
|
35
|
+
config.interceptors.concat(client_interceptors)
|
36
|
+
config.validate!
|
37
|
+
config.freeze
|
38
|
+
end
|
39
|
+
|
40
|
+
def operation_config(options)
|
41
|
+
return @config if options.empty?
|
42
|
+
|
43
|
+
if options.include?(:stub_responses) || options.include?(:stubs)
|
44
|
+
msg = 'Overriding stubs or stub_responses on ' \
|
45
|
+
'operations is not allowed'
|
46
|
+
raise ArgumentError, msg
|
47
|
+
end
|
48
|
+
|
49
|
+
operation_plugins = options.delete(:plugins)
|
50
|
+
operation_interceptors = options.delete(:interceptors) || []
|
51
|
+
config = @config.merge(options)
|
52
|
+
config.validate!
|
53
|
+
operation_plugins&.each { |p| p.call(config) }
|
54
|
+
config.interceptors.concat(operation_interceptors)
|
55
|
+
config.validate!
|
56
|
+
config.freeze
|
57
|
+
end
|
58
|
+
|
59
|
+
def output_stream(options = {}, &block)
|
60
|
+
return options.delete(:output_stream) if options[:output_stream]
|
61
|
+
return Hearth::BlockIO.new(block) if block
|
62
|
+
|
63
|
+
::StringIO.new
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/hearth/client_stubs.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'stubs'
|
4
|
-
|
5
3
|
module Hearth
|
6
4
|
# This module provides the ability to specify the data and/or errors to
|
7
5
|
# return when a client is using stubbed responses.
|
@@ -119,7 +117,7 @@ module Hearth
|
|
119
117
|
# `:stub_responses => true`.
|
120
118
|
def stub_responses(operation_name, *stubs)
|
121
119
|
if @config.stub_responses
|
122
|
-
@stubs.
|
120
|
+
@config.stubs.set_stubs(operation_name, stubs.flatten)
|
123
121
|
else
|
124
122
|
msg = 'Stubbing is not enabled. Enable stubbing in Config ' \
|
125
123
|
'with `stub_responses: true`'
|
@@ -38,11 +38,12 @@ module Hearth
|
|
38
38
|
|
39
39
|
def resolve_default(key)
|
40
40
|
@defaults[key]&.each do |default|
|
41
|
-
value =
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
41
|
+
value =
|
42
|
+
if default.respond_to?(:call)
|
43
|
+
default.call(self)
|
44
|
+
else
|
45
|
+
default
|
46
|
+
end
|
46
47
|
return value unless value.nil?
|
47
48
|
end
|
48
49
|
nil
|
data/lib/hearth/context.rb
CHANGED
@@ -3,21 +3,25 @@
|
|
3
3
|
module Hearth
|
4
4
|
module DNS
|
5
5
|
# Address results from a DNS lookup in {HostResolver}.
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
6
|
+
# @!method initialize(*args)
|
7
|
+
# @option args [Symbol] :address_type The type of address. For example,
|
8
|
+
# :A or :AAAA.
|
9
|
+
# @option args [String] :address The IP address.
|
10
|
+
# @option args [String] :hostname The hostname that was resolved.
|
11
|
+
# @!attribute address_type
|
12
|
+
# The type of address. For example, :A or :AAAA.
|
13
|
+
# @return [Symbol]
|
14
|
+
# @!attribute address
|
15
|
+
# The IP address.
|
16
|
+
# @return [String]
|
17
|
+
# @!attribute hostname
|
18
|
+
# The hostname that was resolved.
|
19
|
+
# @return [String]
|
20
|
+
HostAddress = Struct.new(
|
21
|
+
:address_type,
|
22
|
+
:address,
|
23
|
+
:hostname,
|
24
|
+
keyword_init: true
|
25
|
+
)
|
22
26
|
end
|
23
27
|
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cgi'
|
4
|
+
require 'ipaddr'
|
5
|
+
require 'uri'
|
6
|
+
|
7
|
+
module Hearth
|
8
|
+
# Functions in the Smithy rules engine are named routines that
|
9
|
+
# operate on a finite set of specified inputs, returning an output.
|
10
|
+
# The rules engine has a set of included functions that can be
|
11
|
+
# invoked without additional dependencies, called the standard library.
|
12
|
+
module EndpointRules
|
13
|
+
# An Authentication Scheme supported by an Endpoint
|
14
|
+
# @!attribute scheme_id
|
15
|
+
# The identifier of the authentication scheme.
|
16
|
+
# @return [String]
|
17
|
+
# @!attribute properties
|
18
|
+
# Additional properties of the authentication scheme.
|
19
|
+
# @return [Hash]
|
20
|
+
AuthScheme = Struct.new(
|
21
|
+
:scheme_id,
|
22
|
+
:properties,
|
23
|
+
keyword_init: true
|
24
|
+
) do
|
25
|
+
# @option args [String] :scheme_id
|
26
|
+
# @option args [Hash] :properties ({})
|
27
|
+
def initialize(*args)
|
28
|
+
super
|
29
|
+
self.properties ||= {}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# An Endpoint resolved by an EndpointProvider
|
34
|
+
# @!attribute uri
|
35
|
+
# The URI of the endpoint.
|
36
|
+
# @return [String]
|
37
|
+
# @!attribute auth_schemes
|
38
|
+
# The authentication schemes supported by the endpoint.
|
39
|
+
# @return [Array<AuthScheme>]
|
40
|
+
# @!attribute headers
|
41
|
+
# The headers to include in requests to the endpoint.
|
42
|
+
# @return [Hash]
|
43
|
+
Endpoint = Struct.new(
|
44
|
+
:uri,
|
45
|
+
:auth_schemes,
|
46
|
+
:headers,
|
47
|
+
keyword_init: true
|
48
|
+
) do
|
49
|
+
# @option args [String] :uri
|
50
|
+
# @option args [Array<AuthScheme>] :auth_schemes ([])
|
51
|
+
# @option args [Hash] :headers ({})
|
52
|
+
def initialize(*args)
|
53
|
+
super
|
54
|
+
self.auth_schemes ||= []
|
55
|
+
self.headers ||= {}
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Evaluates whether the input string is a compliant RFC 1123 host segment.
|
60
|
+
# When allowSubDomains is true, evaluates whether the input string is
|
61
|
+
# composed of values that are each compliant RFC 1123 host segments
|
62
|
+
# joined by dot (.) characters.
|
63
|
+
# @api private
|
64
|
+
# rubocop:disable Style/OptionalBooleanParameter
|
65
|
+
def self.valid_host_label?(value, allow_sub_domains = false)
|
66
|
+
return false if value.empty?
|
67
|
+
|
68
|
+
if allow_sub_domains
|
69
|
+
labels = value.split('.', -1)
|
70
|
+
return labels.all? { |l| valid_host_label?(l, false) }
|
71
|
+
end
|
72
|
+
|
73
|
+
!!(value =~ /\A(?!-)[a-zA-Z0-9-]{1,63}(?<!-)\z/)
|
74
|
+
end
|
75
|
+
# rubocop:enable Style/OptionalBooleanParameter
|
76
|
+
|
77
|
+
# Computes a URL structure given an input string.
|
78
|
+
# @api private
|
79
|
+
def self.parse_url(value)
|
80
|
+
URL.new(value).as_json
|
81
|
+
rescue ArgumentError, URI::InvalidURIError
|
82
|
+
nil
|
83
|
+
end
|
84
|
+
|
85
|
+
# Computes a portion of a given string based on
|
86
|
+
# the provided start and end indices.
|
87
|
+
# @api private
|
88
|
+
def self.substring(input, start, stop, reverse)
|
89
|
+
return nil if start >= stop || input.size < stop
|
90
|
+
|
91
|
+
return nil if input.chars.any? { |c| c.ord > 127 }
|
92
|
+
|
93
|
+
return input[start...stop] unless reverse
|
94
|
+
|
95
|
+
r_start = input.size - stop
|
96
|
+
r_stop = input.size - start
|
97
|
+
input[r_start...r_stop]
|
98
|
+
end
|
99
|
+
|
100
|
+
# Performs RFC 3986#section-2.1 defined percent-encoding on the input value.
|
101
|
+
# @api private
|
102
|
+
def self.uri_encode(value)
|
103
|
+
CGI.escape(value.encode('UTF-8')).gsub('+', '%20').gsub('%7E', '~')
|
104
|
+
end
|
105
|
+
|
106
|
+
# @api private
|
107
|
+
class URL
|
108
|
+
def initialize(url)
|
109
|
+
uri = URI(url)
|
110
|
+
@scheme = uri.scheme
|
111
|
+
# only support http and https schemes
|
112
|
+
raise ArgumentError unless %w[https http].include?(@scheme)
|
113
|
+
|
114
|
+
# do not support query
|
115
|
+
raise ArgumentError if uri.query
|
116
|
+
|
117
|
+
@authority = _authority(url, uri)
|
118
|
+
@path = uri.path
|
119
|
+
@normalized_path = uri.path + (uri.path[-1] == '/' ? '' : '/')
|
120
|
+
@is_ip = _is_ip(uri.host)
|
121
|
+
end
|
122
|
+
|
123
|
+
attr_reader :scheme, :authority, :path, :normalized_path, :is_ip
|
124
|
+
|
125
|
+
def as_json(_options = {})
|
126
|
+
{
|
127
|
+
'scheme' => scheme,
|
128
|
+
'authority' => authority,
|
129
|
+
'path' => path,
|
130
|
+
'normalizedPath' => normalized_path,
|
131
|
+
'isIp' => is_ip
|
132
|
+
}
|
133
|
+
end
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
def _authority(url, uri)
|
138
|
+
# don't include port if it's default and not parsed originally
|
139
|
+
if uri.default_port == uri.port && !url.include?(":#{uri.port}")
|
140
|
+
uri.host
|
141
|
+
else
|
142
|
+
"#{uri.host}:#{uri.port}"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def _is_ip(authority)
|
147
|
+
IPAddr.new(authority)
|
148
|
+
true
|
149
|
+
rescue IPAddr::InvalidAddressError
|
150
|
+
false
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
data/lib/hearth/http/client.rb
CHANGED
@@ -11,7 +11,7 @@ module Hearth
|
|
11
11
|
class Client
|
12
12
|
# @api private
|
13
13
|
OPTIONS = {
|
14
|
-
logger:
|
14
|
+
logger: nil,
|
15
15
|
debug_output: nil,
|
16
16
|
proxy: nil,
|
17
17
|
open_timeout: 15,
|
@@ -31,12 +31,11 @@ module Hearth
|
|
31
31
|
#
|
32
32
|
# @param [Hash] options The options for this HTTP Client
|
33
33
|
#
|
34
|
-
# @option options [Logger] :logger (
|
35
|
-
#
|
36
|
-
# is enabled.
|
34
|
+
# @option options [Logger] :logger (nil) A logger used to log Net::HTTP
|
35
|
+
# requests and responses when `:debug_output` is enabled.
|
37
36
|
#
|
38
37
|
# @option options [Boolean] :debug_output (false) When `true`,
|
39
|
-
# sets an output stream to the configured Logger for debugging.
|
38
|
+
# sets an output stream to the configured Logger (if any) for debugging.
|
40
39
|
#
|
41
40
|
# @option options [String, URI] :proxy A proxy to send
|
42
41
|
# requests through. Formatted like 'http://proxy.com:123'.
|
@@ -131,11 +130,10 @@ module Hearth
|
|
131
130
|
end
|
132
131
|
|
133
132
|
# Starts and returns a new HTTP connection.
|
134
|
-
# @param [URI] endpoint
|
135
133
|
# @return [Net::HTTP]
|
136
134
|
def new_connection(endpoint, logger)
|
137
135
|
http = create_http(endpoint)
|
138
|
-
http.set_debug_output(logger ||
|
136
|
+
http.set_debug_output(@logger || logger) if @debug_output
|
139
137
|
configure_timeouts(http)
|
140
138
|
|
141
139
|
if endpoint.scheme == 'https'
|
@@ -5,7 +5,6 @@ require 'time'
|
|
5
5
|
module Hearth
|
6
6
|
module HTTP
|
7
7
|
# An HTTP error inspector, using hints from status code and headers.
|
8
|
-
# @api private
|
9
8
|
class ErrorInspector
|
10
9
|
def initialize(error, http_response)
|
11
10
|
@error = error
|
@@ -17,6 +16,7 @@ module Hearth
|
|
17
16
|
throttling? ||
|
18
17
|
transient? ||
|
19
18
|
server?) &&
|
19
|
+
# IO does not respond to #truncate and is not rewindable
|
20
20
|
@http_response.body.respond_to?(:truncate)
|
21
21
|
end
|
22
22
|
|
@@ -77,7 +77,7 @@ module Hearth
|
|
77
77
|
rescue ArgumentError # empty string, somehow
|
78
78
|
nil
|
79
79
|
end
|
80
|
-
rescue TypeError # header is not
|
80
|
+
rescue TypeError # header is not present
|
81
81
|
nil
|
82
82
|
end
|
83
83
|
end
|
data/lib/hearth/http/field.rb
CHANGED
@@ -5,9 +5,8 @@ module Hearth
|
|
5
5
|
# Represents an HTTP field.
|
6
6
|
class Field
|
7
7
|
# @param [String] name The name of the field.
|
8
|
-
# @param [
|
9
|
-
# object that responds to `#to_s
|
10
|
-
# `#to_s`.
|
8
|
+
# @param [#to_s] value (nil) The value for the field. It can be any
|
9
|
+
# object that responds to `#to_s`.
|
11
10
|
# @param [Symbol] kind The kind of field, either :header or :trailer.
|
12
11
|
def initialize(name, value = nil, kind: :header)
|
13
12
|
if name.nil? || name.empty?
|
@@ -25,17 +24,10 @@ module Hearth
|
|
25
24
|
# @return [Symbol]
|
26
25
|
attr_reader :kind
|
27
26
|
|
28
|
-
# Returns
|
27
|
+
# Returns a string representation of the field.
|
29
28
|
# @return [String]
|
30
29
|
def value(encoding = nil)
|
31
|
-
value
|
32
|
-
if @value.is_a?(Array)
|
33
|
-
@value.compact.map { |v| escape_value(v.to_s) }.join(', ')
|
34
|
-
else
|
35
|
-
@value.to_s
|
36
|
-
end
|
37
|
-
value = value.encode(encoding) if encoding
|
38
|
-
value
|
30
|
+
encoding ? @value.to_s.encode(encoding) : @value.to_s
|
39
31
|
end
|
40
32
|
|
41
33
|
# @return [Boolean]
|
@@ -52,13 +44,6 @@ module Hearth
|
|
52
44
|
def to_h
|
53
45
|
{ @name => value }
|
54
46
|
end
|
55
|
-
|
56
|
-
private
|
57
|
-
|
58
|
-
def escape_value(str)
|
59
|
-
s = str
|
60
|
-
s.include?('"') || s.include?(',') ? "\"#{s.gsub('"', '\"')}\"" : s
|
61
|
-
end
|
62
47
|
end
|
63
48
|
end
|
64
49
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hearth
|
4
|
+
module HTTP
|
5
|
+
# @api private
|
6
|
+
module HeaderListBuilder
|
7
|
+
class << self
|
8
|
+
def build_list(value)
|
9
|
+
value.compact.join(', ')
|
10
|
+
end
|
11
|
+
|
12
|
+
# builds a string from a list of possibly quoted values
|
13
|
+
# ensures that quoted values are escaped
|
14
|
+
def build_string_list(value)
|
15
|
+
value.compact.map { |s| escape_value(s) }.join(', ')
|
16
|
+
end
|
17
|
+
|
18
|
+
def build_http_date_list(value)
|
19
|
+
value.compact.map { |t| Hearth::TimeHelper.to_http_date(t) }
|
20
|
+
.join(', ')
|
21
|
+
end
|
22
|
+
|
23
|
+
def build_date_time_list(value)
|
24
|
+
value.compact.map { |t| Hearth::TimeHelper.to_date_time(t) }
|
25
|
+
.join(', ')
|
26
|
+
end
|
27
|
+
|
28
|
+
def build_epoch_seconds_list(value)
|
29
|
+
value.compact.map { |t| Hearth::TimeHelper.to_epoch_seconds(t) }
|
30
|
+
.join(', ')
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def escape_value(str)
|
36
|
+
s = str
|
37
|
+
s.include?('"') || s.include?(',') ? "\"#{s.gsub('"', '\"')}\"" : s
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'strscan'
|
4
|
+
|
5
|
+
module Hearth
|
6
|
+
module HTTP
|
7
|
+
# @api private
|
8
|
+
module HeaderListParser
|
9
|
+
class << self
|
10
|
+
def parse_boolean_list(value)
|
11
|
+
value.split(', ').map { |s| s == 'true' }
|
12
|
+
end
|
13
|
+
|
14
|
+
def parse_integer_list(value)
|
15
|
+
value.split(', ').map(&:to_i)
|
16
|
+
end
|
17
|
+
|
18
|
+
def parse_float_list(value)
|
19
|
+
value.split(', ').map(&:to_f)
|
20
|
+
end
|
21
|
+
|
22
|
+
# parse a list of possibly quoted and escaped string values
|
23
|
+
# Follows:
|
24
|
+
# # [RFC-7230's specification of header values](https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6).
|
25
|
+
def parse_string_list(value)
|
26
|
+
buffer = StringScanner.new(value)
|
27
|
+
parsed = []
|
28
|
+
|
29
|
+
parsed << read_value(buffer) until buffer.eos?
|
30
|
+
|
31
|
+
parsed
|
32
|
+
end
|
33
|
+
|
34
|
+
# rfc822/http-date has a comma after day but is NOT escaped.
|
35
|
+
# # eg: Mon, 16 Dec 2019 23:48:18 GMT, Mon, 16 Dec 2019 23:48:18 GMT
|
36
|
+
def parse_http_date_list(value)
|
37
|
+
value.split(',').each_slice(2).map { |v| Time.parse(v[0] + v[1]) }
|
38
|
+
end
|
39
|
+
|
40
|
+
def parse_date_time_list(value)
|
41
|
+
value.split(',').map { |v| Time.parse(v) }
|
42
|
+
end
|
43
|
+
|
44
|
+
def parse_epoch_seconds_list(value)
|
45
|
+
value.split(',').map { |v| Time.at(v.to_i) }
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def read_value(buffer)
|
51
|
+
until buffer.eos?
|
52
|
+
case buffer.peek(1)
|
53
|
+
when ' ', "\t"
|
54
|
+
# drop leading whitespace
|
55
|
+
buffer.getch
|
56
|
+
next
|
57
|
+
when '"'
|
58
|
+
buffer.getch # drop the quote and advance
|
59
|
+
return read_quoted_value(buffer)
|
60
|
+
else
|
61
|
+
return read_unquoted_value(buffer)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
# buffer is only whitespace
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
|
68
|
+
def read_unquoted_value(buffer)
|
69
|
+
# there cannot be any escaped values
|
70
|
+
value = buffer.scan_until(/,|$/)
|
71
|
+
# drop the comma if we matched it
|
72
|
+
buffer.matched == ',' ? value.chop : value
|
73
|
+
end
|
74
|
+
|
75
|
+
def read_quoted_value(buffer)
|
76
|
+
# scan until we have an unescaped double quote
|
77
|
+
value = buffer.scan_until(/[^\\]"/)
|
78
|
+
unless value
|
79
|
+
raise ArgumentError,
|
80
|
+
'Invalid String list: No closing quote found'
|
81
|
+
end
|
82
|
+
|
83
|
+
# drop any remaining whitespace/commas
|
84
|
+
buffer.scan_until(/[\s,]*/)
|
85
|
+
# the last character will always be the closing quote.
|
86
|
+
# Add a starting quote and then unescape (undump)
|
87
|
+
"\"#{value}".undump
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -4,7 +4,6 @@ module Hearth
|
|
4
4
|
module HTTP
|
5
5
|
module Middleware
|
6
6
|
# A middleware that sets Content-Length for any body that has a size.
|
7
|
-
# @api private
|
8
7
|
class ContentLength
|
9
8
|
include Hearth::Middleware::Logging
|
10
9
|
|
@@ -17,9 +16,10 @@ module Hearth
|
|
17
16
|
# @return [Output]
|
18
17
|
def call(input, context)
|
19
18
|
request = context.request
|
19
|
+
body = request.body
|
20
20
|
if !request.headers.key?('Content-Length') &&
|
21
|
-
|
22
|
-
length =
|
21
|
+
(body.respond_to?(:size) && body.size.positive?)
|
22
|
+
length = body.size
|
23
23
|
request.headers['Content-Length'] = length
|
24
24
|
log_debug(context, "Set Content-Length to #{length}")
|
25
25
|
end
|