Sunday, May 27, 2012

jQuery $.ajax(), $.post sending "OPTIONS' as REQUEST_METHOD in Firefox


Having trouble with what I thought was a relatively simple jQuery plugin...



The plugin should fetch data from a php script via ajax to add options to a <select> . The ajax request is pretty generic:




$.ajax({
url:o.url,
type: 'post',
contentType:"application/x-www-form-urlencoded",
data: '{"method":"getStates", "program":"EXPLORE"}',
success: function(data, status) {
console.log("Success!!");
console.log(data);
console.log(status);
},
error: function(xhr, desc, err) {
console.log(xhr);
console.log("Desc: " + desc + "\nErr:" + err);
}
});



This seems to work fine in Safari. In Firefox 3.5, the REQUEST_TYPE on the server is always 'OPTIONS', and the $_POST data does not appear. Apache logs the request as type 'OPTIONS':




::1 - - [08/Jul/2009:11:43:27 -0500] "OPTIONS sitecodes.php HTTP/1.1" 200 46



Why would this ajax call work in Safari, but not Firefox, and how do I fix it for Firefox?



Response Headers
Date: Wed, 08 Jul 2009 21:22:17 GMT
Server:Apache/2.0.59 (Unix) PHP/5.2.6 DAV/2
X-Powered-By: PHP/5.2.6
Content-Length 46
Keep-Alive timeout=15, max=100
Connection Keep-Alive
Content-Type text/html

Request Headers
Host orderform:8888
User-Agent Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1) Gecko/20090624 Firefox/3.5
Accept text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language en-us,en;q=0.5
Accept-Encoding gzip,deflate
Accept-Charset ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive 300
Connection keep-alive
Origin http://ux.inetu.act.org
Access-Control-Request-Method POST
Access-Control-Request-Headers x-requested-with


Here's a picture of the Firebug output: http://www.flickr.com/photos/fitzgeraldsteele/3701700103/


Source: Tips4all

16 comments:

  1. The reason for the error is the same origin policy. It only allows you to do XMLHTTPRequests to your own domain. See if you can use JSON instead:

    $.getJSON( 'http://<url>/api.php?callback=?', function ( data ) { alert ( data ); } );

    ReplyDelete
  2. I used the following code on Django side to interpret the OPTIONS request and to set the required Access-Control headers. After this my cross domain requests from Firefox started working. As said before, the browser first sends the OPTIONS request and then immediately after that the POST/GET

    def send_data(request):
    if request.method == "OPTIONS":
    response = HttpResponse()
    response['Access-Control-Allow-Origin'] = '*'
    response['Access-Control-Allow-Methods'] = 'POST, GET, OPTIONS'
    response['Access-Control-Max-Age'] = 1000
    response['Access-Control-Allow-Headers'] = '*'
    return response
    if request.method == "POST":
    # ...


    Edit: it seems to be that at least in some cases you also need to add the same Access-Control headers to the actual response. This can be a little bit confusing, since the request seems to succeed, but Firefox does not pass the contents of the response to the Javascript.

    ReplyDelete
  3. This mozilla developer center article describes various cross-domain request scenarios. The article seems to indicate that a POST request with content type of 'application/x-www-form-urlencoded' should be sent as a 'simple request' (with no 'preflight' OPTIONS request). I found , however, that Firefox sent the OPTIONS request, even though my POST was sent with that content type.

    I was able to make this work by creating an options request handler on the server, that set the 'Access-Control-Allow-Origin' response header to '*'. You can be more restrictive by setting it to something specific, like 'http://someurl.com'. Also, I have read that, supposedly, you can specify a comma-separated list of multiple origins, but I couldn't get this to work.

    Once Firefox receives the response to the OPTIONS request with an acceptable 'Access-Control-Allow-Origin' value, it sends the POST request.

    ReplyDelete
  4. I had same problem with sending requests to google maps, and solution is quite simple with jQuery 1.5 - for dataType use dataType: "jsonp"

    ReplyDelete
  5. I was looking through source 1.3.2, when using JSONP, the request is made by building a SCRIPT element dynamically, which gets past the browsers Same-domain policy. Naturally, you can't make a POST request using a SCRIPT element, the browser would fetch the result using GET.

    As you are requesting a JSONP call, the SCRIPT element is not generated, because it only does this when the Type of AJAX call is set to GET.

    http://dev.jquery.com/ticket/4690

    ReplyDelete
  6. This PHP at the top of the responding script seems to work. (With Firefox 3.6.11. I have not yet done a lot of testing.)

    header('Access-Control-Allow-Origin: *');
    header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
    header('Access-Control-Max-Age: 1000');
    if(array_key_exists('HTTP_ACCESS_CONTROL_REQUEST_HEADERS', $_SERVER)) {
    header('Access-Control-Allow-Headers: '
    . $_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']);
    } else {
    header('Access-Control-Allow-Headers: *');
    }

    if("OPTIONS" == $_SERVER['REQUEST_METHOD']) {
    exit(0);
    }

    ReplyDelete
  7. I've fixed this issue using an entirely-Apache based solution. In my vhost / htaccess I put the following block:

    # enable cross domain access control
    Header always set Access-Control-Allow-Origin "*"
    Header always set Access-Control-Allow-Methods "POST, GET, OPTIONS"

    # force apache to return 200 without executing my scripts
    RewriteEngine On
    RewriteCond %{REQUEST_METHOD} OPTIONS
    RewriteRule .* / [R=200,L]


    You may not need the latter part, depending on what happens when Apache executes your target script. Credit goes to the friendly ServerFault folk for the latter part.

    ReplyDelete
  8. Check if your form's action URL includes the www part of the domain, while the original page you have opened is viewed without www.

    Typically done for Canonical Urls..

    I struggled for hours before stumbling upon this article and found the hint of Cross Domain.

    ReplyDelete
  9. I seems that if o.url = 'index.php' and this file exists is ok and returning a success message in the console. It returns an error if I use url:'http://www.google.com'.

    If doing a post request why not using directly the $.post method:

    $.post("test.php", { func: "getNameAndTime" },
    function(data){
    alert(data.name); // John
    console.log(data.time); // 2pm
    }, "json");


    It is so much simpler.

    ReplyDelete
  10. Try adding the option:

    dataType: "json"

    ReplyDelete
  11. Another possibility to circumvent the problem is to use a proxy script. That method is described for example here: http://developer.yahoo.com/javascript/howto-proxy.html

    ReplyDelete
  12. We had a problem like this with ASP.Net. Our IIS was returning an Internal Server Error when trying to execute a jQuery $.post to get some html content due to PageHandlerFactory was restricted to respond only GET,HEAD,POST,DEBUG Verbs. So you can change that restriction adding the verb "OPTIONS" to the list or selecting "All Verbs"

    You can modify that in your IIS Manager, selecting your website, then selecting Handler Mappings, double click in your PageHandlerFactory for *.apx files as you need (We use Integrated application pool with framework 4.0). Click on Request Restrictions, then go to Verbs Tabn and apply your modification.

    Now our $.post request is working as expected :)

    ReplyDelete
  13. I have posted a clear example of how to solve this if control the server code of the domain you are POSTing to. This answer is touched on in this thread, but this more clearly explains it IMO.

    How do I send a cross-domain POST request via JavaScript?

    ReplyDelete
  14. Can you try this without

    contentType:application/x-www-form-urlencoded

    ReplyDelete
  15. I had a similar problem with trying to use the Facebook API.

    The only contentType which didn't send the Preflighted request seemed to be just text/plain... not the rest of the parameters mentioned at mozilla https://developer.mozilla.org/en/http_access_control


    Why is this the only browser which does this?
    Why doesn't Facebook know and accept the preflight request?


    FYI: The aforementioned Moz doc suggests X-Lori headers should trigger a Preflighted request ... it doesn't.

    ReplyDelete
  16. Solution to this is:


    use dataType: json
    add &callback=? to your url


    this worked on calling Facebook API and with Firefox. Firebug is using GET instead of OPTIONS with the above conditions (both of them).

    ReplyDelete