Ejemplo de Middleware: Rack::ETag

An ETag or entity tag, is part of HTTP, the protocol for the World Wide Web. It is one of several mechanisms that HTTP provides for web cache validation, and which allows a client to make conditional requests.

This allows caches to be more efficient, and saves bandwidth, as a web server does not need to send a full response if the content has not changed.

An ETag is an opaque identifier assigned by a web server to a specific version of a resource found at a URL.

If the resource content at that URL ever changes, a new and different ETag is assigned.

Used in this manner ETags are similar to fingerprints, and they can be quickly compared to determine if two versions of a resource are the same or not.

  1. Rack::ETag en GitHub
  2. DocumentaciĆ³n de Rack::ETag

require 'digest/md5'

module Rack
  class ETag
    DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate".freeze

    def initialize(app, no_cache_control = nil, cache_control = DEFAULT_CACHE_CONTROL)
      @app = app
      @cache_control = cache_control
      @no_cache_control = no_cache_control
    end

    def call(env)
      status, headers, body = @app.call(env)

      if etag_status?(status) && etag_body?(body) && !skip_caching?(headers)
        digest, body = digest_body(body)
        headers['ETag'] = %("#{digest}") if digest
      end

      unless headers['Cache-Control']
        if digest
          headers['Cache-Control'] = @cache_control if @cache_control
        else
          headers['Cache-Control'] = @no_cache_control if @no_cache_control
        end
      end

      [status, headers, body]
    end

    private

      def etag_status?(status)
        status == 200 || status == 201
      end

      def etag_body?(body)
        !body.respond_to?(:to_path)
      end

      def skip_caching?(headers)
        (headers['Cache-Control'] && headers['Cache-Control'].include?('no-cache')) ||
          headers.key?('ETag') || headers.key?('Last-Modified')
      end

      def digest_body(body)
        parts = []
        digest = nil

        body.each do |part|
          parts << part
          (digest ||= Digest::MD5.new) << part unless part.empty?
        end

        [digest && digest.hexdigest, parts]
      end
  end
end

Casiano Rodriguez LeĆ³n 2015-06-18