Module: DedupeRequests::Fingerprint
- Defined in:
- lib/dedupe_requests/fingerprint.rb
Overview
Computes a stable fingerprint for a request.
The fingerprint covers: caller_id + verb + path + query string + body. Time is NOT part of the fingerprint — the dedup window is the Redis TTL.
Constant Summary collapse
- HASHERS =
Prefer OpenSSL (uses the CPU’s SHA instructions — several times faster on large bodies), falling back to the stdlib Digest when OpenSSL is unavailable or has the algorithm disabled (e.g. MD5 under FIPS). Output is identical.
{ sha256: ["SHA256", Digest::SHA256], sha1: ["SHA1", Digest::SHA1], sha512: ["SHA512", Digest::SHA512], md5: ["MD5", Digest::MD5] }.freeze
- ALGORITHMS =
HASHERS.transform_values do |(openssl_name, digest_class)| lambda do |data| OpenSSL::Digest.hexdigest(openssl_name, data) rescue StandardError digest_class.hexdigest(data) end end.freeze
Class Method Summary collapse
- .body(request) ⇒ Object
-
.digest(parts, algorithm = :sha256) ⇒ Object
Length-prefixes each field so a value cannot “shift” across a field boundary and collide with a different set of fields.
- .for_request(request, config, caller_id: nil) ⇒ Object
- .read_and_rewind(request) ⇒ Object
- .resolve(algorithm) ⇒ Object
Class Method Details
.body(request) ⇒ Object
68 69 70 |
# File 'lib/dedupe_requests/fingerprint.rb', line 68 def body(request) request.respond_to?(:raw_post) ? request.raw_post.to_s : read_and_rewind(request).to_s end |
.digest(parts, algorithm = :sha256) ⇒ Object
Length-prefixes each field so a value cannot “shift” across a field boundary and collide with a different set of fields.
52 53 54 55 56 57 58 |
# File 'lib/dedupe_requests/fingerprint.rb', line 52 def digest(parts, algorithm = :sha256) data = Array(parts).map do |part| s = part.to_s "#{s.bytesize}:#{s}" end.join resolve(algorithm).call(data) end |
.for_request(request, config, caller_id: nil) ⇒ Object
37 38 39 40 41 42 43 44 45 46 47 48 |
# File 'lib/dedupe_requests/fingerprint.rb', line 37 def for_request(request, config, caller_id: nil) return config.fingerprint.call(request) if config.fingerprint parts = [ caller_id.to_s, request.request_method.to_s, request.path.to_s, request.query_string.to_s, body(request) ] digest(parts, config.digest) end |
.read_and_rewind(request) ⇒ Object
72 73 74 75 76 77 78 |
# File 'lib/dedupe_requests/fingerprint.rb', line 72 def read_and_rewind(request) return "" unless request.respond_to?(:body) && request.body data = request.body.read request.body.rewind if request.body.respond_to?(:rewind) data end |
.resolve(algorithm) ⇒ Object
60 61 62 63 64 65 66 |
# File 'lib/dedupe_requests/fingerprint.rb', line 60 def resolve(algorithm) return algorithm if algorithm.respond_to?(:call) ALGORITHMS.fetch(algorithm.to_sym) do raise ArgumentError, "unknown digest #{algorithm.inspect} (known: #{ALGORITHMS.keys.join(', ')} or a callable)" end end |