I noticed that there are no helpers for Etag or Last-Modified based caching.
That way we could use
def json():
etag(some_etag)
last_modified(some_timestamp)
some_response = do_expensive_stuff
return some_response
which is _very_ useful when you implement an API with expensive operations which often get requests for the exactly same response. Add a caching proxy and and they are only expensive once.
Sinatra (Ruby) has those helpers: https://github.com/sinatra/sinatra/blob/v1.4.5/lib/sinatra/base.rb#L439-L554
You can use response.add_etag() on a response object:
def json():
some_response = do_expensive_stuff()
some_response = make_response(some_response)
some_response.add_etag()
return some_response
This could quite easily be turned into a decorator.
Maybe I did not make my point clear well enough. Surely, I know how to add a header to my response.
I want my application to respond with 304 if Etag or Last-Modified match. I _don't_ want to regenerate any response which is already cached somewhere. etag or last_modified should yield a 304-response if the cache's object is fresh and otherwise add those headers to the newly response.
That's also how it's done in the code I linked.
Not sure what exactly you are looking for. There is already support for handling conditional requests. http://werkzeug.pocoo.org/docs/wrappers/#werkzeug.wrappers.ETagResponseMixin.make_conditional
I think @fb is saying that if an e-tag has been set on the response and the request has an e-tag in it then flask should automatically return a not modified response.
In response to @fb:
I want my application to respond with 304 if Etag or Last-Modified match.
As per Armin's reply this can be done using:
def json():
some_response = do_expensive_stuff()
some_response = make_response(some_response)
some_response.add_etag()
return some_response.make_conditional()
I don't want to regenerate any response which is already cached somewhere. etag or last_modified should yield a 304-response if the cache's object is fresh and otherwise add those headers to the newly response.
But to add an e-tag you need to know what the response is, so either your etag is not reliant upon the response and thus isn't really an etag and can cause stale results to be used or it is reliant on the response and thus your goal of "not wanting to regenerate a response" won't be met. Supposing you can somehow generate the etag without actually generating the response then you could do something like this:
def json()
some_response = make_response()
some_response.set_etag('the-etag')
some_response.make_conditional()
if some_response.status_code = 304:
return some_response
some_response = do_expensive_stuff()
some_response.set_etag('the-etag')
return some_response
These are very crude and I would make use of decorators but you get the rough idea
What I did was use flask-cache to save the response and then created a cache_header decorator:
from datetime import datetime as dt, timedelta
from json import dumps
from flask import make_response, request, route
from app import cache
def jsonify(**kwargs):
response = make_response(dumps(kwargs))
response.headers['Content-Type'] = 'application/json; charset=utf-8'
response.headers['mimetype'] = 'application/json'
response.last_modified = dt.utcnow()
response.add_etag()
return response
def cache_header(max_age, **ckwargs):
def decorator(view):
f = cache.cached(max_age, **ckwargs)(view)
@wraps(f)
def wrapper(*args, **wkwargs):
response = f(*args, **wkwargs)
response.cache_control.max_age = max_age
response.cache_control.public = True
extra = timedelta(seconds=max_age)
response.expires = response.last_modified + extra
return response.make_conditional(request)
return wrapper
return decorator
@route('/route')
@cache_header(TIMEOUT)
def route():
result = do_expensive_stuff()
return jsonify(result=result)
CR #637
Most helpful comment
What I did was use flask-cache to save the response and then created a
cache_headerdecorator:CR #637