The project is in a healthy, maintained state
Generate strong and weak ETags, evaluate If-None-Match and If-Match headers, and serve 304 Not Modified responses via included Rack middleware.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies
 Project Readme

philiprehberger-etag

Tests Gem Version Last updated

ETag generation and conditional request helpers with Rack middleware

Requirements

  • Ruby >= 3.1

Installation

Add to your Gemfile:

gem "philiprehberger-etag"

Or install directly:

gem install philiprehberger-etag

Usage

require "philiprehberger/etag"

etag = Philiprehberger::Etag.generate("Hello, World!")
# => "\"dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f\""

Custom Hash Algorithm

require "philiprehberger/etag"

Philiprehberger::Etag.generate("content", algorithm: :sha256)  # default
Philiprehberger::Etag.generate("content", algorithm: :sha512)
Philiprehberger::Etag.generate("content", algorithm: :md5)
Philiprehberger::Etag.generate("content", algorithm: :sha1)

Weak ETags

require "philiprehberger/etag"

weak = Philiprehberger::Etag.weak("Hello, World!")
# => "W/\"65a8e27d8879283831b664bd8b7f0ad4\""

Conditional Request Matching

require "philiprehberger/etag"

etag = Philiprehberger::Etag.generate("content")

# Weak comparison (If-None-Match)
Philiprehberger::Etag.match?(etag, etag)           # => true
Philiprehberger::Etag.match?(etag, "*")             # => true
Philiprehberger::Etag.match?(etag, "\"other\"")     # => false

# Strong comparison (If-Match)
Philiprehberger::Etag.strong_match?(etag, etag)     # => true

Modified Detection

require "philiprehberger/etag"

etag = Philiprehberger::Etag.generate("content")

headers = { "HTTP_IF_NONE_MATCH" => etag }
Philiprehberger::Etag.modified?(etag, headers)  # => false

headers = { "HTTP_IF_NONE_MATCH" => "\"stale\"" }
Philiprehberger::Etag.modified?(etag, headers)  # => true

If-Modified-Since Support

require "philiprehberger/etag"

last_modified = Time.utc(2026, 3, 28, 12, 0, 0)

Philiprehberger::Etag.modified_since?(last_modified, "Fri, 27 Mar 2026 12:00:00 GMT")
# => true (resource is newer)

Philiprehberger::Etag.not_modified_since?(last_modified, "Sun, 29 Mar 2026 12:00:00 GMT")
# => true (resource is older)

File-Based ETags

require "philiprehberger/etag"

etag = Philiprehberger::Etag.for_file("/path/to/file.txt")
# => "\"a1b2c3...\"" (based on mtime + size, does not read content)

etag = Philiprehberger::Etag.for_file("/path/to/file.txt", algorithm: :md5)

ETag Parsing

require "philiprehberger/etag"

Philiprehberger::Etag.parse('"abc123"')
# => { weak: false, value: "abc123" }

Philiprehberger::Etag.parse('W/"abc123"')
# => { weak: true, value: "abc123" }

Philiprehberger::Etag.parse('"aaa", W/"bbb", "ccc"')
# => [{ weak: false, value: "aaa" }, { weak: true, value: "bbb" }, { weak: false, value: "ccc" }]

Rack Middleware

# config.ru
require "philiprehberger/etag"

use Philiprehberger::Etag::Middleware

run MyApp

The middleware computes a strong ETag from the raw response body before any Content-Encoding is applied, adds the ETag header, and returns 304 Not Modified with an empty body when If-None-Match matches.

API

Method Description
Etag.generate(content, algorithm: :sha256) Strong ETag using specified algorithm, returns quoted string
Etag.weak(content) Weak ETag from MD5, returns W/"..." string
Etag.match?(etag, header) Weak comparison against If-None-Match header
Etag.strong_match?(etag, header) Strong comparison against If-Match header
Etag.modified?(etag, request_headers) Check if resource is modified based on ETag headers
Etag.modified_since?(last_modified, header) Check if resource was modified after If-Modified-Since date
Etag.not_modified_since?(last_modified, header) Inverse of modified_since?
Etag.for_file(path, algorithm: :sha256) Strong ETag from file mtime and size without reading content
Etag.parse(header) Parse ETag header into {weak:, value:} hash or array of hashes
Etag::Middleware.new(app) Rack middleware for automatic ETag and 304 handling

Development

bundle install
bundle exec rspec
bundle exec rubocop

Support

If you find this project useful:

Star the repo

🐛 Report issues

💡 Suggest features

❤️ Sponsor development

🌐 All Open Source Projects

💻 GitHub Profile

🔗 LinkedIn Profile

License

MIT