Class: Idempo
- Inherits:
-
Object
- Object
- Idempo
- Defined in:
- lib/idempo.rb,
lib/idempo/version.rb
Defined Under Namespace
Modules: RequestFingerprint Classes: ActiveRecordBackend, ConcurrentRequest, ConcurrentRequestErrorApp, Error, MalformedIdempotencyKey, MalformedKeyErrorApp, MemoryBackend, MemoryLock, RedisBackend, ResponseStore
Constant Summary collapse
- DEFAULT_TTL_SECONDS =
30
- SAVED_RESPONSE_BODY_SIZE_LIMIT =
4 * 1024 * 1024
- VERSION =
"1.3.0"
Instance Method Summary collapse
- #call(env) ⇒ Object
-
#initialize(app, backend: MemoryBackend.new, malformed_key_error_app: MalformedKeyErrorApp, compute_fingerprint_via: RequestFingerprint, concurrent_request_error_app: ConcurrentRequestErrorApp, persist_for_seconds: DEFAULT_TTL_SECONDS) ⇒ Idempo
constructor
A new instance of Idempo.
Constructor Details
#initialize(app, backend: MemoryBackend.new, malformed_key_error_app: MalformedKeyErrorApp, compute_fingerprint_via: RequestFingerprint, concurrent_request_error_app: ConcurrentRequestErrorApp, persist_for_seconds: DEFAULT_TTL_SECONDS) ⇒ Idempo
Returns a new instance of Idempo.
32 33 34 35 36 37 38 39 |
# File 'lib/idempo.rb', line 32 def initialize(app, backend: MemoryBackend.new, malformed_key_error_app: MalformedKeyErrorApp, compute_fingerprint_via: RequestFingerprint, concurrent_request_error_app: ConcurrentRequestErrorApp, persist_for_seconds: DEFAULT_TTL_SECONDS) @backend = backend @app = app @concurrent_request_error_app = concurrent_request_error_app @malformed_key_error_app = malformed_key_error_app @fingerprint_calculator = compute_fingerprint_via @persist_for_seconds = persist_for_seconds.to_i end |
Instance Method Details
#call(env) ⇒ Object
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/idempo.rb', line 41 def call(env) req = Rack::Request.new(env) return @app.call(env) if request_verb_idempotent?(req) return @app.call(env) unless (idempotency_key_header = extract_idempotency_key_from(env)) # The RFC requires that the Idempotency-Key header value is enclosed in quotes idempotency_key_header_value = unquote(idempotency_key_header) raise MalformedIdempotencyKey if idempotency_key_header_value == "" request_key = @fingerprint_calculator.call(idempotency_key_header_value, req) @backend.with_idempotency_key(request_key) do |store| if (stored_response = store.lookup) Measurometer.increment_counter("idempo.responses_served_from", 1, from: "store") return from_persisted_response(stored_response) end status, raw_headers, body = @app.call(env) headers = downcase_keys(raw_headers) expires_in_seconds = (headers.delete("x-idempo-persist-for-seconds") || @persist_for_seconds).to_i # In some cases `body` could respond to to_ary. In this case, we don't need to # call .close on the body afterwards, as it is supposed to self-close as per Rack 3.0 SPEC # # @see https://github.com/rack/rack/blob/main/SPEC.rdoc#the-body- body = body.to_ary if body.respond_to?(:to_ary) if response_may_be_persisted?(status, headers, body) # Body is replaced with a cached version since a Rack response body is not rewindable marshaled_response, body = serialize_response(status, headers, body) store.store(data: marshaled_response, ttl: expires_in_seconds) end Measurometer.increment_counter("idempo.responses_served_from", 1, from: "freshly-generated") [status, headers, body] end rescue MalformedIdempotencyKey Measurometer.increment_counter("idempo.responses_served_from", 1, from: "malformed-idempotency-key") @malformed_key_error_app.call(env) rescue ConcurrentRequest Measurometer.increment_counter("idempo.responses_served_from", 1, from: "conflict-concurrent-request") @concurrent_request_error_app.call(env) end |