247 lines
6.9 KiB
Python
247 lines
6.9 KiB
Python
|
import time
|
||
|
|
||
|
import logging
|
||
|
|
||
|
from .coaplrucache import CoapLRUCache
|
||
|
from coapthon import utils
|
||
|
from coapthon.messages.request import *
|
||
|
|
||
|
__author__ = 'Emilio Vallati'
|
||
|
|
||
|
logger = logging.getLogger(__name__)
|
||
|
|
||
|
|
||
|
class Cache(object):
|
||
|
|
||
|
def __init__(self, mode, max_dim):
|
||
|
"""
|
||
|
|
||
|
:param max_dim: max number of elements in the cache
|
||
|
:param mode: used to differentiate between a cache used in a forward-proxy or in a reverse-proxy
|
||
|
"""
|
||
|
|
||
|
self.max_dimension = max_dim
|
||
|
self.mode = mode
|
||
|
self.cache = CoapLRUCache(max_dim)
|
||
|
|
||
|
def cache_add(self, request, response):
|
||
|
"""
|
||
|
checks for full cache and valid code before updating the cache
|
||
|
|
||
|
:param request:
|
||
|
:param response:
|
||
|
:return:
|
||
|
"""
|
||
|
logger.debug("adding response to the cache")
|
||
|
|
||
|
"""
|
||
|
checking for valid code
|
||
|
- """
|
||
|
code = response.code
|
||
|
try:
|
||
|
utils.check_code(code)
|
||
|
except utils.InvalidResponseCode: # pragma no cover
|
||
|
logger.error("Invalid response code")
|
||
|
return
|
||
|
|
||
|
"""
|
||
|
return if max_age is 0
|
||
|
"""
|
||
|
if response.max_age == 0:
|
||
|
return
|
||
|
|
||
|
"""
|
||
|
Initialising new cache element based on the mode and updating the cache
|
||
|
"""
|
||
|
|
||
|
if self.mode == defines.FORWARD_PROXY:
|
||
|
new_key = CacheKey(request)
|
||
|
else:
|
||
|
new_key = ReverseCacheKey(request)
|
||
|
|
||
|
logger.debug("MaxAge = {maxage}".format(maxage=response.max_age))
|
||
|
new_element = CacheElement(new_key, response, request, response.max_age)
|
||
|
|
||
|
self.cache.update(new_key, new_element)
|
||
|
logger.debug("Cache Size = {size}".format(size=self.cache.debug_print()))
|
||
|
|
||
|
def search_related(self, request):
|
||
|
logger.debug("Cache Search Request")
|
||
|
if self.cache.is_empty() is True:
|
||
|
logger.debug("Empty Cache")
|
||
|
return None
|
||
|
|
||
|
"""
|
||
|
extracting everything from the cache
|
||
|
"""
|
||
|
result = []
|
||
|
items = list(self.cache.cache.items())
|
||
|
|
||
|
for key, item in items:
|
||
|
element = self.cache.get(item.key)
|
||
|
logger.debug("Element : {elm}".format(elm=str(element)))
|
||
|
|
||
|
if request.proxy_uri == element.uri:
|
||
|
result.append(item)
|
||
|
|
||
|
return result
|
||
|
|
||
|
def search_response(self, request):
|
||
|
"""
|
||
|
creates a key from the request and searches the cache with it
|
||
|
|
||
|
:param request:
|
||
|
:return CacheElement: returns None if there's a cache miss
|
||
|
"""
|
||
|
logger.debug("Cache Search Response")
|
||
|
|
||
|
if self.cache.is_empty() is True:
|
||
|
logger.debug("Empty Cache")
|
||
|
return None
|
||
|
|
||
|
"""
|
||
|
create a new cache key from the request
|
||
|
"""
|
||
|
|
||
|
if self.mode == defines.FORWARD_PROXY:
|
||
|
search_key = CacheKey(request)
|
||
|
else:
|
||
|
search_key = ReverseCacheKey(request)
|
||
|
|
||
|
response = self.cache.get(search_key)
|
||
|
|
||
|
return response
|
||
|
|
||
|
def validate(self, request, response):
|
||
|
"""
|
||
|
refreshes a resource when a validation response is received
|
||
|
|
||
|
:param request:
|
||
|
:param response:
|
||
|
:return:
|
||
|
"""
|
||
|
element = self.search_response(request)
|
||
|
if element is not None:
|
||
|
element.cached_response.options = response.options
|
||
|
element.freshness = True
|
||
|
element.max_age = response.max_age
|
||
|
element.creation_time = time.time()
|
||
|
element.uri = request.proxy_uri
|
||
|
|
||
|
def mark(self, element):
|
||
|
"""
|
||
|
marks the requested resource in the cache as not fresh
|
||
|
:param element:
|
||
|
:return:
|
||
|
"""
|
||
|
logger.debug("Element : {elm}".format(elm=str(element)))
|
||
|
if element is not None:
|
||
|
logger.debug("Mark as not fresh")
|
||
|
element.freshness = False
|
||
|
logger.debug(str(self.cache))
|
||
|
|
||
|
"""
|
||
|
class for the element contained in the cache
|
||
|
"""
|
||
|
|
||
|
|
||
|
class CacheElement(object):
|
||
|
def __init__(self, cache_key, response, request, max_age=60):
|
||
|
"""
|
||
|
|
||
|
:param cache_key: the key used to search for the element in the cache
|
||
|
:param response: the server response to store
|
||
|
:param max_age: maximum number of seconds that the resource is considered fresh
|
||
|
"""
|
||
|
self.freshness = True
|
||
|
self.key = cache_key
|
||
|
self.cached_response = response
|
||
|
self.max_age = max_age
|
||
|
self.creation_time = time.time()
|
||
|
self.uri = request.proxy_uri
|
||
|
|
||
|
def __str__(self):
|
||
|
return "Key: {key}, Fresh: {fresh}, URI: {uri}, MaxAge: {maxage}, CreationTime: {ct}, Response: {resp}"\
|
||
|
.format(key=str(self.key), fresh=self.freshness, uri=self.uri, maxage=self.max_age, ct=self.creation_time,
|
||
|
resp=str(self.cached_response))
|
||
|
|
||
|
"""
|
||
|
class for the key used to search elements in the cache (forward-proxy only)
|
||
|
"""
|
||
|
|
||
|
|
||
|
class CacheKey(object):
|
||
|
def __init__(self, request):
|
||
|
"""
|
||
|
|
||
|
:param request:
|
||
|
"""
|
||
|
logger.debug("Creating Key")
|
||
|
self._payload = request.payload
|
||
|
self._method = request.code
|
||
|
|
||
|
"""
|
||
|
making a list of the options that do not have a nocachekey option number and are not uri-path, uri-host, uri-port, uri-query
|
||
|
"""
|
||
|
|
||
|
self._options = []
|
||
|
for option in request.options:
|
||
|
if (utils.check_nocachekey(option) is False) and (utils.is_uri_option(option.number) is False):
|
||
|
self._options.append(option)
|
||
|
|
||
|
"""
|
||
|
creating a usable key for the cache structure
|
||
|
"""
|
||
|
|
||
|
option_str = ', '.join(map(str, self._options))
|
||
|
self._list = [self._payload, self._method, option_str]
|
||
|
|
||
|
self.hashkey = ', '.join(map(str, self._list))
|
||
|
logger.debug(self)
|
||
|
|
||
|
def __str__(self):
|
||
|
msg = ""
|
||
|
for opt in self._options:
|
||
|
msg += "{name}: {value}, ".format(name=opt.name, value=opt.value)
|
||
|
return "Payload: {p}, Method: {m}, Options: [{o}]".format(p=self._payload, m=self._method, o=msg)
|
||
|
|
||
|
"""
|
||
|
class for the key used to search elements in the cache (reverse-proxy only)
|
||
|
"""
|
||
|
|
||
|
|
||
|
class ReverseCacheKey(object):
|
||
|
def __init__(self, request):
|
||
|
"""
|
||
|
|
||
|
:param request:
|
||
|
"""
|
||
|
self._payload = request.payload
|
||
|
self._method = request.code
|
||
|
|
||
|
"""
|
||
|
making a list of the options that do not have a nocachekey option number
|
||
|
"""
|
||
|
|
||
|
self._options = []
|
||
|
for option in request.options:
|
||
|
if utils.check_nocachekey(option) is False:
|
||
|
self._options.append(option)
|
||
|
|
||
|
"""
|
||
|
creating a usable key for the cache structure
|
||
|
"""
|
||
|
|
||
|
option_str = ', '.join(map(str, self._options))
|
||
|
self.hashkey = (self._payload, self._method, option_str)
|
||
|
|
||
|
def __str__(self):
|
||
|
msg = ""
|
||
|
for opt in self._options:
|
||
|
msg += "{name}: {value}, ".format(name=opt.name, value=opt.value)
|
||
|
return "Payload: {p}, Method: {m}, Options: [{o}]".format(p=self._payload, m=self._method, o=msg)
|
||
|
|
||
|
|
||
|
|
||
|
|