response_bank 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
 - data/LICENSE.txt +20 -0
 - data/README.md +91 -0
 - data/lib/response_bank.rb +72 -0
 - data/lib/response_bank/controller.rb +67 -0
 - data/lib/response_bank/middleware.rb +88 -0
 - data/lib/response_bank/model_extensions.rb +19 -0
 - data/lib/response_bank/railtie.rb +20 -0
 - data/lib/response_bank/response_cache_handler.rb +176 -0
 - data/lib/response_bank/version.rb +4 -0
 - metadata +152 -0
 
    
        checksums.yaml
    ADDED
    
    | 
         @@ -0,0 +1,7 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            ---
         
     | 
| 
      
 2 
     | 
    
         
            +
            SHA256:
         
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 22412918297172ba47f51811bb250ace6dbb2b3d8eb26405f0f970a18b2479de
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 94022c0b2e796e9a0a8a0fcc47f7f4ba40f64118458695554683d21452009388
         
     | 
| 
      
 5 
     | 
    
         
            +
            SHA512:
         
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: eb15b72956d34a954d829f1ac5666568e5b0c1b4eeebd7a127d8f88b5d103efbd856fe0d4466c675826868a2e8fb8fd572004891753a0bf1799ba3bc04a1cc81
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 86ac266abcbe74dbf7fb72c833def9283fdd5943c0666fc1bf6dbed34008161c3458d85f1d365f7b8d6aacdd4af1a5b8822bc792c33de094668490d6108d4a87
         
     | 
    
        data/LICENSE.txt
    ADDED
    
    | 
         @@ -0,0 +1,20 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            Copyright (c) 2008-2019 Tobias Lütke
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            Permission is hereby granted, free of charge, to any person obtaining
         
     | 
| 
      
 4 
     | 
    
         
            +
            a copy of this software and associated documentation files (the
         
     | 
| 
      
 5 
     | 
    
         
            +
            "Software"), to deal in the Software without restriction, including
         
     | 
| 
      
 6 
     | 
    
         
            +
            without limitation the rights to use, copy, modify, merge, publish,
         
     | 
| 
      
 7 
     | 
    
         
            +
            distribute, sublicense, and/or sell copies of the Software, and to
         
     | 
| 
      
 8 
     | 
    
         
            +
            permit persons to whom the Software is furnished to do so, subject to
         
     | 
| 
      
 9 
     | 
    
         
            +
            the following conditions:
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            The above copyright notice and this permission notice shall be
         
     | 
| 
      
 12 
     | 
    
         
            +
            included in all copies or substantial portions of the Software.
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
         
     | 
| 
      
 15 
     | 
    
         
            +
            EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
         
     | 
| 
      
 16 
     | 
    
         
            +
            MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
         
     | 
| 
      
 17 
     | 
    
         
            +
            NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
         
     | 
| 
      
 18 
     | 
    
         
            +
            LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
         
     | 
| 
      
 19 
     | 
    
         
            +
            OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
         
     | 
| 
      
 20 
     | 
    
         
            +
            WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
         
     | 
    
        data/README.md
    ADDED
    
    | 
         @@ -0,0 +1,91 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # ResponseBank [](http://travis-ci.org/Shopify/response_bank)
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            ### Features
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            * Serve gzip'd content
         
     | 
| 
      
 6 
     | 
    
         
            +
            * Add ETag and 304 Not Modified headers
         
     | 
| 
      
 7 
     | 
    
         
            +
            * Generational caching
         
     | 
| 
      
 8 
     | 
    
         
            +
            * No explicit expiry
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            ### Support
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            This gem supports the following versions of Ruby and Rails:
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            * Ruby 2.4.0+
         
     | 
| 
      
 15 
     | 
    
         
            +
            * Rails 5.0.0+
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
            ### Usage
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
            1. include the gem in your Gemfile
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 22 
     | 
    
         
            +
            gem 'response_bank'
         
     | 
| 
      
 23 
     | 
    
         
            +
            ```
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
            2. use `#response_cache` method to any desired controller's action
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 28 
     | 
    
         
            +
            class PostsController < ApplicationController
         
     | 
| 
      
 29 
     | 
    
         
            +
              def show
         
     | 
| 
      
 30 
     | 
    
         
            +
                response_cache do
         
     | 
| 
      
 31 
     | 
    
         
            +
                  @post = @shop.posts.find(params[:id])
         
     | 
| 
      
 32 
     | 
    
         
            +
                  respond_with(@post)
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
              end
         
     | 
| 
      
 35 
     | 
    
         
            +
            end
         
     | 
| 
      
 36 
     | 
    
         
            +
            ```
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
            3. **(optional)** override custom cache key data. For default, cache key is defined by URL and query string
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 41 
     | 
    
         
            +
            class PostsController < ApplicationController
         
     | 
| 
      
 42 
     | 
    
         
            +
              before_action :set_shop
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
              def index
         
     | 
| 
      
 45 
     | 
    
         
            +
                response_cache do
         
     | 
| 
      
 46 
     | 
    
         
            +
                  @post = @shop.posts
         
     | 
| 
      
 47 
     | 
    
         
            +
                  respond_with(@post)
         
     | 
| 
      
 48 
     | 
    
         
            +
                end
         
     | 
| 
      
 49 
     | 
    
         
            +
              end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
              def show
         
     | 
| 
      
 52 
     | 
    
         
            +
                response_cache do
         
     | 
| 
      
 53 
     | 
    
         
            +
                  @post = @shop.posts.find(params[:id])
         
     | 
| 
      
 54 
     | 
    
         
            +
                  respond_with(@post)
         
     | 
| 
      
 55 
     | 
    
         
            +
                end
         
     | 
| 
      
 56 
     | 
    
         
            +
              end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
              def another_action
         
     | 
| 
      
 59 
     | 
    
         
            +
                # custom cache key data
         
     | 
| 
      
 60 
     | 
    
         
            +
                cache_key = {
         
     | 
| 
      
 61 
     | 
    
         
            +
                  action: action_name,
         
     | 
| 
      
 62 
     | 
    
         
            +
                  format: request.format,
         
     | 
| 
      
 63 
     | 
    
         
            +
                  shop_updated_at: @shop.updated_at
         
     | 
| 
      
 64 
     | 
    
         
            +
                  # you may add more keys here
         
     | 
| 
      
 65 
     | 
    
         
            +
                }
         
     | 
| 
      
 66 
     | 
    
         
            +
                response_cache cache_key do
         
     | 
| 
      
 67 
     | 
    
         
            +
                  @post = @shop.posts.find(params[:id])
         
     | 
| 
      
 68 
     | 
    
         
            +
                  respond_with(@post)
         
     | 
| 
      
 69 
     | 
    
         
            +
                end
         
     | 
| 
      
 70 
     | 
    
         
            +
              end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
              # override default cache key data globally per class
         
     | 
| 
      
 73 
     | 
    
         
            +
              def cache_key_data
         
     | 
| 
      
 74 
     | 
    
         
            +
                {
         
     | 
| 
      
 75 
     | 
    
         
            +
                  action: action_name,
         
     | 
| 
      
 76 
     | 
    
         
            +
                  format: request.format,
         
     | 
| 
      
 77 
     | 
    
         
            +
                  params: params.slice(:id),
         
     | 
| 
      
 78 
     | 
    
         
            +
                  shop_version: @shop.version
         
     | 
| 
      
 79 
     | 
    
         
            +
                  # you may add more keys here
         
     | 
| 
      
 80 
     | 
    
         
            +
                 }
         
     | 
| 
      
 81 
     | 
    
         
            +
              end
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
              def set_shop
         
     | 
| 
      
 84 
     | 
    
         
            +
                # @shop = ...
         
     | 
| 
      
 85 
     | 
    
         
            +
              end
         
     | 
| 
      
 86 
     | 
    
         
            +
            end
         
     | 
| 
      
 87 
     | 
    
         
            +
            ```
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
            ### License
         
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
      
 91 
     | 
    
         
            +
            ResponseBank is released under the [MIT License](LICENSE.txt).
         
     | 
| 
         @@ -0,0 +1,72 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'response_bank/middleware'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'response_bank/railtie' if defined?(Rails)
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'response_bank/response_cache_handler'
         
     | 
| 
      
 5 
     | 
    
         
            +
            require 'msgpack'
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            module ResponseBank
         
     | 
| 
      
 8 
     | 
    
         
            +
              class << self
         
     | 
| 
      
 9 
     | 
    
         
            +
                attr_accessor :cache_store
         
     | 
| 
      
 10 
     | 
    
         
            +
                attr_writer :logger
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                def log(message)
         
     | 
| 
      
 13 
     | 
    
         
            +
                  @logger.info("[ResponseBank] #{message}")
         
     | 
| 
      
 14 
     | 
    
         
            +
                end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                def acquire_lock(_cache_key)
         
     | 
| 
      
 17 
     | 
    
         
            +
                  raise NotImplementedError, "Override ResponseBank.acquire_lock in an initializer."
         
     | 
| 
      
 18 
     | 
    
         
            +
                end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                def write_to_cache(_key)
         
     | 
| 
      
 21 
     | 
    
         
            +
                  yield
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                def compress(content)
         
     | 
| 
      
 25 
     | 
    
         
            +
                  io = StringIO.new
         
     | 
| 
      
 26 
     | 
    
         
            +
                  gz = Zlib::GzipWriter.new(io)
         
     | 
| 
      
 27 
     | 
    
         
            +
                  gz.write(content)
         
     | 
| 
      
 28 
     | 
    
         
            +
                  io.string
         
     | 
| 
      
 29 
     | 
    
         
            +
                ensure
         
     | 
| 
      
 30 
     | 
    
         
            +
                  gz.close
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                def decompress(content)
         
     | 
| 
      
 34 
     | 
    
         
            +
                  Zlib::GzipReader.new(StringIO.new(content)).read
         
     | 
| 
      
 35 
     | 
    
         
            +
                end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                def cache_key_for(data)
         
     | 
| 
      
 38 
     | 
    
         
            +
                  case data
         
     | 
| 
      
 39 
     | 
    
         
            +
                  when Hash
         
     | 
| 
      
 40 
     | 
    
         
            +
                    return data.inspect unless data.key?(:key)
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                    key = hash_value_str(data[:key])
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                    return key unless data.key?(:version)
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                    version = hash_value_str(data[:version])
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                    [key, version].join(":")
         
     | 
| 
      
 49 
     | 
    
         
            +
                  when Array
         
     | 
| 
      
 50 
     | 
    
         
            +
                    data.inspect
         
     | 
| 
      
 51 
     | 
    
         
            +
                  when Time, DateTime
         
     | 
| 
      
 52 
     | 
    
         
            +
                    data.to_i
         
     | 
| 
      
 53 
     | 
    
         
            +
                  when Date
         
     | 
| 
      
 54 
     | 
    
         
            +
                    data.to_time.to_i
         
     | 
| 
      
 55 
     | 
    
         
            +
                  when true, false, Integer, Symbol, String
         
     | 
| 
      
 56 
     | 
    
         
            +
                    data.inspect
         
     | 
| 
      
 57 
     | 
    
         
            +
                  else
         
     | 
| 
      
 58 
     | 
    
         
            +
                    data.to_s.inspect
         
     | 
| 
      
 59 
     | 
    
         
            +
                  end
         
     | 
| 
      
 60 
     | 
    
         
            +
                end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                private
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                def hash_value_str(data)
         
     | 
| 
      
 65 
     | 
    
         
            +
                  if data.is_a?(Hash)
         
     | 
| 
      
 66 
     | 
    
         
            +
                    data.values.join(",")
         
     | 
| 
      
 67 
     | 
    
         
            +
                  else
         
     | 
| 
      
 68 
     | 
    
         
            +
                    data.to_s
         
     | 
| 
      
 69 
     | 
    
         
            +
                  end
         
     | 
| 
      
 70 
     | 
    
         
            +
                end
         
     | 
| 
      
 71 
     | 
    
         
            +
              end
         
     | 
| 
      
 72 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,67 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
            module ResponseBank
         
     | 
| 
      
 3 
     | 
    
         
            +
              module Controller
         
     | 
| 
      
 4 
     | 
    
         
            +
                private
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
                # Only get? and head? requests should be cached.
         
     | 
| 
      
 7 
     | 
    
         
            +
                def cacheable_request?
         
     | 
| 
      
 8 
     | 
    
         
            +
                  (request.get? || request.head?) && (request.params[:cache] != 'false')
         
     | 
| 
      
 9 
     | 
    
         
            +
                end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                # Override this method with additional information that changes to invalidate the cache.
         
     | 
| 
      
 12 
     | 
    
         
            +
                def cache_version_data
         
     | 
| 
      
 13 
     | 
    
         
            +
                  {}
         
     | 
| 
      
 14 
     | 
    
         
            +
                end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                def cache_key_data
         
     | 
| 
      
 17 
     | 
    
         
            +
                  { 'request' => { 'env' => request.env.slice('PATH_INFO', 'QUERY_STRING') } }
         
     | 
| 
      
 18 
     | 
    
         
            +
                end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                def force_refill_cache?
         
     | 
| 
      
 21 
     | 
    
         
            +
                  params[:fill_cache] == "true"
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                def serve_unversioned_cacheable_entry?
         
     | 
| 
      
 25 
     | 
    
         
            +
                  false
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                # If you're okay with serving pages that are not at the newest version, bump this up
         
     | 
| 
      
 29 
     | 
    
         
            +
                # to whatever number of seconds you're comfortable with.
         
     | 
| 
      
 30 
     | 
    
         
            +
                def cache_age_tolerance_in_seconds
         
     | 
| 
      
 31 
     | 
    
         
            +
                  0
         
     | 
| 
      
 32 
     | 
    
         
            +
                end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                def response_cache(key_data = nil, version_data = nil, &block)
         
     | 
| 
      
 35 
     | 
    
         
            +
                  cacheable_req = cacheable_request?
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                  unless cache_configured? && cacheable_req
         
     | 
| 
      
 38 
     | 
    
         
            +
                    ResponseBank.log("Uncacheable request. cache_configured='#{!!cache_configured?}'" \
         
     | 
| 
      
 39 
     | 
    
         
            +
                        " cacheable_request='#{cacheable_req}' params_cache='#{request.params[:cache] != 'false'}'")
         
     | 
| 
      
 40 
     | 
    
         
            +
                    response.headers['Cache-Control'] = 'no-cache, no-store' unless cacheable_req
         
     | 
| 
      
 41 
     | 
    
         
            +
                    return yield
         
     | 
| 
      
 42 
     | 
    
         
            +
                  end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                  handler = ResponseBank::ResponseCacheHandler.new(
         
     | 
| 
      
 45 
     | 
    
         
            +
                    key_data: key_data || cache_key_data,
         
     | 
| 
      
 46 
     | 
    
         
            +
                    version_data: version_data || cache_version_data,
         
     | 
| 
      
 47 
     | 
    
         
            +
                    env: request.env,
         
     | 
| 
      
 48 
     | 
    
         
            +
                    cache_age_tolerance: cache_age_tolerance_in_seconds,
         
     | 
| 
      
 49 
     | 
    
         
            +
                    serve_unversioned: serve_unversioned_cacheable_entry?,
         
     | 
| 
      
 50 
     | 
    
         
            +
                    force_refill_cache: force_refill_cache?,
         
     | 
| 
      
 51 
     | 
    
         
            +
                    headers: response.headers,
         
     | 
| 
      
 52 
     | 
    
         
            +
                    &block
         
     | 
| 
      
 53 
     | 
    
         
            +
                  )
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                  status, _headers, body = handler.run!
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                  return if request.env['cacheable.miss']
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                  case request.env['cacheable.store']
         
     | 
| 
      
 60 
     | 
    
         
            +
                  when 'server'
         
     | 
| 
      
 61 
     | 
    
         
            +
                    render(status: status, plain: body.first)
         
     | 
| 
      
 62 
     | 
    
         
            +
                  when 'client'
         
     | 
| 
      
 63 
     | 
    
         
            +
                    head(:not_modified)
         
     | 
| 
      
 64 
     | 
    
         
            +
                  end
         
     | 
| 
      
 65 
     | 
    
         
            +
                end
         
     | 
| 
      
 66 
     | 
    
         
            +
              end
         
     | 
| 
      
 67 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,88 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'useragent'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module ResponseBank
         
     | 
| 
      
 5 
     | 
    
         
            +
              class Middleware
         
     | 
| 
      
 6 
     | 
    
         
            +
                REQUESTED_WITH = "HTTP_X_REQUESTED_WITH"
         
     | 
| 
      
 7 
     | 
    
         
            +
                ACCEPT = "HTTP_ACCEPT"
         
     | 
| 
      
 8 
     | 
    
         
            +
                USER_AGENT = "HTTP_USER_AGENT"
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                def initialize(app)
         
     | 
| 
      
 11 
     | 
    
         
            +
                  @app = app
         
     | 
| 
      
 12 
     | 
    
         
            +
                end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                def call(env)
         
     | 
| 
      
 15 
     | 
    
         
            +
                  env['cacheable.cache'] = false
         
     | 
| 
      
 16 
     | 
    
         
            +
                  gzip = env['gzip'] = env['HTTP_ACCEPT_ENCODING'].to_s.include?("gzip")
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                  status, headers, body = @app.call(env)
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                  if env['cacheable.cache']
         
     | 
| 
      
 21 
     | 
    
         
            +
                    if [200, 404, 301, 304].include?(status)
         
     | 
| 
      
 22 
     | 
    
         
            +
                      headers['ETag'] = env['cacheable.key']
         
     | 
| 
      
 23 
     | 
    
         
            +
                      headers['X-Alternate-Cache-Key'] = env['cacheable.unversioned-key']
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                      if ie_ajax_request?(env)
         
     | 
| 
      
 26 
     | 
    
         
            +
                        headers["Expires"] = "-1"
         
     | 
| 
      
 27 
     | 
    
         
            +
                      end
         
     | 
| 
      
 28 
     | 
    
         
            +
                    end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                    if [200, 404, 301].include?(status) && env['cacheable.miss']
         
     | 
| 
      
 31 
     | 
    
         
            +
                      # Flatten down the result so that it can be stored to memcached.
         
     | 
| 
      
 32 
     | 
    
         
            +
                      if body.is_a?(String)
         
     | 
| 
      
 33 
     | 
    
         
            +
                        body_string = body
         
     | 
| 
      
 34 
     | 
    
         
            +
                      else
         
     | 
| 
      
 35 
     | 
    
         
            +
                        body_string = +""
         
     | 
| 
      
 36 
     | 
    
         
            +
                        body.each { |part| body_string << part }
         
     | 
| 
      
 37 
     | 
    
         
            +
                      end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                      body_gz = ResponseBank.compress(body_string)
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                      # Store result
         
     | 
| 
      
 42 
     | 
    
         
            +
                      cache_data = [status, headers['Content-Type'], body_gz, timestamp]
         
     | 
| 
      
 43 
     | 
    
         
            +
                      cache_data << headers['Location'] if status == 301
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                      ResponseBank.write_to_cache(env['cacheable.key']) do
         
     | 
| 
      
 46 
     | 
    
         
            +
                        payload = MessagePack.dump(cache_data)
         
     | 
| 
      
 47 
     | 
    
         
            +
                        ResponseBank.cache_store.write(env['cacheable.key'], payload, raw: true)
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                        if env['cacheable.unversioned-key']
         
     | 
| 
      
 50 
     | 
    
         
            +
                          ResponseBank.cache_store.write(env['cacheable.unversioned-key'], payload, raw: true)
         
     | 
| 
      
 51 
     | 
    
         
            +
                        end
         
     | 
| 
      
 52 
     | 
    
         
            +
                      end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                      # since we had to generate the gz version above already we may
         
     | 
| 
      
 55 
     | 
    
         
            +
                      # as well serve it if the client wants it
         
     | 
| 
      
 56 
     | 
    
         
            +
                      if gzip
         
     | 
| 
      
 57 
     | 
    
         
            +
                        headers['Content-Encoding'] = "gzip"
         
     | 
| 
      
 58 
     | 
    
         
            +
                        body = [body_gz]
         
     | 
| 
      
 59 
     | 
    
         
            +
                      end
         
     | 
| 
      
 60 
     | 
    
         
            +
                    end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                    # Add X-Cache header
         
     | 
| 
      
 63 
     | 
    
         
            +
                    miss = env['cacheable.miss']
         
     | 
| 
      
 64 
     | 
    
         
            +
                    x_cache = miss ? 'miss' : 'hit'
         
     | 
| 
      
 65 
     | 
    
         
            +
                    x_cache += ", #{env['cacheable.store']}" unless miss
         
     | 
| 
      
 66 
     | 
    
         
            +
                    headers['X-Cache'] = x_cache
         
     | 
| 
      
 67 
     | 
    
         
            +
                  end
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                  [status, headers, body]
         
     | 
| 
      
 70 
     | 
    
         
            +
                end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                private
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                def timestamp
         
     | 
| 
      
 75 
     | 
    
         
            +
                  Time.now.to_i
         
     | 
| 
      
 76 
     | 
    
         
            +
                end
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
                def ie_ajax_request?(env)
         
     | 
| 
      
 79 
     | 
    
         
            +
                  return false unless !env[USER_AGENT].nil? && !env[USER_AGENT].empty?
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
                  if env[REQUESTED_WITH] == "XmlHttpRequest" || env[ACCEPT] == "application/json"
         
     | 
| 
      
 82 
     | 
    
         
            +
                    UserAgent.parse(env["HTTP_USER_AGENT"]).is_a?(UserAgent::Browsers::InternetExplorer)
         
     | 
| 
      
 83 
     | 
    
         
            +
                  else
         
     | 
| 
      
 84 
     | 
    
         
            +
                    false
         
     | 
| 
      
 85 
     | 
    
         
            +
                  end
         
     | 
| 
      
 86 
     | 
    
         
            +
                end
         
     | 
| 
      
 87 
     | 
    
         
            +
              end
         
     | 
| 
      
 88 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,19 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
            module ResponseBank
         
     | 
| 
      
 3 
     | 
    
         
            +
              module ModelExtensions
         
     | 
| 
      
 4 
     | 
    
         
            +
                def self.included(base)
         
     | 
| 
      
 5 
     | 
    
         
            +
                  super
         
     | 
| 
      
 6 
     | 
    
         
            +
                  base.extend(ClassMethods)
         
     | 
| 
      
 7 
     | 
    
         
            +
                end
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                module ClassMethods
         
     | 
| 
      
 10 
     | 
    
         
            +
                  def cache_store
         
     | 
| 
      
 11 
     | 
    
         
            +
                    ActionController::Base.cache_store
         
     | 
| 
      
 12 
     | 
    
         
            +
                  end
         
     | 
| 
      
 13 
     | 
    
         
            +
                end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                def cache_store
         
     | 
| 
      
 16 
     | 
    
         
            +
                  ActionController::Base.cache_store
         
     | 
| 
      
 17 
     | 
    
         
            +
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
              end
         
     | 
| 
      
 19 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,20 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'rails'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'response_bank/controller'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'response_bank/model_extensions'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            module ResponseBank
         
     | 
| 
      
 7 
     | 
    
         
            +
              class Railtie < ::Rails::Railtie
         
     | 
| 
      
 8 
     | 
    
         
            +
                initializer "cachable.configure_active_record" do |config|
         
     | 
| 
      
 9 
     | 
    
         
            +
                  config.middleware.insert_after(Rack::Head, ResponseBank::Middleware)
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                  ActiveSupport.on_load(:action_controller) do
         
     | 
| 
      
 12 
     | 
    
         
            +
                    include ResponseBank::Controller
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  ActiveSupport.on_load(:active_record) do
         
     | 
| 
      
 16 
     | 
    
         
            +
                    include ResponseBank::ModelExtensions
         
     | 
| 
      
 17 
     | 
    
         
            +
                  end
         
     | 
| 
      
 18 
     | 
    
         
            +
                end
         
     | 
| 
      
 19 
     | 
    
         
            +
              end
         
     | 
| 
      
 20 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,176 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'digest/md5'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module ResponseBank
         
     | 
| 
      
 5 
     | 
    
         
            +
              class ResponseCacheHandler
         
     | 
| 
      
 6 
     | 
    
         
            +
                def initialize(
         
     | 
| 
      
 7 
     | 
    
         
            +
                  key_data:,
         
     | 
| 
      
 8 
     | 
    
         
            +
                  version_data:,
         
     | 
| 
      
 9 
     | 
    
         
            +
                  env:,
         
     | 
| 
      
 10 
     | 
    
         
            +
                  cache_age_tolerance:,
         
     | 
| 
      
 11 
     | 
    
         
            +
                  serve_unversioned:,
         
     | 
| 
      
 12 
     | 
    
         
            +
                  headers:,
         
     | 
| 
      
 13 
     | 
    
         
            +
                  force_refill_cache: false,
         
     | 
| 
      
 14 
     | 
    
         
            +
                  cache_store: ResponseBank.cache_store,
         
     | 
| 
      
 15 
     | 
    
         
            +
                  &block
         
     | 
| 
      
 16 
     | 
    
         
            +
                )
         
     | 
| 
      
 17 
     | 
    
         
            +
                  @cache_miss_block = block
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                  @key_data = key_data
         
     | 
| 
      
 20 
     | 
    
         
            +
                  @version_data = version_data
         
     | 
| 
      
 21 
     | 
    
         
            +
                  @env = env
         
     | 
| 
      
 22 
     | 
    
         
            +
                  @cache_age_tolerance = cache_age_tolerance
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                  @serve_unversioned = serve_unversioned
         
     | 
| 
      
 25 
     | 
    
         
            +
                  @force_refill_cache = force_refill_cache
         
     | 
| 
      
 26 
     | 
    
         
            +
                  @cache_store = cache_store
         
     | 
| 
      
 27 
     | 
    
         
            +
                  @headers = headers || {}
         
     | 
| 
      
 28 
     | 
    
         
            +
                end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                def run!
         
     | 
| 
      
 31 
     | 
    
         
            +
                  @env['cacheable.cache']           = true
         
     | 
| 
      
 32 
     | 
    
         
            +
                  @env['cacheable.key']             = versioned_key_hash
         
     | 
| 
      
 33 
     | 
    
         
            +
                  @env['cacheable.unversioned-key'] = unversioned_key_hash
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                  ResponseBank.log(cacheable_info_dump)
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                  if @force_refill_cache
         
     | 
| 
      
 38 
     | 
    
         
            +
                    refill_cache
         
     | 
| 
      
 39 
     | 
    
         
            +
                  else
         
     | 
| 
      
 40 
     | 
    
         
            +
                    try_to_serve_from_cache
         
     | 
| 
      
 41 
     | 
    
         
            +
                  end
         
     | 
| 
      
 42 
     | 
    
         
            +
                end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                def versioned_key_hash
         
     | 
| 
      
 45 
     | 
    
         
            +
                  @versioned_key_hash ||= key_hash(versioned_key)
         
     | 
| 
      
 46 
     | 
    
         
            +
                end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                def unversioned_key_hash
         
     | 
| 
      
 49 
     | 
    
         
            +
                  @unversioned_key_hash ||= key_hash(unversioned_key)
         
     | 
| 
      
 50 
     | 
    
         
            +
                end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                private
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                def key_hash(key)
         
     | 
| 
      
 55 
     | 
    
         
            +
                  "cacheable:#{Digest::MD5.hexdigest(key)}"
         
     | 
| 
      
 56 
     | 
    
         
            +
                end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                def versioned_key
         
     | 
| 
      
 59 
     | 
    
         
            +
                  @versioned_key ||= ResponseBank.cache_key_for(key: @key_data, version: @version_data)
         
     | 
| 
      
 60 
     | 
    
         
            +
                end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                def unversioned_key
         
     | 
| 
      
 63 
     | 
    
         
            +
                  @unversioned_key ||= ResponseBank.cache_key_for(key: @key_data)
         
     | 
| 
      
 64 
     | 
    
         
            +
                end
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                def cacheable_info_dump
         
     | 
| 
      
 67 
     | 
    
         
            +
                  log_info = [
         
     | 
| 
      
 68 
     | 
    
         
            +
                    "Raw cacheable.key: #{versioned_key}",
         
     | 
| 
      
 69 
     | 
    
         
            +
                    "cacheable.key: #{versioned_key_hash}",
         
     | 
| 
      
 70 
     | 
    
         
            +
                  ]
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                  if @env['HTTP_IF_NONE_MATCH']
         
     | 
| 
      
 73 
     | 
    
         
            +
                    log_info.push("If-None-Match: #{@env['HTTP_IF_NONE_MATCH']}")
         
     | 
| 
      
 74 
     | 
    
         
            +
                  end
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                  log_info.join(', ')
         
     | 
| 
      
 77 
     | 
    
         
            +
                end
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                def try_to_serve_from_cache
         
     | 
| 
      
 80 
     | 
    
         
            +
                  # Etag
         
     | 
| 
      
 81 
     | 
    
         
            +
                  response = serve_from_browser_cache(versioned_key_hash)
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
                  return response if response
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
                  # Memcached
         
     | 
| 
      
 86 
     | 
    
         
            +
                  response = if @serve_unversioned
         
     | 
| 
      
 87 
     | 
    
         
            +
                    serve_from_cache(unversioned_key_hash, "Cache hit: server (unversioned)")
         
     | 
| 
      
 88 
     | 
    
         
            +
                  else
         
     | 
| 
      
 89 
     | 
    
         
            +
                    serve_from_cache(versioned_key_hash, "Cache hit: server")
         
     | 
| 
      
 90 
     | 
    
         
            +
                  end
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
                  return response if response
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
                  @env['cacheable.locked'] ||= false
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
                  if @env['cacheable.locked'] || ResponseBank.acquire_lock(versioned_key_hash)
         
     | 
| 
      
 97 
     | 
    
         
            +
                    # execute if we can get the lock
         
     | 
| 
      
 98 
     | 
    
         
            +
                    @env['cacheable.locked'] = true
         
     | 
| 
      
 99 
     | 
    
         
            +
                  elsif serving_from_noncurrent_but_recent_version_acceptable?
         
     | 
| 
      
 100 
     | 
    
         
            +
                    # serve a stale version
         
     | 
| 
      
 101 
     | 
    
         
            +
                    response = serve_from_cache(unversioned_key_hash, "Cache hit: server (recent)", @cache_age_tolerance)
         
     | 
| 
      
 102 
     | 
    
         
            +
             
     | 
| 
      
 103 
     | 
    
         
            +
                    return response if response
         
     | 
| 
      
 104 
     | 
    
         
            +
                  end
         
     | 
| 
      
 105 
     | 
    
         
            +
             
     | 
| 
      
 106 
     | 
    
         
            +
                  # No cache hit; this request cannot be handled from cache.
         
     | 
| 
      
 107 
     | 
    
         
            +
                  # Yield to the controller and mark for writing into cache.
         
     | 
| 
      
 108 
     | 
    
         
            +
                  refill_cache
         
     | 
| 
      
 109 
     | 
    
         
            +
                end
         
     | 
| 
      
 110 
     | 
    
         
            +
             
     | 
| 
      
 111 
     | 
    
         
            +
                def serving_from_noncurrent_but_recent_version_acceptable?
         
     | 
| 
      
 112 
     | 
    
         
            +
                  @cache_age_tolerance > 0
         
     | 
| 
      
 113 
     | 
    
         
            +
                end
         
     | 
| 
      
 114 
     | 
    
         
            +
             
     | 
| 
      
 115 
     | 
    
         
            +
                def serve_from_browser_cache(cache_key_hash)
         
     | 
| 
      
 116 
     | 
    
         
            +
                  if @env["HTTP_IF_NONE_MATCH"] == cache_key_hash
         
     | 
| 
      
 117 
     | 
    
         
            +
                    @env['cacheable.miss']  = false
         
     | 
| 
      
 118 
     | 
    
         
            +
                    @env['cacheable.store'] = 'client'
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
                    @headers.delete('Content-Type')
         
     | 
| 
      
 121 
     | 
    
         
            +
                    @headers.delete('Content-Length')
         
     | 
| 
      
 122 
     | 
    
         
            +
             
     | 
| 
      
 123 
     | 
    
         
            +
                    ResponseBank.log("Cache hit: client")
         
     | 
| 
      
 124 
     | 
    
         
            +
             
     | 
| 
      
 125 
     | 
    
         
            +
                    [304, @headers, []]
         
     | 
| 
      
 126 
     | 
    
         
            +
                  end
         
     | 
| 
      
 127 
     | 
    
         
            +
                end
         
     | 
| 
      
 128 
     | 
    
         
            +
             
     | 
| 
      
 129 
     | 
    
         
            +
                def serve_from_cache(cache_key_hash, message, cache_age_tolerance = nil)
         
     | 
| 
      
 130 
     | 
    
         
            +
                  raw = @cache_store.read(cache_key_hash)
         
     | 
| 
      
 131 
     | 
    
         
            +
             
     | 
| 
      
 132 
     | 
    
         
            +
                  if raw
         
     | 
| 
      
 133 
     | 
    
         
            +
                    hit = MessagePack.load(raw)
         
     | 
| 
      
 134 
     | 
    
         
            +
             
     | 
| 
      
 135 
     | 
    
         
            +
                    @env['cacheable.miss']  = false
         
     | 
| 
      
 136 
     | 
    
         
            +
                    @env['cacheable.store'] = 'server'
         
     | 
| 
      
 137 
     | 
    
         
            +
             
     | 
| 
      
 138 
     | 
    
         
            +
                    status, content_type, body, timestamp, location = hit
         
     | 
| 
      
 139 
     | 
    
         
            +
             
     | 
| 
      
 140 
     | 
    
         
            +
                    if cache_age_tolerance && page_too_old?(timestamp, cache_age_tolerance)
         
     | 
| 
      
 141 
     | 
    
         
            +
                      ResponseBank.log("Found an unversioned cache entry, but it was too old (#{timestamp})")
         
     | 
| 
      
 142 
     | 
    
         
            +
             
     | 
| 
      
 143 
     | 
    
         
            +
                      nil
         
     | 
| 
      
 144 
     | 
    
         
            +
                    else
         
     | 
| 
      
 145 
     | 
    
         
            +
                      @headers['Content-Type'] = content_type
         
     | 
| 
      
 146 
     | 
    
         
            +
             
     | 
| 
      
 147 
     | 
    
         
            +
                      @headers['Location'] = location if location
         
     | 
| 
      
 148 
     | 
    
         
            +
             
     | 
| 
      
 149 
     | 
    
         
            +
                      if @env["gzip"]
         
     | 
| 
      
 150 
     | 
    
         
            +
                        @headers['Content-Encoding'] = "gzip"
         
     | 
| 
      
 151 
     | 
    
         
            +
                      else
         
     | 
| 
      
 152 
     | 
    
         
            +
                        # we have to uncompress because the client doesn't support gzip
         
     | 
| 
      
 153 
     | 
    
         
            +
                        ResponseBank.log("uncompressing for client without gzip")
         
     | 
| 
      
 154 
     | 
    
         
            +
                        body = ResponseBank.decompress(body)
         
     | 
| 
      
 155 
     | 
    
         
            +
                      end
         
     | 
| 
      
 156 
     | 
    
         
            +
             
     | 
| 
      
 157 
     | 
    
         
            +
                      ResponseBank.log(message)
         
     | 
| 
      
 158 
     | 
    
         
            +
             
     | 
| 
      
 159 
     | 
    
         
            +
                      [status, @headers, [body]]
         
     | 
| 
      
 160 
     | 
    
         
            +
                    end
         
     | 
| 
      
 161 
     | 
    
         
            +
                  end
         
     | 
| 
      
 162 
     | 
    
         
            +
                end
         
     | 
| 
      
 163 
     | 
    
         
            +
             
     | 
| 
      
 164 
     | 
    
         
            +
                def page_too_old?(timestamp, cache_age_tolerance)
         
     | 
| 
      
 165 
     | 
    
         
            +
                  !timestamp || timestamp < (Time.now.to_i - cache_age_tolerance)
         
     | 
| 
      
 166 
     | 
    
         
            +
                end
         
     | 
| 
      
 167 
     | 
    
         
            +
             
     | 
| 
      
 168 
     | 
    
         
            +
                def refill_cache
         
     | 
| 
      
 169 
     | 
    
         
            +
                  @env['cacheable.miss'] = true
         
     | 
| 
      
 170 
     | 
    
         
            +
             
     | 
| 
      
 171 
     | 
    
         
            +
                  ResponseBank.log("Refilling cache")
         
     | 
| 
      
 172 
     | 
    
         
            +
             
     | 
| 
      
 173 
     | 
    
         
            +
                  @cache_miss_block.call
         
     | 
| 
      
 174 
     | 
    
         
            +
                end
         
     | 
| 
      
 175 
     | 
    
         
            +
              end
         
     | 
| 
      
 176 
     | 
    
         
            +
            end
         
     | 
    
        metadata
    ADDED
    
    | 
         @@ -0,0 +1,152 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            --- !ruby/object:Gem::Specification
         
     | 
| 
      
 2 
     | 
    
         
            +
            name: response_bank
         
     | 
| 
      
 3 
     | 
    
         
            +
            version: !ruby/object:Gem::Version
         
     | 
| 
      
 4 
     | 
    
         
            +
              version: 1.0.0
         
     | 
| 
      
 5 
     | 
    
         
            +
            platform: ruby
         
     | 
| 
      
 6 
     | 
    
         
            +
            authors:
         
     | 
| 
      
 7 
     | 
    
         
            +
            - Tobias Lütke
         
     | 
| 
      
 8 
     | 
    
         
            +
            - Burke Libbey
         
     | 
| 
      
 9 
     | 
    
         
            +
            autorequire: 
         
     | 
| 
      
 10 
     | 
    
         
            +
            bindir: bin
         
     | 
| 
      
 11 
     | 
    
         
            +
            cert_chain: []
         
     | 
| 
      
 12 
     | 
    
         
            +
            date: 2020-01-27 00:00:00.000000000 Z
         
     | 
| 
      
 13 
     | 
    
         
            +
            dependencies:
         
     | 
| 
      
 14 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 15 
     | 
    
         
            +
              name: useragent
         
     | 
| 
      
 16 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 17 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 18 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 19 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 20 
     | 
    
         
            +
                    version: '0'
         
     | 
| 
      
 21 
     | 
    
         
            +
              type: :runtime
         
     | 
| 
      
 22 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 23 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 24 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 25 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 26 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 27 
     | 
    
         
            +
                    version: '0'
         
     | 
| 
      
 28 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 29 
     | 
    
         
            +
              name: msgpack
         
     | 
| 
      
 30 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 31 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 32 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 33 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 34 
     | 
    
         
            +
                    version: '0'
         
     | 
| 
      
 35 
     | 
    
         
            +
              type: :runtime
         
     | 
| 
      
 36 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 37 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 38 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 39 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 40 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 41 
     | 
    
         
            +
                    version: '0'
         
     | 
| 
      
 42 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 43 
     | 
    
         
            +
              name: minitest
         
     | 
| 
      
 44 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 45 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 46 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 47 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 48 
     | 
    
         
            +
                    version: 5.13.0
         
     | 
| 
      
 49 
     | 
    
         
            +
              type: :development
         
     | 
| 
      
 50 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 51 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 52 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 53 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 54 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 55 
     | 
    
         
            +
                    version: 5.13.0
         
     | 
| 
      
 56 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 57 
     | 
    
         
            +
              name: mocha
         
     | 
| 
      
 58 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 59 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 60 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 61 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 62 
     | 
    
         
            +
                    version: 1.10.0
         
     | 
| 
      
 63 
     | 
    
         
            +
              type: :development
         
     | 
| 
      
 64 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 65 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 66 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 67 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 68 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 69 
     | 
    
         
            +
                    version: 1.10.0
         
     | 
| 
      
 70 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 71 
     | 
    
         
            +
              name: rake
         
     | 
| 
      
 72 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 73 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 74 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 75 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 76 
     | 
    
         
            +
                    version: '0'
         
     | 
| 
      
 77 
     | 
    
         
            +
              type: :development
         
     | 
| 
      
 78 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 79 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 80 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 81 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 82 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 83 
     | 
    
         
            +
                    version: '0'
         
     | 
| 
      
 84 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 85 
     | 
    
         
            +
              name: rails
         
     | 
| 
      
 86 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 87 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 88 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 89 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 90 
     | 
    
         
            +
                    version: '5.0'
         
     | 
| 
      
 91 
     | 
    
         
            +
              type: :development
         
     | 
| 
      
 92 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 93 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 94 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 95 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 96 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 97 
     | 
    
         
            +
                    version: '5.0'
         
     | 
| 
      
 98 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 99 
     | 
    
         
            +
              name: tzinfo-data
         
     | 
| 
      
 100 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 101 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 102 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 103 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 104 
     | 
    
         
            +
                    version: 1.2019.3
         
     | 
| 
      
 105 
     | 
    
         
            +
              type: :development
         
     | 
| 
      
 106 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 107 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 108 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 109 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 110 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 111 
     | 
    
         
            +
                    version: 1.2019.3
         
     | 
| 
      
 112 
     | 
    
         
            +
            description: 
         
     | 
| 
      
 113 
     | 
    
         
            +
            email:
         
     | 
| 
      
 114 
     | 
    
         
            +
            - tobi@shopify.com
         
     | 
| 
      
 115 
     | 
    
         
            +
            - burke@burkelibbey.org
         
     | 
| 
      
 116 
     | 
    
         
            +
            executables: []
         
     | 
| 
      
 117 
     | 
    
         
            +
            extensions: []
         
     | 
| 
      
 118 
     | 
    
         
            +
            extra_rdoc_files: []
         
     | 
| 
      
 119 
     | 
    
         
            +
            files:
         
     | 
| 
      
 120 
     | 
    
         
            +
            - LICENSE.txt
         
     | 
| 
      
 121 
     | 
    
         
            +
            - README.md
         
     | 
| 
      
 122 
     | 
    
         
            +
            - lib/response_bank.rb
         
     | 
| 
      
 123 
     | 
    
         
            +
            - lib/response_bank/controller.rb
         
     | 
| 
      
 124 
     | 
    
         
            +
            - lib/response_bank/middleware.rb
         
     | 
| 
      
 125 
     | 
    
         
            +
            - lib/response_bank/model_extensions.rb
         
     | 
| 
      
 126 
     | 
    
         
            +
            - lib/response_bank/railtie.rb
         
     | 
| 
      
 127 
     | 
    
         
            +
            - lib/response_bank/response_cache_handler.rb
         
     | 
| 
      
 128 
     | 
    
         
            +
            - lib/response_bank/version.rb
         
     | 
| 
      
 129 
     | 
    
         
            +
            homepage: ''
         
     | 
| 
      
 130 
     | 
    
         
            +
            licenses:
         
     | 
| 
      
 131 
     | 
    
         
            +
            - MIT
         
     | 
| 
      
 132 
     | 
    
         
            +
            metadata: {}
         
     | 
| 
      
 133 
     | 
    
         
            +
            post_install_message: 
         
     | 
| 
      
 134 
     | 
    
         
            +
            rdoc_options: []
         
     | 
| 
      
 135 
     | 
    
         
            +
            require_paths:
         
     | 
| 
      
 136 
     | 
    
         
            +
            - lib
         
     | 
| 
      
 137 
     | 
    
         
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         
     | 
| 
      
 138 
     | 
    
         
            +
              requirements:
         
     | 
| 
      
 139 
     | 
    
         
            +
              - - ">="
         
     | 
| 
      
 140 
     | 
    
         
            +
                - !ruby/object:Gem::Version
         
     | 
| 
      
 141 
     | 
    
         
            +
                  version: 2.4.0
         
     | 
| 
      
 142 
     | 
    
         
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         
     | 
| 
      
 143 
     | 
    
         
            +
              requirements:
         
     | 
| 
      
 144 
     | 
    
         
            +
              - - ">="
         
     | 
| 
      
 145 
     | 
    
         
            +
                - !ruby/object:Gem::Version
         
     | 
| 
      
 146 
     | 
    
         
            +
                  version: '0'
         
     | 
| 
      
 147 
     | 
    
         
            +
            requirements: []
         
     | 
| 
      
 148 
     | 
    
         
            +
            rubygems_version: 3.1.2
         
     | 
| 
      
 149 
     | 
    
         
            +
            signing_key: 
         
     | 
| 
      
 150 
     | 
    
         
            +
            specification_version: 4
         
     | 
| 
      
 151 
     | 
    
         
            +
            summary: Simple response caching for Ruby applications
         
     | 
| 
      
 152 
     | 
    
         
            +
            test_files: []
         
     |