lib/idempo.rb in idempo-1.0.0 vs lib/idempo.rb in idempo-1.1.0
- old
+ new
@@ -1,21 +1,21 @@
# frozen_string_literal: true
-require 'zlib'
-require 'msgpack'
-require 'base64'
-require 'digest'
-require 'json'
-require 'measurometer'
+require "base64"
+require "digest"
+require "json"
+require "measurometer"
+require "msgpack"
+require "zlib"
-require_relative "idempo/version"
-require_relative "idempo/request_fingerprint"
-require_relative "idempo/memory_backend"
-require_relative "idempo/redis_backend"
require_relative "idempo/active_record_backend"
-require_relative "idempo/malformed_key_error_app"
require_relative "idempo/concurrent_request_error_app"
+require_relative "idempo/malformed_key_error_app"
+require_relative "idempo/memory_backend"
+require_relative "idempo/redis_backend"
+require_relative "idempo/request_fingerprint"
+require_relative "idempo/version"
class Idempo
DEFAULT_TTL_SECONDS = 30
SAVED_RESPONSE_BODY_SIZE_LIMIT = 4 * 1024 * 1024
@@ -35,85 +35,85 @@
end
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)
+ 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 == ''
+ 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')
+ if (stored_response = store.lookup)
+ Measurometer.increment_counter("idempo.responses_served_from", 1, from: "store")
return from_persisted_response(stored_response)
end
status, headers, body = @app.call(env)
- expires_in_seconds = (headers.delete('X-Idempo-Persist-For-Seconds') || @persist_for_seconds).to_i
+ expires_in_seconds = (headers.delete("X-Idempo-Persist-For-Seconds") || @persist_for_seconds).to_i
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')
+ 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')
+ 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')
+ Measurometer.increment_counter("idempo.responses_served_from", 1, from: "conflict-concurrent-request")
@concurrent_request_error_app.call(env)
end
private
def from_persisted_response(marshaled_response)
- if marshaled_response[-2..-1] != ':1'
+ if marshaled_response[-2..] != ":1"
raise Error, "Unknown serialization of the marshaled response"
else
MessagePack.unpack(Zlib.inflate(marshaled_response[0..-3]))
end
end
def serialize_response(status, headers, rack_response_body)
# Buffer the Rack response body, we can only do that once (it is non-rewindable)
body_chunks = []
- rack_response_body.each { |chunk| body_chunks << chunk.dup }
+ rack_response_body.each { |chunk| body_chunks << chunk.dup }
rack_response_body.close if rack_response_body.respond_to?(:close)
# Only keep headers which are strings
stringified_headers = headers.each_with_object({}) do |(header, value), filtered|
- filtered[header] = value if !header.start_with?('rack.') && value.is_a?(String)
+ filtered[header] = value if !header.start_with?("rack.") && value.is_a?(String)
end
message_packed_str = MessagePack.pack([status, stringified_headers, body_chunks])
deflated_message_packed_str = Zlib.deflate(message_packed_str) + ":1"
- Measurometer.increment_counter('idempo.response_total_generated_bytes', deflated_message_packed_str.bytesize)
- Measurometer.add_distribution_value('idempo.response_size_bytes', deflated_message_packed_str.bytesize)
+ Measurometer.increment_counter("idempo.response_total_generated_bytes", deflated_message_packed_str.bytesize)
+ Measurometer.add_distribution_value("idempo.response_size_bytes", deflated_message_packed_str.bytesize)
# Add the version specifier at the end, because slicing a string in Ruby at the end
# (when we unserialize our response again) does a realloc, while slicing at the start
# does not
[deflated_message_packed_str, body_chunks]
end
def response_may_be_persisted?(status, headers, body)
- return false if headers.delete('X-Idempo-Policy') == 'no-store'
+ return false if headers.delete("X-Idempo-Policy") == "no-store"
return false unless status_may_be_persisted?(status)
return false unless body_size_within_limit?(headers, body)
true
end
def body_size_within_limit?(response_headers, body)
- return response_headers['Content-Length'].to_i <= SAVED_RESPONSE_BODY_SIZE_LIMIT if response_headers['Content-Length']
+ return response_headers["Content-Length"].to_i <= SAVED_RESPONSE_BODY_SIZE_LIMIT if response_headers["Content-Length"]
return false unless body.is_a?(Array) # Arbitrary iterable of unknown size
sum_of_string_bytesizes(body) <= SAVED_RESPONSE_BODY_SIZE_LIMIT
end
@@ -130,10 +130,10 @@
false
end
end
def extract_idempotency_key_from(env)
- env['HTTP_IDEMPOTENCY_KEY'] || env['HTTP_X_IDEMPOTENCY_KEY']
+ env["HTTP_IDEMPOTENCY_KEY"] || env["HTTP_X_IDEMPOTENCY_KEY"]
end
def request_verb_idempotent?(request)
request.get? || request.head? || request.options?
end