13

I'm sending data cross domain via a POST request but the response isn't working, specifically, jQuery's success handler never gets called.

Stuff being used: Django, Apache, jQuery.

So, I set up a request rather similar to this:

$.ajax({
    url: "http://somesite.com/someplace",
    type: "POST",
    cache: false,
    dataType: "json",
    data: { ... },
    success: function( msg ) {
        alert(msg);
    },
});

As you well know, CORS allows me to respond to an OPTIONS query appropriately to say "Yes, you can POST to me". Which I'm doing. Firebug confirms I'm getting my 200 status code and that the return type is in fact application/json. However, Firebug also confirms that the success handler in the above is not being called.

For reference, my response to OPTIONS is:

elif request.method == "OPTIONS":
    response = HttpResponse("")
    response['Access-Control-Allow-Origin'] = "*"
    response['Access-Control-Allow-Methods'] = "POST, GET, OPTIONS"
    response['Access-Control-Allow-Headers'] = "X-Requested-With"
    return response

In contrast, if I set up a complete: function()... handler it works.

So, question is: what's happening (or not) and why? I am getting data fine, I'd just like to be able to return the response.


Update: This fixes my issue on some browsers but since I don't have a complete definite explanation to this behaviour I'm leaving it open.

Ok, so I read the manual and what I understand of it, the algorithm applied is roughly this:

  1. User agents may implement a preflight call. This is the OPTIONS request. The idea is that they make this request which gives them an answer with respect to the requested resource, which they are then supposed to cache. I'm not passing back a max-age field, so I suspect whilst success is being returned and the X-request allowed, there is nothing in the user agent's cache which permitted me to make it, so the default rules (isolate the request) are applied.
  2. When you make the actual request, I believe the user agent is supposed to inspect the pre-flight cache for permissions. Without my max-age field, I believe it isn't finding these permissions. However, responding with the same headers on POST appears to allow Firefox and Google Chrome to view the response. Opera can not. IE remains untested at the moment.

I do not currently understand and it is not clear from the manual (to me at least) whether a CORS request should also answer with these headers in the request as well as the OPTIONS. I shall experiment with the Max-Age header and see what that allows or does not allow. However, I'm still short of some definite authoritative understanding on the issue so if there is someone on here who knows, I'm all ears.

  • Chances are it's being limited due to being a cross-domain call. I wouldn't want to log on to mybank.com and have my info sent to somebank.com (Same premise applies here, no matter how trivial the payload). – Brad Christie Mar 09 '11 at 20:25
  • 2
    The info has already been sent. CORS enables that. So whatever info that was supposed to be protected has just been POSTED to a totally separate domain and is now sat in my Postgres server. Too late. The problem here is not **sending** information, it is **receiving** it. –  Mar 09 '11 at 20:34
  • the browser not support this. the data is received , but the browser not let you see. you can test with "fiddler" – Gergely Fehérvári Mar 09 '11 at 21:00
  • @Ninefingers: I was using send as an example, but the issue still remains cross-domain related. My apologies if I was too vague. – Brad Christie Mar 09 '11 at 21:04
  • Not sure, but it might be that you need to use jsonp instead of json. http://www.codeproject.com/KB/aspnet/JSONToJSONP.aspx – idbentley Mar 09 '11 at 21:12
  • Everyone, the idea behind CORS is that it replaces the need for JSONP and allows you both send and receive data cross domain in a safe manner. In my example, I'm able to send and not receive, which is why I'm asking. This technique is only available on modern browsers, given, but it should work in its entirety, not in half. I've managed to make that happen but I'm not sure if my interpretation of the protocol is correct, so I've updated my question. But browsers do support this and I'm deliberately NOT using JSONP. –  Mar 10 '11 at 12:09
  • Note that CORS only has **partial support on IE9** (_while other browsers have had full support for over 7 years now!_). In particular, you have to use MS's **XDomainRequest** object (and it doesn't support setting headers, so POSTing url-encoded form data is out). **jQuery doesn't even try supporting IE9 via XDomainRequest and just gives an error**. – DavidJ Jan 03 '12 at 20:55

4 Answers4

17

Ok, so I believe the correct way to do things is this:

if request.method == "POST":
    response = HttpResponse(simplejson.dumps(data),mimetype='application/json')
    response['Access-Control-Allow-Origin'] = "*"
    return response
elif request.method == "OPTIONS":
    response = HttpResponse("")
    response['Access-Control-Allow-Origin'] = "*"
    response['Access-Control-Allow-Methods'] = "POST, OPTIONS"
    response['Access-Control-Allow-Headers'] = "X-Requested-With"
    response['Access-Control-Max-Age'] = "1800"
else:
    return HttpResponseBadRequest()

This is based on the documentation I dug up from Mozilla on preflighted requests.

So, what I believe will happen is this:

  1. If there's nothing in the preflight cache, OPTIONS is sent with X-Requested-With set to XMLHttpRequest I believe this is necessary to allow Javascript access to anything, along with an Origin header.
  2. The server can examine that information. That is the security of CORS. In my case, I'm responding with "any origin will do" and "you're allowed to send the X-Requested-With thing". I'm saying that OPTIONS and POST are allowed and that this response should be cached for 30 mins.
  3. The client then goes ahead and makes the POST, which was working before.
  4. I modified the response originally to include Allow-Methods and Allow-Headers but according to the exchange in the above linked documentation this isn't needed. This makes sense, the access check has already been done.
  5. I believe then that what happens is the resource sharing check described here. Basically, once said request has been made, the browser again checks the Allow-Origin field for validity, this being on the request such as POST. If this passes, the client can have access to the data, if not, the request has already completed but the browser denies the actual client side application (Javascript) access to that data.

I believe that is a correct summary of what is going on and in any case it appears to work. If I'm not right, please shout.

  • Are you sure that `Access-Control-Allow-Origin: "*"` is a legitimate response from a server? My understanding is that a server may accept any origin, but it should respond to each request with the client's Origin header... – Philip C Aug 19 '12 at 20:01
  • It is valid, yes - but please note that if you are passing xhr credentials, a wild-card origin acceptance will fail, regardless. – Troy Alford Dec 14 '12 at 05:47
  • `Access-Control-Max-Age: 180` = 3 minutes, not 30 minutes. – Cheeso Jul 26 '13 at 20:36
1

For any future searchers who may come across this posting, the following resource is the W3C 2008 working-draft which discusses CORS in-depth.

http://www.w3.org/TR/2008/WD-access-control-20080912/

As of the time of this posting, it should be noted that Chromium specifically, and probably all of WebKit has a bug which prevents the Access-Control-Max-Age header's value from being honored. Details on this can be found on the discussion page for Chromium Issue 131368. In summary - as of now, WebKit-based browsers will override whatever the server returns as a value here with 600 (10 minutes).

Troy Alford
  • 26,660
  • 10
  • 64
  • 82
0

REQUEST:

 $.ajax({
            url: "http://localhost:8079/students/add/",
            type: "POST",
            crossDomain: true,
            data: JSON.stringify(somejson),
            dataType: "json",
            success: function (response) {
                var resp = JSON.parse(response)
                alert(resp.status);
            },
            error: function (xhr, status) {
                alert("error");
            }
        });

RESPONSE:

response = HttpResponse(json.dumps('{"status" : "success"}'))
response.__setitem__("Content-type", "application/json")
response.__setitem__("Access-Control-Allow-Origin", "*")

return response
Hassan Zaheer
  • 1,361
  • 2
  • 20
  • 34
-2

I don't think this is possible for security reasons. The only cross domain ajax calls which browsers allow, can be done using JSONP and these are exclusively GET requests.

This will work:

$.ajax({
    url: "http://somesite.com/someplace",
    type: "GET",
    cache: false,
    dataType: "JSONP",
    data: { ... },
    success: function( msg ) {
        alert(msg);
    },
});

This won't:

$.ajax({
    url: "http://somesite.com/someplace",
    type: "POST",
    cache: false,
    dataType: "JSONP",
    data: { ... },
    success: function( msg ) {
        alert(msg);
    },
});
AyKarsi
  • 9,435
  • 10
  • 54
  • 92
  • 8
    -1 because "The only cross domain ajax calls which browsers allow, can be done using JSONP" isn't true. I've already managed a cross domain ajax call, or half of it, as is evident when I say "getting no data back". I'm well aware of the JSONP mechanism; I'm not familiar enough with the ins and outs of CORS (http://en.wikipedia.org/wiki/Cross-Origin_Resource_Sharing) which is what I'm using here. –  Mar 10 '11 at 11:55