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)