hearth 1.0.0.pre1 → 1.0.0.pre3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -4
- data/VERSION +1 -1
- data/lib/hearth/anonymous_auth_resolver.rb +11 -0
- data/lib/hearth/api_error.rb +15 -1
- data/lib/hearth/auth_option.rb +21 -0
- data/lib/hearth/auth_schemes/anonymous.rb +21 -0
- data/lib/hearth/auth_schemes/http_api_key.rb +16 -0
- data/lib/hearth/auth_schemes/http_basic.rb +16 -0
- data/lib/hearth/auth_schemes/http_bearer.rb +16 -0
- data/lib/hearth/auth_schemes/http_digest.rb +16 -0
- data/lib/hearth/auth_schemes.rb +32 -0
- data/lib/hearth/checksums.rb +31 -0
- data/lib/hearth/client.rb +66 -0
- data/lib/hearth/client_stubs.rb +128 -0
- data/lib/hearth/config/env_provider.rb +53 -0
- data/lib/hearth/config/resolver.rb +53 -0
- data/lib/hearth/configuration.rb +15 -0
- data/lib/hearth/connection_pool.rb +77 -0
- data/lib/hearth/context.rb +29 -4
- data/lib/hearth/dns/host_address.rb +27 -0
- data/lib/hearth/dns/host_resolver.rb +92 -0
- data/lib/hearth/dns.rb +48 -0
- data/lib/hearth/endpoint_rules.rb +154 -0
- data/lib/hearth/http/api_error.rb +4 -8
- data/lib/hearth/http/client.rb +206 -59
- data/lib/hearth/http/error_inspector.rb +85 -0
- data/lib/hearth/http/error_parser.rb +18 -20
- data/lib/hearth/http/field.rb +49 -0
- data/lib/hearth/http/fields.rb +117 -0
- 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 +7 -4
- data/lib/hearth/http/middleware/content_md5.rb +30 -0
- data/lib/hearth/http/middleware/request_compression.rb +154 -0
- data/lib/hearth/http/middleware.rb +12 -0
- data/lib/hearth/http/networking_error.rb +1 -14
- data/lib/hearth/http/request.rb +83 -56
- data/lib/hearth/http/response.rb +42 -13
- data/lib/hearth/http.rb +16 -5
- data/lib/hearth/identities/anonymous.rb +8 -0
- data/lib/hearth/identities/http_api_key.rb +16 -0
- data/lib/hearth/identities/http_bearer.rb +16 -0
- data/lib/hearth/identities/http_login.rb +20 -0
- data/lib/hearth/identities.rb +21 -0
- data/lib/hearth/identity_provider.rb +17 -0
- data/lib/hearth/interceptor.rb +506 -0
- data/lib/hearth/interceptor_context.rb +40 -0
- data/lib/hearth/interceptor_list.rb +48 -0
- data/lib/hearth/interceptors.rb +76 -0
- data/lib/hearth/json.rb +4 -4
- data/lib/hearth/middleware/auth.rb +103 -0
- data/lib/hearth/middleware/build.rb +32 -1
- data/lib/hearth/middleware/endpoint.rb +79 -0
- data/lib/hearth/middleware/host_prefix.rb +11 -8
- data/lib/hearth/middleware/initialize.rb +57 -0
- data/lib/hearth/middleware/parse.rb +45 -7
- data/lib/hearth/middleware/retry.rb +105 -24
- data/lib/hearth/middleware/send.rb +137 -26
- data/lib/hearth/middleware/sign.rb +65 -0
- data/lib/hearth/middleware/validate.rb +11 -1
- data/lib/hearth/middleware.rb +20 -8
- data/lib/hearth/middleware_stack.rb +2 -44
- data/lib/hearth/networking_error.rb +18 -0
- data/lib/hearth/number_helper.rb +3 -3
- data/lib/hearth/output.rb +8 -4
- data/lib/hearth/plugin_list.rb +53 -0
- data/lib/hearth/query/param.rb +56 -0
- data/lib/hearth/query/param_list.rb +54 -0
- data/lib/hearth/query/param_matcher.rb +31 -0
- data/lib/hearth/refreshing_identity_provider.rb +63 -0
- data/lib/hearth/request.rb +22 -0
- data/lib/hearth/response.rb +36 -0
- data/lib/hearth/retry/adaptive.rb +60 -0
- data/lib/hearth/retry/capacity_not_available_error.rb +9 -0
- data/lib/hearth/retry/client_rate_limiter.rb +145 -0
- data/lib/hearth/retry/exponential_backoff.rb +15 -0
- data/lib/hearth/retry/retry_quota.rb +56 -0
- data/lib/hearth/retry/standard.rb +46 -0
- data/lib/hearth/retry.rb +29 -0
- data/lib/hearth/signers/anonymous.rb +16 -0
- data/lib/hearth/signers/http_api_key.rb +29 -0
- data/lib/hearth/signers/http_basic.rb +23 -0
- data/lib/hearth/signers/http_bearer.rb +19 -0
- data/lib/hearth/signers/http_digest.rb +19 -0
- data/lib/hearth/signers.rb +23 -0
- data/lib/hearth/structure.rb +7 -3
- data/lib/hearth/stubs.rb +38 -0
- data/lib/hearth/time_helper.rb +6 -5
- data/lib/hearth/validator.rb +60 -5
- data/lib/hearth/waiters/poller.rb +10 -9
- data/lib/hearth/waiters/waiter.rb +23 -9
- data/lib/hearth/xml/formatter.rb +11 -2
- data/lib/hearth/xml/node.rb +2 -3
- data/lib/hearth/xml/node_matcher.rb +0 -1
- data/lib/hearth.rb +37 -6
- data/sig/lib/hearth/aliases.rbs +6 -0
- data/sig/lib/hearth/anonymous_auth_resolver.rbs +5 -0
- data/sig/lib/hearth/api_error.rbs +13 -0
- data/sig/lib/hearth/auth_option.rbs +11 -0
- data/sig/lib/hearth/auth_schemes/anonymous.rbs +7 -0
- data/sig/lib/hearth/auth_schemes/http_api_key.rbs +7 -0
- data/sig/lib/hearth/auth_schemes/http_basic.rbs +7 -0
- data/sig/lib/hearth/auth_schemes/http_bearer.rbs +7 -0
- data/sig/lib/hearth/auth_schemes/http_digest.rbs +7 -0
- data/sig/lib/hearth/auth_schemes.rbs +13 -0
- data/sig/lib/hearth/block_io.rbs +9 -0
- data/sig/lib/hearth/client.rbs +9 -0
- data/sig/lib/hearth/client_stubs.rbs +5 -0
- data/sig/lib/hearth/configuration.rbs +7 -0
- data/sig/lib/hearth/dns/host_address.rbs +11 -0
- data/sig/lib/hearth/dns/host_resolver.rbs +19 -0
- data/sig/lib/hearth/endpoint_rules.rbs +17 -0
- data/sig/lib/hearth/http/api_error.rbs +13 -0
- data/sig/lib/hearth/http/client.rbs +9 -0
- data/sig/lib/hearth/http/field.rbs +19 -0
- data/sig/lib/hearth/http/fields.rbs +43 -0
- 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/request.rbs +25 -0
- data/sig/lib/hearth/http/response.rbs +21 -0
- data/sig/lib/hearth/identities/anonymous.rbs +6 -0
- data/sig/lib/hearth/identities/http_api_key.rbs +9 -0
- data/sig/lib/hearth/identities/http_bearer.rbs +9 -0
- data/sig/lib/hearth/identities/http_login.rbs +11 -0
- data/sig/lib/hearth/identities.rbs +9 -0
- data/sig/lib/hearth/identity_provider.rbs +7 -0
- data/sig/lib/hearth/interceptor.rbs +9 -0
- data/sig/lib/hearth/interceptor_context.rbs +17 -0
- data/sig/lib/hearth/interceptor_list.rbs +16 -0
- data/sig/lib/hearth/interfaces.rbs +87 -0
- 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 +11 -0
- data/sig/lib/hearth/plugin_list.rbs +13 -0
- data/sig/lib/hearth/query/param.rbs +17 -0
- data/sig/lib/hearth/query/param_list.rbs +25 -0
- data/sig/lib/hearth/refreshing_identity_provider.rbs +10 -0
- data/sig/lib/hearth/request.rbs +9 -0
- data/sig/lib/hearth/response.rbs +11 -0
- data/sig/lib/hearth/retry/adaptive.rbs +13 -0
- data/sig/lib/hearth/retry/exponential_backoff.rbs +7 -0
- data/sig/lib/hearth/retry/standard.rbs +13 -0
- data/sig/lib/hearth/retry/strategy.rbs +11 -0
- data/sig/lib/hearth/retry.rbs +9 -0
- data/sig/lib/hearth/signers/anonymous.rbs +9 -0
- data/sig/lib/hearth/signers/http_api_key.rbs +9 -0
- data/sig/lib/hearth/signers/http_basic.rbs +9 -0
- data/sig/lib/hearth/signers/http_bearer.rbs +9 -0
- data/sig/lib/hearth/signers/http_digest.rbs +9 -0
- data/sig/lib/hearth/signers.rbs +9 -0
- data/sig/lib/hearth/structure.rbs +6 -0
- data/sig/lib/hearth/stubs.rbs +9 -0
- data/sig/lib/hearth/union.rbs +5 -0
- data/sig/lib/hearth/waiters/waiter.rbs +17 -0
- data/sig/lib/hearth/xml/parse_error.rbs +9 -0
- metadata +151 -25
- data/lib/hearth/http/headers.rb +0 -70
- data/lib/hearth/middleware/around_handler.rb +0 -24
- data/lib/hearth/middleware/request_handler.rb +0 -24
- data/lib/hearth/middleware/response_handler.rb +0 -25
- data/lib/hearth/middleware_builder.rb +0 -246
- data/lib/hearth/stubbing/client_stubs.rb +0 -115
- data/lib/hearth/stubbing/stubs.rb +0 -32
- data/lib/hearth/waiters/errors.rb +0 -15
- data/sig/lib/seahorse/api_error.rbs +0 -10
- data/sig/lib/seahorse/document.rbs +0 -2
- data/sig/lib/seahorse/http/api_error.rbs +0 -21
- data/sig/lib/seahorse/http/headers.rbs +0 -47
- data/sig/lib/seahorse/http/response.rbs +0 -21
- data/sig/lib/seahorse/simple_delegator.rbs +0 -3
- data/sig/lib/seahorse/structure.rbs +0 -18
- data/sig/lib/seahorse/stubbing/client_stubs.rbs +0 -103
- data/sig/lib/seahorse/stubbing/stubs.rbs +0 -14
- data/sig/lib/seahorse/union.rbs +0 -6
@@ -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,8 +4,9 @@ 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
|
8
|
+
include Hearth::Middleware::Logging
|
9
|
+
|
9
10
|
def initialize(app, _ = {})
|
10
11
|
@app = app
|
11
12
|
end
|
@@ -15,10 +16,12 @@ module Hearth
|
|
15
16
|
# @return [Output]
|
16
17
|
def call(input, context)
|
17
18
|
request = context.request
|
18
|
-
|
19
|
-
|
20
|
-
|
19
|
+
body = request.body
|
20
|
+
if !request.headers.key?('Content-Length') &&
|
21
|
+
(body.respond_to?(:size) && body.size.positive?)
|
22
|
+
length = body.size
|
21
23
|
request.headers['Content-Length'] = length
|
24
|
+
log_debug(context, "Set Content-Length to #{length}")
|
22
25
|
end
|
23
26
|
|
24
27
|
@app.call(input, context)
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hearth
|
4
|
+
module HTTP
|
5
|
+
module Middleware
|
6
|
+
# A middleware that sets Content-MD5 for any body.
|
7
|
+
class ContentMD5
|
8
|
+
include Hearth::Middleware::Logging
|
9
|
+
|
10
|
+
def initialize(app, _ = {})
|
11
|
+
@app = app
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param input
|
15
|
+
# @param context
|
16
|
+
# @return [Output]
|
17
|
+
def call(input, context)
|
18
|
+
request = context.request
|
19
|
+
unless request.headers.key?('Content-MD5')
|
20
|
+
md5 = Hearth::Checksums.md5(request.body)
|
21
|
+
request.headers['Content-MD5'] = md5
|
22
|
+
log_debug(context, "Set Content-MD5 to #{md5}")
|
23
|
+
end
|
24
|
+
|
25
|
+
@app.call(input, context)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hearth
|
4
|
+
module HTTP
|
5
|
+
module Middleware
|
6
|
+
# A middleware that compresses the request body and
|
7
|
+
# adds the Content-Encoding header
|
8
|
+
class RequestCompression
|
9
|
+
include Hearth::Middleware::Logging
|
10
|
+
|
11
|
+
SUPPORTED_ENCODINGS = %w[gzip].freeze
|
12
|
+
CHUNK_SIZE = 1 * 1024 * 1024 # one MB
|
13
|
+
|
14
|
+
# @param [Class] app The next middleware in the stack.
|
15
|
+
# @param [Boolean] disable_request_compression If true, the request
|
16
|
+
# body is not compressed.
|
17
|
+
# @param [Integer] request_min_compression_size_bytes The minimum size
|
18
|
+
# of the request body to be compressed.
|
19
|
+
# @param [Array<String>] encodings The encodings to be used for
|
20
|
+
# compression.
|
21
|
+
# @param [Boolean] streaming If true, the request body is compressed
|
22
|
+
# in chunks.
|
23
|
+
def initialize(app, disable_request_compression:,
|
24
|
+
request_min_compression_size_bytes:, encodings:,
|
25
|
+
streaming:)
|
26
|
+
@app = app
|
27
|
+
@disable_request_compression = disable_request_compression
|
28
|
+
@request_min_compression_size_bytes =
|
29
|
+
request_min_compression_size_bytes
|
30
|
+
@encodings = encodings
|
31
|
+
@streaming = streaming
|
32
|
+
end
|
33
|
+
|
34
|
+
# @param input
|
35
|
+
# @param context
|
36
|
+
# @return [Output]
|
37
|
+
def call(input, context)
|
38
|
+
compress_request(context) unless @disable_request_compression
|
39
|
+
@app.call(input, context)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def compress_request(context)
|
45
|
+
selected_encoding = @encodings.find do |encoding|
|
46
|
+
SUPPORTED_ENCODINGS.include?(encoding)
|
47
|
+
end
|
48
|
+
return unless selected_encoding
|
49
|
+
|
50
|
+
log_debug(context, "Compressing request with: #{selected_encoding}")
|
51
|
+
request = context.request
|
52
|
+
if @streaming
|
53
|
+
compress_streaming_body(selected_encoding, request)
|
54
|
+
log_debug(context, 'Compressed request body in chunks')
|
55
|
+
elsif request.body.size >= @request_min_compression_size_bytes
|
56
|
+
compress_body(selected_encoding, request)
|
57
|
+
log_debug(context, 'Compressed request body')
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def update_content_encoding(encoding, request)
|
62
|
+
headers = request.headers
|
63
|
+
if headers['Content-Encoding']
|
64
|
+
headers['Content-Encoding'] += ", #{encoding}"
|
65
|
+
else
|
66
|
+
headers['Content-Encoding'] = encoding
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def compress_body(encoding, request)
|
71
|
+
return unless encoding == 'gzip'
|
72
|
+
|
73
|
+
gzip_compress(request)
|
74
|
+
update_content_encoding(encoding, request)
|
75
|
+
end
|
76
|
+
|
77
|
+
def gzip_compress(request)
|
78
|
+
compressed = StringIO.new
|
79
|
+
compressed.binmode
|
80
|
+
gzip_writer = Zlib::GzipWriter.new(compressed)
|
81
|
+
if request.body.respond_to?(:read)
|
82
|
+
update_in_chunks(gzip_writer, request.body)
|
83
|
+
else
|
84
|
+
gzip_writer.write(request.body)
|
85
|
+
end
|
86
|
+
gzip_writer.close
|
87
|
+
new_body = StringIO.new(compressed.string)
|
88
|
+
request.body = new_body
|
89
|
+
end
|
90
|
+
|
91
|
+
def update_in_chunks(compressor, io)
|
92
|
+
loop do
|
93
|
+
chunk = io.read(CHUNK_SIZE)
|
94
|
+
break unless chunk
|
95
|
+
|
96
|
+
compressor.write(chunk)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def compress_streaming_body(encoding, request)
|
101
|
+
return unless encoding == 'gzip'
|
102
|
+
|
103
|
+
request.body = GzipIO.new(request.body)
|
104
|
+
update_content_encoding(encoding, request)
|
105
|
+
end
|
106
|
+
|
107
|
+
# @api private
|
108
|
+
class GzipIO
|
109
|
+
def initialize(body)
|
110
|
+
@body = body
|
111
|
+
@buffer = ChunkBuffer.new
|
112
|
+
@gzip_writer = Zlib::GzipWriter.new(@buffer)
|
113
|
+
end
|
114
|
+
|
115
|
+
def read(length, buff = nil)
|
116
|
+
if @gzip_writer.closed?
|
117
|
+
# an empty string to signify an end as
|
118
|
+
# there will be nothing remaining to be read
|
119
|
+
StringIO.new('').read(length, buff)
|
120
|
+
return
|
121
|
+
end
|
122
|
+
|
123
|
+
chunk = @body.read(length)
|
124
|
+
if !chunk || chunk.empty?
|
125
|
+
# closing the writer will write one last chunk
|
126
|
+
# with a trailer (to be read from the @buffer)
|
127
|
+
@gzip_writer.close
|
128
|
+
else
|
129
|
+
# flush happens first to ensure that header fields
|
130
|
+
# are being sent over since write will override
|
131
|
+
@gzip_writer.flush
|
132
|
+
@gzip_writer.write(chunk)
|
133
|
+
end
|
134
|
+
|
135
|
+
StringIO.new(@buffer.last_chunk).read(length, buff)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# @api private
|
140
|
+
class ChunkBuffer
|
141
|
+
def initialize
|
142
|
+
@last_chunk = nil
|
143
|
+
end
|
144
|
+
|
145
|
+
attr_reader :last_chunk
|
146
|
+
|
147
|
+
def write(data)
|
148
|
+
@last_chunk = data
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'middleware/content_length'
|
4
|
+
require_relative 'middleware/content_md5'
|
5
|
+
require_relative 'middleware/request_compression'
|
6
|
+
|
7
|
+
module Hearth
|
8
|
+
module HTTP
|
9
|
+
# @api private
|
10
|
+
module Middleware; end
|
11
|
+
end
|
12
|
+
end
|
@@ -2,19 +2,6 @@
|
|
2
2
|
|
3
3
|
module Hearth
|
4
4
|
module HTTP
|
5
|
-
|
6
|
-
# a request or receiving a response. You can access the original error
|
7
|
-
# by calling {#original_error}.
|
8
|
-
class NetworkingError < StandardError
|
9
|
-
MSG = 'Encountered an error while transmitting the request: %<message>s'
|
10
|
-
|
11
|
-
def initialize(original_error)
|
12
|
-
@original_error = original_error
|
13
|
-
super(format(MSG, message: original_error.message))
|
14
|
-
end
|
15
|
-
|
16
|
-
# @return [StandardError]
|
17
|
-
attr_reader :original_error
|
18
|
-
end
|
5
|
+
class NetworkingError < Hearth::NetworkingError; end
|
19
6
|
end
|
20
7
|
end
|
data/lib/hearth/http/request.rb
CHANGED
@@ -1,83 +1,77 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'stringio'
|
4
|
-
require 'uri'
|
5
|
-
|
6
3
|
module Hearth
|
7
4
|
module HTTP
|
8
5
|
# Represents an HTTP request.
|
9
|
-
|
10
|
-
class Request
|
6
|
+
class Request < Hearth::Request
|
11
7
|
# @param [String] http_method
|
12
|
-
# @param [
|
13
|
-
# @param
|
14
|
-
|
15
|
-
|
16
|
-
body: StringIO.new)
|
8
|
+
# @param [Fields] fields
|
9
|
+
# @param (see Hearth::Request#initialize)
|
10
|
+
def initialize(http_method: nil, fields: Fields.new, **kwargs)
|
11
|
+
super(**kwargs)
|
17
12
|
@http_method = http_method
|
18
|
-
@
|
19
|
-
@headers =
|
20
|
-
@
|
13
|
+
@fields = fields
|
14
|
+
@headers = Fields::Proxy.new(@fields, :header)
|
15
|
+
@trailers = Fields::Proxy.new(@fields, :trailer)
|
21
16
|
end
|
22
17
|
|
23
18
|
# @return [String]
|
24
19
|
attr_accessor :http_method
|
25
20
|
|
26
|
-
# @return [
|
27
|
-
|
21
|
+
# @return [Fields]
|
22
|
+
attr_reader :fields
|
28
23
|
|
29
|
-
# @return [
|
30
|
-
|
24
|
+
# @return [Fields::Proxy]
|
25
|
+
attr_reader :headers
|
31
26
|
|
32
|
-
# @return [
|
33
|
-
|
27
|
+
# @return [Fields::Proxy]
|
28
|
+
attr_reader :trailers
|
34
29
|
|
35
|
-
# Append a path to the HTTP request
|
30
|
+
# Append a path to the HTTP request URI.
|
36
31
|
#
|
37
|
-
# http_req.
|
32
|
+
# http_req.uri = "https://example.com"
|
38
33
|
# http_req.append_path('/')
|
39
|
-
# http_req.
|
34
|
+
# http_req.uri.to_s
|
40
35
|
# #=> "https://example.com/"
|
41
36
|
#
|
42
37
|
# Paths will be joined by a single '/':
|
43
38
|
#
|
44
|
-
# http_req.
|
39
|
+
# http_req.uri = "https://example.com/path-prefix/"
|
45
40
|
# http_req.append_path('/path-suffix')
|
46
|
-
# http_req.
|
41
|
+
# http_req.uri.to_s
|
47
42
|
# #=> "https://example.com/path-prefix/path-suffix"
|
48
43
|
#
|
49
|
-
# Resultant
|
44
|
+
# Resultant URI preserves the querystring:
|
50
45
|
#
|
51
|
-
# http_req.
|
46
|
+
# http_req.uri = "https://example.com/path-prefix?querystring
|
52
47
|
# http_req.append_path('/path-suffix')
|
53
|
-
# http_req.
|
48
|
+
# http_req.uri.to_s
|
54
49
|
# #=> "https://example.com/path-prefix/path-suffix?querystring"
|
55
50
|
#
|
56
51
|
# The provided path should be URI escaped before being passed.
|
57
52
|
#
|
58
|
-
# http_req.
|
53
|
+
# http_req.uri = "https://example.com
|
59
54
|
# http_req.append_path(
|
60
55
|
# Hearth::HTTP.uri_escape_path('/part 1/part 2')
|
61
56
|
# )
|
62
|
-
# http_req.
|
57
|
+
# http_req.uri.to_s
|
63
58
|
# #=> "https://example.com/part%201/part%202"
|
64
59
|
#
|
65
60
|
# @param [String] path A URI escaped path.
|
61
|
+
#
|
66
62
|
def append_path(path)
|
67
|
-
uri = URI.parse(@url)
|
68
63
|
base_path = uri.path.sub(%r{/$}, '') # remove trailing slash
|
69
64
|
path = path.sub(%r{^/}, '') # remove prefix slash
|
70
65
|
uri.path = "#{base_path}/#{path}" # join on single slash
|
71
|
-
@url = uri.to_s
|
72
66
|
end
|
73
67
|
|
74
|
-
# Append querystring parameter to the HTTP request
|
68
|
+
# Append querystring parameter to the HTTP request URI.
|
75
69
|
#
|
76
|
-
# http_req.
|
70
|
+
# http_req.uri = "https://example.com"
|
77
71
|
# http_req.append_query_param('query')
|
78
72
|
# http_req.append_query_param('key 1', 'value 1')
|
79
73
|
#
|
80
|
-
# http_req.
|
74
|
+
# http_req.uri.to_s
|
81
75
|
# #=> "https://example.com?query&key%201=value%201
|
82
76
|
#
|
83
77
|
# @overload append_query_param(name)
|
@@ -93,39 +87,72 @@ module Hearth
|
|
93
87
|
# The value of the querystring parameter to add. This value
|
94
88
|
# will be URI escaped.
|
95
89
|
#
|
96
|
-
def append_query_param(
|
97
|
-
param =
|
98
|
-
|
99
|
-
when 1 then escape(args[0])
|
100
|
-
when 2 then "#{escape(args[0])}=#{escape(args[1])}"
|
101
|
-
else raise ArgumentError, 'wrong number of arguments ' \
|
102
|
-
"(given #{args.size}, expected 1 or 2)"
|
103
|
-
end
|
104
|
-
uri = URI.parse(@url)
|
105
|
-
uri.query = uri.query ? "#{uri.query}&#{param}" : param
|
106
|
-
@url = uri.to_s
|
90
|
+
def append_query_param(name, value = nil)
|
91
|
+
param = Hearth::Query::Param.new(name, value)
|
92
|
+
uri.query = uri.query ? "#{uri.query}&#{param}" : param.to_s
|
107
93
|
end
|
108
94
|
|
109
|
-
# Append
|
95
|
+
# Append querystring parameter list to the HTTP request URI.
|
96
|
+
#
|
97
|
+
# http_req.uri = "https://example.com"
|
98
|
+
# query_params = Hearth::Query::ParamList.new
|
99
|
+
# query_params['key 1'] = nil
|
100
|
+
# query_params['key 2'] = 'value 2'
|
101
|
+
# http_req.append_query_param_list(query_params)
|
110
102
|
#
|
111
|
-
# http_req.
|
103
|
+
# http_req.uri.to_s
|
104
|
+
# #=> "https://example.com?key%201=&key%202=value%202"
|
105
|
+
#
|
106
|
+
# @param [ParamList] param_list
|
107
|
+
# An instance of Hearth::Query::ParamList containing the list of
|
108
|
+
# querystring parameters to add. The names and values are URI escaped.
|
109
|
+
#
|
110
|
+
def append_query_param_list(param_list)
|
111
|
+
return if param_list.empty?
|
112
|
+
|
113
|
+
uri.query = uri.query ? "#{uri.query}&#{param_list}" : param_list.to_s
|
114
|
+
end
|
115
|
+
|
116
|
+
# Remove querystring parameter from the HTTP request URI.
|
117
|
+
#
|
118
|
+
# http_req.uri = "https://example.com"
|
119
|
+
# http_req.append_query_param('query')
|
120
|
+
# http_req.append_query_param('empty', '')
|
121
|
+
# http_req.append_query_param('deleteme', 'true')
|
122
|
+
# http_req.append_query_param('key', 'value')
|
123
|
+
# #=> "https://example.com?query&empty=&deleteme=true&key=value"
|
124
|
+
#
|
125
|
+
# http_req.remove_query_param('deleteme')
|
126
|
+
# #=> "query&empty=&key=value"
|
127
|
+
#
|
128
|
+
# http_req.uri.to_s
|
129
|
+
# #=> "https://example.com?query&empty=&key=value"
|
130
|
+
#
|
131
|
+
# @param [String] name The name of the querystring parameter to remove.
|
132
|
+
#
|
133
|
+
def remove_query_param(name)
|
134
|
+
parsed = CGI.parse(uri.query)
|
135
|
+
parsed.delete(name)
|
136
|
+
# encode_www_form ignores query params without values
|
137
|
+
# (CGI parses these as empty lists)
|
138
|
+
parsed.each do |key, values|
|
139
|
+
parsed[key] = values.empty? ? nil : values
|
140
|
+
end
|
141
|
+
uri.query = URI.encode_www_form(parsed)
|
142
|
+
end
|
143
|
+
|
144
|
+
# Append a host prefix to the HTTP request URI.
|
145
|
+
#
|
146
|
+
# http_req.uri = "https://example.com"
|
112
147
|
# http_req.prefix_host('data.')
|
113
148
|
#
|
114
|
-
# http_req.
|
149
|
+
# http_req.uri.to_s
|
115
150
|
# #=> "https://data.foo.com
|
116
151
|
#
|
117
152
|
# @param [String] prefix A dot (.) terminated prefix for the host.
|
118
153
|
#
|
119
154
|
def prefix_host(prefix)
|
120
|
-
uri = URI.parse(@url)
|
121
155
|
uri.host = prefix + uri.host
|
122
|
-
@url = uri.to_s
|
123
|
-
end
|
124
|
-
|
125
|
-
private
|
126
|
-
|
127
|
-
def escape(value)
|
128
|
-
Hearth::HTTP.uri_escape(value.to_s)
|
129
156
|
end
|
130
157
|
end
|
131
158
|
end
|
data/lib/hearth/http/response.rb
CHANGED
@@ -1,29 +1,58 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'stringio'
|
4
|
-
|
5
3
|
module Hearth
|
6
4
|
module HTTP
|
7
5
|
# Represents an HTTP Response.
|
8
|
-
|
9
|
-
class Response
|
6
|
+
class Response < Hearth::Response
|
10
7
|
# @param [Integer] status
|
11
|
-
# @param [
|
12
|
-
# @param [
|
13
|
-
|
8
|
+
# @param [String, nil] reason
|
9
|
+
# @param [Fields] fields
|
10
|
+
# @param (see Hearth::Response#initialize)
|
11
|
+
def initialize(status: 0, reason: nil, fields: Fields.new, **kwargs)
|
12
|
+
super(**kwargs)
|
14
13
|
@status = status
|
15
|
-
@
|
16
|
-
@
|
14
|
+
@reason = reason
|
15
|
+
@fields = fields
|
16
|
+
@headers = Fields::Proxy.new(@fields, :header)
|
17
|
+
@trailers = Fields::Proxy.new(@fields, :trailer)
|
17
18
|
end
|
18
19
|
|
19
20
|
# @return [Integer]
|
20
21
|
attr_accessor :status
|
21
22
|
|
22
|
-
# @return [
|
23
|
-
attr_accessor :
|
23
|
+
# @return [String, nil]
|
24
|
+
attr_accessor :reason
|
25
|
+
|
26
|
+
# @return [Fields]
|
27
|
+
attr_reader :fields
|
28
|
+
|
29
|
+
# @return [Fields::Proxy]
|
30
|
+
attr_reader :headers
|
31
|
+
|
32
|
+
# @return [Fields::Proxy]
|
33
|
+
attr_reader :trailers
|
24
34
|
|
25
|
-
#
|
26
|
-
|
35
|
+
# Replace attributes from other response
|
36
|
+
# @param [Response] other
|
37
|
+
# @return [Response]
|
38
|
+
def replace(other)
|
39
|
+
@status = other.status
|
40
|
+
@reason = other.reason
|
41
|
+
@fields = other.fields
|
42
|
+
@headers = Fields::Proxy.new(@fields, :header)
|
43
|
+
@trailers = Fields::Proxy.new(@fields, :trailer)
|
44
|
+
|
45
|
+
super
|
46
|
+
end
|
47
|
+
|
48
|
+
# Resets the HTTP response.
|
49
|
+
# @return [Response]
|
50
|
+
def reset
|
51
|
+
@status = 0
|
52
|
+
@reason = nil
|
53
|
+
@fields.clear
|
54
|
+
super
|
55
|
+
end
|
27
56
|
end
|
28
57
|
end
|
29
58
|
end
|