# frozen_string_literal: true# Copyright, 2020, by Samuel G. D. Williams. <http://www.codeotaku.com>## Permission is hereby granted, free of charge, to any person obtaining a copy# of this software and associated documentation files (the "Software"), to deal# in the Software without restriction, including without limitation the rights# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell# copies of the Software, and to permit persons to whom the Software is# furnished to do so, subject to the following conditions:## The above copyright notice and this permission notice shall be included in# all copies or substantial portions of the Software.## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN# THE SOFTWARE.require'set'require'protocol/http/middleware'require_relative'body'require_relative'response'require_relative'store'moduleAsyncmoduleHTTPmoduleCacheclassGeneral<::Protocol::HTTP::MiddlewareCACHE_CONTROL='cache-control'CONTENT_TYPE='content-type'AUTHORIZATION='authorization'COOKIE='cookie'# Status codes of responses that MAY be stored by a cache or used in reply# to a subsequent request.## http://tools.ietf.org/html/rfc2616#section-13.4CACHEABLE_RESPONSE_CODES={200=>true,# OK203=>true,# Non-Authoritative Information300=>true,# Multiple Choices301=>true,# Moved Permanently302=>true,# Found404=>true,# Not Found410=>true# Gone}.freezedefinitialize(app,store: Store.default)super(app)@count=0@store=storeendattr:countattr:storedefclose@store.closeensuresuperenddefkey(request)@store.normalize(request)[request.authority,request.method,request.path]enddefcacheable?(request)# We don't support caching requests which have a request body:ifrequest.bodyreturnfalseend# We can't cache upgraded requests:ifrequest.protocolreturnfalseend# We only support caching GET and HEAD requests:unlessrequest.method=='GET'||request.method=='HEAD'returnfalseendifrequest.headers[AUTHORIZATION]returnfalseendifrequest.headers[COOKIE]returnfalseend# Otherwise, we can cache it:returntrueenddefwrap(key,request,response)unlessCACHEABLE_RESPONSE_CODES.include?(response.status)returnresponseendresponse_cache_control=response.headers[CACHE_CONTROL]ifresponse_cache_control&.no_store?||response_cache_control&.private?returnresponseendifrequest.head?andbody=response.bodyunlessbody.empty?Console.logger.warn(self){"HEAD request resulted in non-empty body!"}returnresponseendendreturnBody.wrap(response)do|response,body|Console.logger.debug(self){"Updating cache for #{key}..."}@store.insert(key,request,Response.new(response,body))endenddefcall(request)key=self.key(request)cache_control=request.headers[CACHE_CONTROL]unlesscache_control&.no_cache?ifresponse=@store.lookup(key,request)Console.logger.debug(self){"Cache hit for #{key}..."}@count+=1# Return the cached response:returnresponseendendunlesscache_control&.no_store?ifcacheable?(request)returnwrap(key,request,super)endendreturnsuperendendendendend