Firstly, I'm not sure if this is a Flask or a Safari or a Flask-CORS issue.
In order to emulate a production setting, I have a local Flask backend (API) on one domain and a front-end (SPA) on a different domain. I initially tried this with both the API and the front-end running over HTTPS, using the same self-signed certificate, generated with openssl req -x509 -newkey rsa:4096 -nodes -keyout key.pem -out cert.pem -days 365
. But it turns out that the behaviour described below also manifests itself when running on plain HTTP.
Chrome and Firefox work as expected as far as I can see, but when I open the front-end with Safari and navigate around normally, Flask seemingly logs every XHR request twice. I only discovered this, because I have a Flask decorator that increments a counter in the database for specific requests and the counter always increments by 2 rather than 1, if I use Safari.
I found some information in another SO thread, which seems to suggest that Safari is performing a conditional request. The suggestion in the thread is to set If-Unmodified-Since
header when making the XHR request in the front-end. I tried that, and that seems to solve the problem — the first GET request seems to be replaced by an OPTIONS request, followed by a GET. If you refresh a couple of times, only the GET is performed. After a brief period of time of refreshing, it performs another OPTIONS request. However, if you don't supply If-Unmodified-Since
and log the request headers in the Flask handler, the headers from both requests look identical.
Also, using a different header instead of If-Modified-Since
also seems to first fire an OPTIONS request, followed by a GET, but it doesn't prevent the double GET — another GET is fired!
I have set up a Github repo containing a minimal example that reproduces this behaviour. It requires a bit of setup, but the instructions are outlined in the README. There is a screen recording attached as well.
I could settle for just adding If-Unmodified-Since
and calling it a day, but it feels a bit like brushing the whole issue under the carpet.. Why is it needed for Safari, but not other browsers?