conditionalget.rb 2.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
  1. require 'rack/utils'
  2. module Rack
  3. # Middleware that enables conditional GET using If-None-Match and
  4. # If-Modified-Since. The application should set either or both of the
  5. # Last-Modified or Etag response headers according to RFC 2616. When
  6. # either of the conditions is met, the response body is set to be zero
  7. # length and the response status is set to 304 Not Modified.
  8. #
  9. # Applications that defer response body generation until the body's each
  10. # message is received will avoid response body generation completely when
  11. # a conditional GET matches.
  12. #
  13. # Adapted from Michael Klishin's Merb implementation:
  14. # http://github.com/wycats/merb-core/tree/master/lib/merb-core/rack/middleware/conditional_get.rb
  15. class ConditionalGet
  16. def initialize(app)
  17. @app = app
  18. end
  19. def call(env)
  20. case env['REQUEST_METHOD']
  21. when "GET", "HEAD"
  22. status, headers, body = @app.call(env)
  23. headers = Utils::HeaderHash.new(headers)
  24. if status == 200 && fresh?(env, headers)
  25. status = 304
  26. headers.delete('Content-Type')
  27. headers.delete('Content-Length')
  28. body = []
  29. end
  30. [status, headers, body]
  31. else
  32. @app.call(env)
  33. end
  34. end
  35. private
  36. def fresh?(env, headers)
  37. modified_since = env['HTTP_IF_MODIFIED_SINCE']
  38. none_match = env['HTTP_IF_NONE_MATCH']
  39. return false unless modified_since || none_match
  40. success = true
  41. success &&= modified_since?(to_rfc2822(modified_since), headers) if modified_since
  42. success &&= etag_matches?(none_match, headers) if none_match
  43. success
  44. end
  45. def etag_matches?(none_match, headers)
  46. etag = headers['ETag'] and etag == none_match
  47. end
  48. def modified_since?(modified_since, headers)
  49. last_modified = to_rfc2822(headers['Last-Modified']) and
  50. modified_since and
  51. modified_since >= last_modified
  52. end
  53. def to_rfc2822(since)
  54. Time.rfc2822(since) rescue nil
  55. end
  56. end
  57. end