def _output_if_modified(self, content): """ Check for ETag, then fall back to If-Modified-Since """ with TraceContext() as root: with root.span("CacheableHandler._output_if_modified"): modified = True # Normalize content try: content = str(content) except UnicodeEncodeError: content = unicode(content).encode('utf-8') etag = 'W/"{}"'.format(hashlib.md5(content).hexdigest()) # Weak ETag self.response.headers['ETag'] = etag if_none_match = self.request.headers.get('If-None-Match') if if_none_match and etag in [x.strip() for x in if_none_match.split(',')]: self.response.set_status(304) modified = False # Fall back to If-Modified-Since if modified and self._last_modified is not None: last_modified = format_date_time(mktime(self._last_modified.timetuple())) if_modified_since = self.request.headers.get('If-Modified-Since') self.response.headers['Last-Modified'] = last_modified if if_modified_since and if_modified_since == last_modified: self.response.set_status(304) modified = False if modified: self.response.out.write(content) return modified
def _validate_tba_auth_key(self): """ Tests the presence of a X-TBA-Auth-Key header or URL param. """ with TraceContext() as root: with root.span("ApiBaseController._validate_tba_auth_key"): x_tba_auth_key = self.request.headers.get("X-TBA-Auth-Key") if x_tba_auth_key is None: x_tba_auth_key = self.request.get('X-TBA-Auth-Key') self.auth_owner = None self.auth_owner_key = None self.auth_description = None if not x_tba_auth_key: account = self._user_bundle.account if account: self.auth_owner = account.key.id() self.auth_owner_key = account.key elif 'thebluealliance.com' in self.request.headers.get( "Origin", ""): self.auth_owner = 'The Blue Alliance' else: self._errors = { "Error": "X-TBA-Auth-Key is a required header or URL param. Please get an access key at http://www.thebluealliance.com/account." } self.abort(401) if self.auth_owner: logging.info("Auth owner: {}, LOGGED IN".format( self.auth_owner)) else: auth = ApiAuthAccess.get_by_id(x_tba_auth_key) if auth and auth.is_read_key: self.auth_owner = auth.owner.id() self.auth_owner_key = auth.owner self.auth_description = auth.description if self.REQUIRE_ADMIN_AUTH and not auth.allow_admin: self._errors = { "Error": "X-TBA-Auth-Key does not have required permissions" } self.abort(401) logging.info( "Auth owner: {}, X-TBA-Auth-Key: {}".format( self.auth_owner, x_tba_auth_key)) else: self._errors = { "Error": "X-TBA-Auth-Key is invalid. Please get an access key at http://www.thebluealliance.com/account." } self.abort(401)
def get(self, *args, **kw): with TraceContext() as root: with root.span("ApiBaseController.get"): self._validate_tba_auth_key() self._errors = ValidationHelper.validate_request(self) if self._errors: self.abort(404) super(ApiBaseController, self).get(*args, **kw) self.response.headers['X-TBA-Version'] = '{}'.format( self.API_VERSION) self.response.headers['Vary'] = 'Accept-Encoding' if not self._errors: self._track_call(*args, **kw)
def __init__(self, *args, **kw): super(CacheableHandler, self).__init__(*args, **kw) if type(self.request) == webapp2.Request: trace_context.request = self.request with TraceContext(sendTrace=False) as root: with root.span("CacheableHandler.__init__"): self._cache_expiration = 0 self._last_modified = None # A datetime object self._user_bundle = UserBundle() self._is_admin = self._user_bundle.is_current_user_admin if not hasattr(self, '_partial_cache_key'): self._partial_cache_key = self.CACHE_KEY_FORMAT self.template_values = {} if self.response: self.response.headers['Vary'] = 'Accept-Encoding'
def get(self, *args, **kw): with TraceContext() as root: with root.span("CacheableHandler.get"): with root.span("CacheableHandler._read_cache"): cached_response = self._read_cache() if cached_response is None: self._set_cache_header_length(self.CACHE_HEADER_LENGTH) self.template_values["render_time"] = datetime.datetime.now().replace(second=0, microsecond=0) # Prevent ETag from changing too quickly with root.span("CacheableHandler._render"): rendered = self._render(*args, **kw) if self._output_if_modified(self._add_admin_bar(rendered)): self._write_cache(self.response) else: self.response.headers.update(cached_response.headers) del self.response.headers['Content-Length'] # Content-Length gets set automatically self._output_if_modified(self._add_admin_bar(cached_response.body))
def fetch_async(self, dict_version=None, return_updated=False): with TraceContext() as root: with root.span("{}.fetch_async".format( self.__class__.__name__)) as spn: if dict_version: if dict_version not in self.VALID_DICT_VERSIONS: raise Exception( "Bad api version for database query: {}".format( dict_version)) cache_key = self._dict_cache_key(self.cache_key, dict_version) else: cache_key = self.cache_key cached_query = yield CachedQueryResult.get_by_id_async( cache_key) do_stats = random.random() < tba_config.RECORD_FRACTION rpcs = [] if cached_query is None: if do_stats: rpcs.append( MEMCACHE_CLIENT.incr_async(random.choice( self.DATABASE_MISSES_MEMCACHE_KEYS), initial_value=0)) query_result = yield self._query_async() if dict_version: query_result = self.DICT_CONVERTER.convert( query_result, dict_version) if tba_config.CONFIG['database_query_cache']: if dict_version: rpcs.append( CachedQueryResult( id=cache_key, result_dict=query_result, ).put_async()) else: rpcs.append( CachedQueryResult( id=cache_key, result=query_result, ).put_async()) updated = datetime.datetime.now() else: if do_stats: rpcs.append( MEMCACHE_CLIENT.incr_async(random.choice( self.DATABASE_HITS_MEMCACHE_KEYS), initial_value=0)) if dict_version: query_result = cached_query.result_dict else: query_result = cached_query.result updated = cached_query.updated for rpc in rpcs: try: rpc.get_result() except Exception, e: logging.warning( "An RPC in DatabaseQuery.fetch_async() failed!") if return_updated: raise ndb.Return((query_result, updated)) else: raise ndb.Return(query_result)
def render(template, template_values): from stackdriver.profiler import TraceContext with TraceContext() as root: with root.span("jinja2_engine.render({})".format(template)): template = JINJA_ENV.get_template(template) return template.render(template_values)