idempo 1.2.2 → 1.3.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/.github/workflows/ci.yml +3 -9
- data/Appraisals +9 -0
- data/CHANGELOG.md +4 -0
- data/Rakefile +5 -1
- data/idempo.gemspec +3 -2
- data/lib/idempo/concurrent_request_error_app.rb +1 -1
- data/lib/idempo/malformed_key_error_app.rb +1 -1
- data/lib/idempo/request_fingerprint.rb +16 -3
- data/lib/idempo/version.rb +1 -1
- data/lib/idempo.rb +14 -9
- metadata +23 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3db7fbb3c612bae5675890f118986c3d026e49c73f3c74c24f15fea51e6a67c7
|
4
|
+
data.tar.gz: 65a193261af6e424fe8f7e3437d28e02c20ddb4c9ae199c9556f39676474b8ac
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bd32562481a479070ed2806ef3201d370417fd9acfbbb8499d1aefb2f1000f278ee3dff47ccb0ac640f33001be8c41cccba7c498980d07c893dee519e53f539b
|
7
|
+
data.tar.gz: df3b05f901ac7e49bb82bfde9a522e2a179b96953db10751b7814858597a5a0af0aa82ff0fd6824e3a11cae6bdf4ebc84004a35b09cfae7a39958114a307eef8
|
data/.github/workflows/ci.yml
CHANGED
@@ -34,19 +34,13 @@ jobs:
|
|
34
34
|
- name: Standard (Lint)
|
35
35
|
run: bundle exec rake standard
|
36
36
|
test:
|
37
|
-
name: Specs
|
37
|
+
name: "Specs (Rack 2 and 3)"
|
38
38
|
runs-on: ubuntu-22.04
|
39
39
|
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
|
40
|
-
env:
|
41
|
-
IDEMPO_RACK_VERSION: ${{ matrix.rack }}
|
42
40
|
strategy:
|
43
41
|
matrix:
|
44
42
|
ruby:
|
45
43
|
- "2.7"
|
46
|
-
- "3.2"
|
47
|
-
rack:
|
48
|
-
- "2.0"
|
49
|
-
- "3.0"
|
50
44
|
services:
|
51
45
|
mysql:
|
52
46
|
image: mysql:5.7
|
@@ -76,8 +70,8 @@ jobs:
|
|
76
70
|
with:
|
77
71
|
ruby-version: ${{ matrix.ruby }}
|
78
72
|
bundler-cache: true
|
79
|
-
- name: RSpec
|
80
|
-
run: bundle exec rspec
|
73
|
+
- name: RSpec via Appraisal
|
74
|
+
run: "bundle exec appraisal install && bundle exec appraisal rspec"
|
81
75
|
env:
|
82
76
|
MYSQL_HOST: 127.0.0.1
|
83
77
|
MYSQL_PORT: 3306
|
data/Appraisals
ADDED
data/CHANGELOG.md
CHANGED
data/Rakefile
CHANGED
data/idempo.gemspec
CHANGED
@@ -29,7 +29,7 @@ Gem::Specification.new do |spec|
|
|
29
29
|
# Specify which files should be added to the gem when it is released.
|
30
30
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
31
31
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
32
|
-
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
|
32
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features|gemfiles)/}) }
|
33
33
|
end
|
34
34
|
spec.bindir = "exe"
|
35
35
|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
@@ -37,8 +37,8 @@ Gem::Specification.new do |spec|
|
|
37
37
|
|
38
38
|
spec.add_dependency "msgpack"
|
39
39
|
spec.add_dependency "measurometer", "~> 1.3"
|
40
|
+
spec.add_dependency "rack", "< 4"
|
40
41
|
|
41
|
-
spec.add_development_dependency "rack", "~> 3"
|
42
42
|
spec.add_development_dependency "rake", "~> 13.0"
|
43
43
|
spec.add_development_dependency "rspec", "~> 3.0"
|
44
44
|
spec.add_development_dependency "redis", "~> 4"
|
@@ -47,6 +47,7 @@ Gem::Specification.new do |spec|
|
|
47
47
|
spec.add_development_dependency "mysql2"
|
48
48
|
spec.add_development_dependency "pg"
|
49
49
|
spec.add_development_dependency "standard"
|
50
|
+
spec.add_development_dependency "appraisal"
|
50
51
|
|
51
52
|
# For more information and examples about making a new gem, checkout our
|
52
53
|
# guide at: https://bundler.io/guides/creating_gem.html
|
@@ -8,6 +8,6 @@ class Idempo::ConcurrentRequestErrorApp
|
|
8
8
|
message: "Another request with this idempotency key is still in progress, please try again later"
|
9
9
|
}
|
10
10
|
}
|
11
|
-
[429, {"
|
11
|
+
[429, {"retry-after" => RETRY_AFTER_SECONDS, "content-type" => "application/json"}, [JSON.pretty_generate(res)]]
|
12
12
|
end
|
13
13
|
end
|
@@ -6,6 +6,6 @@ class Idempo::MalformedKeyErrorApp
|
|
6
6
|
message: "The Idempotency-Key header provided was empty or malformed"
|
7
7
|
}
|
8
8
|
}
|
9
|
-
[400, {"
|
9
|
+
[400, {"content-type" => "application/json"}, [JSON.pretty_generate(res)]]
|
10
10
|
end
|
11
11
|
end
|
@@ -5,11 +5,24 @@ module Idempo::RequestFingerprint
|
|
5
5
|
d << rack_request.url << "\n"
|
6
6
|
d << rack_request.request_method << "\n"
|
7
7
|
d << rack_request.get_header("HTTP_AUTHORIZATION").to_s << "\n"
|
8
|
-
|
9
|
-
|
8
|
+
|
9
|
+
# Under Rack 3.0 the rack.input may or may not be rewindable (this is done to support
|
10
|
+
# streaming HTTP request bodies). If we know a request body is rewindable we can read it
|
11
|
+
# out in full and add it to the request fingerprint. If the request body cannot be
|
12
|
+
# rewound, we can't really rely on it as it can be fairly large (and we want the
|
13
|
+
# downstream app to read the request body, not us).
|
14
|
+
if rack_request.env["rack.input"].respond_to?(:rewind)
|
15
|
+
read_and_rewind(rack_request.env["rack.input"], d)
|
10
16
|
end
|
17
|
+
|
11
18
|
Base64.strict_encode64(d.digest)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.read_and_rewind(source_io, to_destination_io)
|
22
|
+
while (chunk = source_io.read(1024 * 65))
|
23
|
+
to_destination_io << chunk
|
24
|
+
end
|
12
25
|
ensure
|
13
|
-
|
26
|
+
source_io.rewind
|
14
27
|
end
|
15
28
|
end
|
data/lib/idempo/version.rb
CHANGED
data/lib/idempo.rb
CHANGED
@@ -55,14 +55,16 @@ class Idempo
|
|
55
55
|
return from_persisted_response(stored_response)
|
56
56
|
end
|
57
57
|
|
58
|
-
status,
|
58
|
+
status, raw_headers, body = @app.call(env)
|
59
|
+
headers = downcase_keys(raw_headers)
|
59
60
|
|
60
|
-
expires_in_seconds = (headers.delete("
|
61
|
+
expires_in_seconds = (headers.delete("x-idempo-persist-for-seconds") || @persist_for_seconds).to_i
|
61
62
|
|
62
|
-
# In some cases `body` could respond to to_ary. In this case, we don't need to
|
63
|
+
# In some cases `body` could respond to to_ary. In this case, we don't need to
|
64
|
+
# call .close on the body afterwards, as it is supposed to self-close as per Rack 3.0 SPEC
|
63
65
|
#
|
64
66
|
# @see https://github.com/rack/rack/blob/main/SPEC.rdoc#the-body-
|
65
|
-
body = body.to_ary if
|
67
|
+
body = body.to_ary if body.respond_to?(:to_ary)
|
66
68
|
|
67
69
|
if response_may_be_persisted?(status, headers, body)
|
68
70
|
# Body is replaced with a cached version since a Rack response body is not rewindable
|
@@ -83,8 +85,10 @@ class Idempo
|
|
83
85
|
|
84
86
|
private
|
85
87
|
|
86
|
-
def
|
87
|
-
|
88
|
+
def downcase_keys(raw_headers)
|
89
|
+
raw_headers.each_with_object({}) do |(name, value), hh|
|
90
|
+
hh[name.to_s.downcase] = value
|
91
|
+
end
|
88
92
|
end
|
89
93
|
|
90
94
|
def from_persisted_response(marshaled_response)
|
@@ -120,17 +124,18 @@ class Idempo
|
|
120
124
|
end
|
121
125
|
|
122
126
|
def response_may_be_persisted?(status, headers, body)
|
123
|
-
return false if headers.delete("
|
127
|
+
return false if headers.delete("x-idempo-policy") == "no-store"
|
124
128
|
return false unless status_may_be_persisted?(status)
|
125
129
|
return false unless body_size_within_limit?(headers, body)
|
126
130
|
true
|
127
131
|
end
|
128
132
|
|
129
133
|
def body_size_within_limit?(response_headers, body)
|
130
|
-
|
134
|
+
if response_headers["content-length"]
|
135
|
+
return response_headers["content-length"].to_i <= SAVED_RESPONSE_BODY_SIZE_LIMIT
|
136
|
+
end
|
131
137
|
|
132
138
|
return false unless body.is_a?(Array) # Arbitrary iterable of unknown size
|
133
|
-
|
134
139
|
sum_of_string_bytesizes(body) <= SAVED_RESPONSE_BODY_SIZE_LIMIT
|
135
140
|
end
|
136
141
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: idempo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Julik Tarkhanov
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date: 2024-
|
12
|
+
date: 2024-11-29 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: msgpack
|
@@ -43,16 +43,16 @@ dependencies:
|
|
43
43
|
name: rack
|
44
44
|
requirement: !ruby/object:Gem::Requirement
|
45
45
|
requirements:
|
46
|
-
- - "
|
46
|
+
- - "<"
|
47
47
|
- !ruby/object:Gem::Version
|
48
|
-
version: '
|
49
|
-
type: :
|
48
|
+
version: '4'
|
49
|
+
type: :runtime
|
50
50
|
prerelease: false
|
51
51
|
version_requirements: !ruby/object:Gem::Requirement
|
52
52
|
requirements:
|
53
|
-
- - "
|
53
|
+
- - "<"
|
54
54
|
- !ruby/object:Gem::Version
|
55
|
-
version: '
|
55
|
+
version: '4'
|
56
56
|
- !ruby/object:Gem::Dependency
|
57
57
|
name: rake
|
58
58
|
requirement: !ruby/object:Gem::Requirement
|
@@ -165,6 +165,20 @@ dependencies:
|
|
165
165
|
- - ">="
|
166
166
|
- !ruby/object:Gem::Version
|
167
167
|
version: '0'
|
168
|
+
- !ruby/object:Gem::Dependency
|
169
|
+
name: appraisal
|
170
|
+
requirement: !ruby/object:Gem::Requirement
|
171
|
+
requirements:
|
172
|
+
- - ">="
|
173
|
+
- !ruby/object:Gem::Version
|
174
|
+
version: '0'
|
175
|
+
type: :development
|
176
|
+
prerelease: false
|
177
|
+
version_requirements: !ruby/object:Gem::Requirement
|
178
|
+
requirements:
|
179
|
+
- - ">="
|
180
|
+
- !ruby/object:Gem::Version
|
181
|
+
version: '0'
|
168
182
|
description: Provides idempotency keys for Rack applications.
|
169
183
|
email:
|
170
184
|
- me@julik.nl
|
@@ -177,6 +191,7 @@ files:
|
|
177
191
|
- ".gitignore"
|
178
192
|
- ".rubocop.yml"
|
179
193
|
- ".standard.yml"
|
194
|
+
- Appraisals
|
180
195
|
- CHANGELOG.md
|
181
196
|
- Gemfile
|
182
197
|
- LICENSE.txt
|
@@ -220,7 +235,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
220
235
|
- !ruby/object:Gem::Version
|
221
236
|
version: '0'
|
222
237
|
requirements: []
|
223
|
-
rubygems_version: 3.
|
238
|
+
rubygems_version: 3.1.6
|
224
239
|
signing_key:
|
225
240
|
specification_version: 4
|
226
241
|
summary: Idempotency keys for all.
|