Class: Async::HTTP::Cache::General

Inherits:
Protocol::HTTP::Middleware
  • Object
show all
Defined in:
lib/async/http/cache/general.rb

Constant Summary collapse

CACHE_CONTROL =
'cache-control'
CONTENT_TYPE =
'content-type'
AUTHORIZATION =
'authorization'
'cookie'
CACHEABLE_RESPONSE_CODES =

Status codes of responses that MAY be stored by a cache or used in reply to a subsequent request.

tools.ietf.org/html/rfc2616#section-13.4

{
  200 => true, # OK
  203 => true, # Non-Authoritative Information
  300 => true, # Multiple Choices
  301 => true, # Moved Permanently
  302 => true, # Found
  404 => true, # Not Found
  410 => true  # Gone
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app, store: Store.default) ⇒ General

Returns a new instance of General.



54
55
56
57
58
59
60
# File 'lib/async/http/cache/general.rb', line 54

def initialize(app, store: Store.default)
  super(app)

  @count = 0

  @store = store
end

Instance Attribute Details

#countObject (readonly)

Returns the value of attribute count.



62
63
64
# File 'lib/async/http/cache/general.rb', line 62

def count
  @count
end

#storeObject (readonly)

Returns the value of attribute store.



63
64
65
# File 'lib/async/http/cache/general.rb', line 63

def store
  @store
end

Instance Method Details

#cacheable?(request) ⇒ Boolean

Returns:

  • (Boolean)


77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/async/http/cache/general.rb', line 77

def cacheable?(request)
  # We don't support caching requests which have a request body:
  if request.body
    return false
  end

  # We can't cache upgraded requests:
  if request.protocol
    return false
  end

  # We only support caching GET and HEAD requests:
  unless request.method == 'GET' || request.method == 'HEAD'
    return false
  end

  if request.headers[AUTHORIZATION]
    return false
  end

  if request.headers[COOKIE]
    return false
  end

  # Otherwise, we can cache it:
  return true
end

#call(request) ⇒ Object



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/async/http/cache/general.rb', line 130

def call(request)
  key = self.key(request)

  cache_control = request.headers[CACHE_CONTROL]

  unless cache_control&.no_cache?
    if response = @store.lookup(key, request)
      Console.logger.debug(self) {"Cache hit for #{key}..."}
      @count += 1

      # Return the cached response:
      return response
    end
  end

  unless cache_control&.no_store?
    if cacheable?(request)
      return wrap(key, request, super)
    end
  end

  return super
end

#closeObject



65
66
67
68
69
# File 'lib/async/http/cache/general.rb', line 65

def close
  @store.close
ensure
  super
end

#key(request) ⇒ Object



71
72
73
74
75
# File 'lib/async/http/cache/general.rb', line 71

def key(request)
  @store.normalize(request)

  [request.authority, request.method, request.path]
end

#wrap(key, request, response) ⇒ Object



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/async/http/cache/general.rb', line 105

def wrap(key, request, response)
  unless CACHEABLE_RESPONSE_CODES.include?(response.status)
    return response
  end

  response_cache_control = response.headers[CACHE_CONTROL]

  if response_cache_control&.no_store? || response_cache_control&.private?
    return response
  end

  if request.head? and body = response.body
    unless body.empty?
      Console.logger.warn(self) {"HEAD request resulted in non-empty body!"}

      return response
    end
  end

  return Body.wrap(response) do |response, body|
    Console.logger.debug(self) {"Updating cache for #{key}..."}
    @store.insert(key, request, Response.new(response, body))
  end
end