Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does an OPTIONS request fail when the answer to the follow up request is a 204?

I'm building a Backbone.js based app and face a strange issue.

At a certain point the app requests a collection resource and inside Chrome (and Safari) I get an error like that:

XMLHttpRequest cannot load http://api.mydomain.net/v1/foos/00000d/bars/000014/boots Origin http://localhost:3501 is not allowed by Access-Control-Allow-Origin.

Ok, CORS issue I thought and blamed my API. Then requested this very resource via CURL:

curl -i -H'Accept: application/json' -H'X-Auth-Token: pAWp5hrCmXA83GgFzgHC' -XOPTIONS 'http://api.mydomain.net/v1/foos/00000d/bars/000014/boots'
HTTP/1.1 200 OK
Status: 200
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,X-Auth-Token
Content-Length: 0

looks good, now the GET:

curl -i -H'Accept: application/json' -H'X-Auth-Token: pAWp5hrCmXA83GgFzgHC' -XGET 'http://api.mydomain.net/v1/foos/00000d/bars/000014/boots'
HTTP/1.1 204 No Content
Status: 204
Cache-Control: no-cache
Content-Length: 0
Content-Type: text/plain

In case I request boots collection that contain at least one object, everything works fine. The CORS headers my server responds with arr totally fine as I think. So why do the browsers report a cross origin resource problem?

Is it due to the content type text/plain of my 204 responses?

Preflight (OPTIONS) request in dev tools: OPTIONS

Request headers of aborted response: GET

like image 786
GeorgieF Avatar asked Nov 10 '12 10:11

GeorgieF


People also ask

Why is there an options request before post?

Prevent sending the post data, if it wont be processed This is the only reason what is valid. Using options request will prevent sending the post data to the server unnecessarily.

Why does browser make options request?

The OPTIONS request is so called pre-flight request, which is part of Cross-origin resource sharing (CORS). Browsers use it to check if a request is allowed from a particular domain as follows: The browser wants to send a request, let's say a POST request with the application/json content type.

Under what circumstances is a request Preflighted?

A preflight request is a small request that is sent by the browser before the actual request. It contains information like which HTTP method is used, as well as if any custom HTTP headers are present. The preflight gives the server a chance to examine what the actual request will look like before it's made.

What does request method options mean?

The HTTP OPTIONS method requests permitted communication options for a given URL or server. A client can specify a URL with this method, or an asterisk ( * ) to refer to the entire server. Request has body. No.


1 Answers

You have to also include the Access-Control-Allow-Origin in the response headers of the second request. That's not a client-side issue, but a backend one.

This behaviour is in accordance with the CORS specification, applied in the following explanation (section 7.1.5 "Cross-Origin Request with Preflight"):

  1. Preflight request (details omitted)
  2. "Set the cross-origin request status to preflight complete."
  3. "This is the actual request. (...) observe the request rules below while making the request."
    • If the response has an HTTP status code of 301, 302, 303, or 307 Not applicable
    • If the end user cancels the request Not applicable
    • If there is a network error Not applicable
    • Otherwise
      Perform a resource sharing check. If it returns fail, apply the cache and network error steps.

Your request already fails at the first step of the resource sharing check:

  1. If the response includes zero or more than one Access-Control-Allow-Origin header values, return fail and terminate this algorithm.

I provide a simple NodeJS example illustrating your problem.
Your current backend behaves like:

require('http').createServer(function(request, response) {
    if (request.method == 'OPTIONS') { // Handle preflight
        response.writeHead(200, {
           "Access-Control-Allow-Origin": "*",
           "Access-Control-Allow-Headers": "X-Foo"
        });
    } else {                           // Handle actual requests
        response.writeHead(204, {
          //"Access-Control-Allow-Origin": "*"
        });
    }
    response.end();
}).listen(12345);

Now, make the request and experience a failure:

var x = new XMLHttpRequest;
x.open('GET', 'http://localhost:12345');
x.setRequestHeader('X-Foo','header to trigger preflight');
x.send();

Go back to the code I provided, and enable the Access-Control-Allow-Origin header in the response, and test again. Your request will now succeed.

like image 72
Rob W Avatar answered Oct 10 '22 16:10

Rob W