Search code examples
restexpresscorsxmlhttprequesthttp-status-code-304

How to Get CORS to use HTTP 304 instead of HTTP 200 responses


It is unclear whether my RESTful CORS responses are ever retrieved from the cache. They all had the 200 status code but never 304; even though there is no change in the request, the response, and the If-None-Match and Etag headers.

The response headers are as follows:

Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: http://www.acme.com
Access-Control-Max-Age: 86400
Cache-Control: no-cache
Pragma: no-cache
Vary: Cookie, Origin

While the preflight requests are no longer invoked on a per-request basis, all other XHR calls are returning 200 HTTP response.

This problem only happens for cross-origin requests.

Edit: Following is a redacted excerpt:

import express from 'express';
import cors from 'cors';

const server = express();
server
    .set( 'etag', 'strong' )
    .use( cors({
        credentials: true,
        maxAge: 86400,
        origin: 'http://www.acme.com'
    }) )
    .use(( req, res, next ) => {
        res.setHeader( 'Cache-Control', 'no-cache' );
        res.setHeader( 'Pragma', 'no-cache' );
        res.setHeader( 'Vary', `Cookie, ${ res.get( 'Vary' ) }` );
        next();
    })
    ...

Solution

  • Seems to be a known issue of Chrome.

    When processing a conditional request where the response matches the ETag, express sets status 304 and returns an empty payload, see here. You can verify this with

    > curl -v -H "If-None-Match:..." http://server/path
    < HTTP/1.1 304 Not Modified
    < X-Powered-By: Express
    < Access-Control-Allow-Origin: *
    < Cache-Control: no-cache
    < ETag: "..."
    

    This server behavior is independent of whether the request is cross-origin or not.

    When the Chrome browser receives this response, it takes the payload from its cache and displays it in the network trace, together with status 304. However, in case of a cross-origin request, it displays status 200 instead. But this is an artifact of the browser alone, and has got nothing to do with the server.