Cross-origin resource sharing (CORS) post request works from plain javascript, but why not with jQuery
Asked Answered
G

10

253

I have a machine on my local lan (machineA) that has two web servers. The first is the in-built one in XBMC (on port 8080) and displays our library. The second server is a CherryPy python script (port 8081) that I am using to trigger a file conversion on demand. The file conversion is triggered by a AJAX POST request from the page served from the XBMC server.

  • Goto http://machineA:8080 which displays library
  • Library is displayed
  • User clicks on 'convert' link which issues the following command -

jQuery Ajax Request

$.post('http://machineA:8081', {file_url: 'asfd'}, function(d){console.log(d)})
  • The browser issues a HTTP OPTIONS request with the following headers;

Request Header - OPTIONS

Host: machineA:8081
User-Agent: ... Firefox/4.01
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: 115
Connection: keep-alive
Origin: http://machineA:8080
Access-Control-Request-Method: POST
Access-Control-Request-Headers: x-requested-with
  • The server responds with the following;

Response Header - OPTIONS (STATUS = 200 OK)

Content-Length: 0
Access-Control-Allow-Headers: *
Access-Control-Max-Age: 1728000
Server: CherryPy/3.2.0
Date: Thu, 21 Apr 2011 22:40:29 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, GET, OPTIONS
Content-Type: text/html;charset=ISO-8859-1
  • The conversation then stops. The browser, should in theory, issue a POST request as the server responded with the correct (?) CORS headers (Access-Control-Allow-Origin: *)

For troubleshooting, I have also issued the same $.post command from http://jquery.com. This is where I am stumped, from jquery.com, the post request works, a OPTIONS request is sent following by a POST. The headers from this transaction are below;

Request Header - OPTIONS

Host: machineA:8081
User-Agent: ... Firefox/4.01
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: 115
Connection: keep-alive
Origin: http://jquery.com
Access-Control-Request-Method: POST

Response Header - OPTIONS (STATUS = 200 OK)

Content-Length: 0
Access-Control-Allow-Headers: *
Access-Control-Max-Age: 1728000
Server: CherryPy/3.2.0
Date: Thu, 21 Apr 2011 22:37:59 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, GET, OPTIONS
Content-Type: text/html;charset=ISO-8859-1

Request Header - POST

Host: machineA:8081
User-Agent: ... Firefox/4.01
Accept: */*
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: 115
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Referer: http://jquery.com/
Content-Length: 12
Origin: http://jquery.com
Pragma: no-cache
Cache-Control: no-cache

Response Header - POST (STATUS = 200 OK)

Content-Length: 32
Access-Control-Allow-Headers: *
Access-Control-Max-Age: 1728000
Server: CherryPy/3.2.0
Date: Thu, 21 Apr 2011 22:37:59 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, GET, OPTIONS
Content-Type: application/json

I can't work out why the same request would work from one site, but not the other. I am hoping someone might be able to point out what I am missing. Thanks for your help!

Gerdi answered 21/4, 2011 at 23:1 Comment(3)
Is CORS needed if both web servers are on the same machine?Absenteeism
To the best of my knowledge it is a CORS request because of the different port. Also, the OPTIONS request indicates that the browser is treating it as a CORS requestGerdi
Possible duplicate of A CORS POST request works from plain javascript, but why not with jQuery?Uchish
G
176

I finally stumbled upon this link "A CORS POST request works from plain javascript, but why not with jQuery?" that notes that jQuery 1.5.1 adds the

 Access-Control-Request-Headers: x-requested-with

header to all CORS requests. jQuery 1.5.2 does not do this. Also, according to the same question, setting a server response header of

Access-Control-Allow-Headers: *

does not allow the response to continue. You need to ensure the response header specifically includes the required headers. ie:

Access-Control-Allow-Headers: x-requested-with 
Gerdi answered 22/4, 2011 at 7:33 Comment(0)
N
78

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
Nne answered 6/12, 2013 at 12:2 Comment(1)
if the server doesn't accept cross origin, the crossdomain=true does not have to solve the issue. using dataType:"jsonp" and setting the callback like jsonpCallback: "response" would be the better idea to do this. See also: api.jquery.com/jquery.ajaxSears
W
36

I solved my own problem when using google distance matrix API by setting my request header with Jquery ajax. take a look below.

var settings = {
          'cache': false,
          'dataType': "jsonp",
          "async": true,
          "crossDomain": true,
          "url": "https://maps.googleapis.com/maps/api/distancematrix/json?units=metric&origins=place_id:"+me.originPlaceId+"&destinations=place_id:"+me.destinationPlaceId+"&region=ng&units=metric&key=mykey",
          "method": "GET",
          "headers": {
              "accept": "application/json",
              "Access-Control-Allow-Origin":"*"
          }
      }

      $.ajax(settings).done(function (response) {
          console.log(response);

      });

Note what i added at the settings
**

"headers": {
          "accept": "application/json",
          "Access-Control-Allow-Origin":"*"
      }

**
I hope this helps.

Wrestling answered 13/4, 2018 at 9:42 Comment(4)
this is the only solution worked for us without changeing anything on server-side...thanx miracoolNeoarsphenamine
@Timsta, am happy my suggestion worked for you. Grateful to stack overflow also. Have a great day.Wrestling
setting the dataType: "jsonp" solved my issueMaxiemaxilla
It seems this does not work any more chromestatus.com/feature/5629709824032768Septi
L
14

Took me some time to find the solution.

In case your server response correctly and the request is the problem, you should add withCredentials: true to the xhrFields in the request:

$.ajax({
    url: url,
    type: method,
    // This is the important part
    xhrFields: {
        withCredentials: true
    },
    // This is the important part
    data: data,
    success: function (response) {
        // handle the response
    },
    error: function (xhr, status) {
        // handle errors
    }
});

Note: jQuery >= 1.5.1 is required

Lackluster answered 2/3, 2017 at 11:32 Comment(5)
jquery version ok? did you set the withCredentials: true? you sure you have the relevant headers?Lackluster
Yes, 1withCredential: true, jquery version: 3.2.1`. In fact it is working in through postman, but its not passing through chrome browserBasidiomycete
I'm almost sure postman should not have CORS problems because it isn't a browser and it's behavior is different. You sure you have the correct and relevant headers send from the server to the client? Again - note that this change is not enough. You need to make sure the server response with the correct headers.Lackluster
Could you tell me what are the response header required on server ?Basidiomycete
@MoxShah this is a complete different question and there are a lot of resources there :)Lackluster
T
9

Well I struggled with this issue for a couple of weeks.

The easiest, most compliant and non hacky way to do this is to probably use a provider JavaScript API which does not make browser based calls and can handle Cross Origin requests.

E.g. Facebook JavaScript API and Google JS API.

In case your API provider is not current and does not support Cross Origin Resource Origin '*' header in its response and does not have a JS api (Yes I am talking about you Yahoo ),you are struck with one of three options-

  1. Using jsonp in your requests which adds a callback function to your URL where you can handle your response. Caveat this will change the request URL so your API server must be equipped to handle the ?callback= at the end of the URL.

  2. Send the request to your API server which is controller by you and is either in the same domain as the client or has Cross Origin Resource Sharing enabled from where you can proxy the request to the 3rd party API server.

  3. Probably most useful in cases where you are making OAuth requests and need to handle user interaction Haha! window.open('url',"newwindowname",'_blank', 'toolbar=0,location=0,menubar=0')

Townsfolk answered 1/8, 2015 at 13:39 Comment(0)
S
7

This is a summary of what worked for me:

Define a new function (wrapped $.ajax to simplify):

jQuery.postCORS = function(url, data, func) {
  if(func == undefined) func = function(){};
  return $.ajax({
    type: 'POST', 
    url: url, 
    data: data, 
    dataType: 'json', 
    contentType: 'application/x-www-form-urlencoded', 
    xhrFields: { withCredentials: true }, 
    success: function(res) { func(res) }, 
    error: function() { 
            func({}) 
    }
  });
}

Usage:

$.postCORS("https://example.com/service.json",{ x : 1 },function(obj){
      if(obj.ok) {
           ...
      }
});

Also works with .done,.fail,etc:

$.postCORS("https://example.com/service.json",{ x : 1 }).done(function(obj){
      if(obj.ok) {
           ...
      }
}).fail(function(){
    alert("Error!");
});

Server side (in this case where example.com is hosted), set these headers (added some sample code in PHP):

header('Access-Control-Allow-Origin: https://not-example.com');
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Max-Age: 604800');
header("Content-type: application/json");
$array = array("ok" => $_POST["x"]);
echo json_encode($array);

This is the only way I know to truly POST cross-domain from JS.

JSONP converts the POST into GET which may display sensitive information at server logs.

Shamble answered 5/8, 2016 at 7:17 Comment(0)
S
4

Using this in combination with Laravel solved my problem. Just add this header to your jquery request Access-Control-Request-Headers: x-requested-with and make sure that your server side response has this header set Access-Control-Allow-Headers: *.

Sybarite answered 13/4, 2014 at 2:58 Comment(2)
There is no reason to add CORS headers to the request manually. The browser will always add the prop CORS headers to the request for you.Michaeline
The real challenge is getting the server to reply with a correct Access-Control-Allow-Headers and JQ supplying correct Access-Control-Request-Headers (plus any you add via code) neither of which can be wildcards. it only takes one "bad" header to blow up the pre-flight, e.g. using If-None-Match for a conditional GET, if server does not have that listed.Vaduz
P
2

I had the exact same issue where jquery ajax only gave me cors issues on post requests where get requests worked fine - I tired everything above with no results. I had the correct headers in my server etc. Changing over to use XMLHTTPRequest instead of jquery fixed my issue immediately. No matter which version of jquery I used it didn't fix it. Fetch also works without issues if you don't need backward browser compatibility.

        var xhr = new XMLHttpRequest()
        xhr.open('POST', 'https://mywebsite.com', true)
        xhr.withCredentials = true
        xhr.onreadystatechange = function() {
          if (xhr.readyState === 2) {// do something}
        }
        xhr.setRequestHeader('Content-Type', 'application/json')
        xhr.send(json)

Hopefully this helps anyone else with the same issues.

Pyoid answered 18/6, 2018 at 18:24 Comment(0)
N
0

If for some reasons while trying to add headers or set control policy you're still getting nowhere you may consider using apache ProxyPass…

For example in one <VirtualHost> that uses SSL add the two following directives:

SSLProxyEngine On
ProxyPass /oauth https://remote.tld/oauth

Make sure the following apache modules are loaded (load them using a2enmod):

  • proxy
  • proxy_connect
  • proxy_http

Obviously you'll have to change your AJAX requests url in order to use the apache proxy…

Narcotize answered 10/3, 2019 at 9:14 Comment(0)
H
-3

This is a little late to the party, but I have been struggling with this for a couple of days. It is possible and none of the answers I found here have worked. It's deceptively simple. Here's the .ajax call:

    <!DOCTYPE HTML>
    <html>
    <head>
    <body>
     <title>Javascript Test</title>
     <script src="http://code.jquery.com/jquery-latest.min.js"></script>
     <script type="text/javascript">
     $(document).domain = 'XXX.com';
     $(document).ready(function () {
     $.ajax({
        xhrFields: {cors: false},
        type: "GET",
        url: "http://XXXX.com/test.php?email='[email protected]'",
        success: function (data) {
           alert(data);
        },
        error: function (x, y, z) {
           alert(x.responseText + " :EEE: " + x.status);
        }
    });
    });
    </script> 
    </body>
    </html>

Here's the php on the server side:

    <html>
    <head>
     <title>PHP Test</title>
     </head>
    <body>
      <?php
      header('Origin: xxx.com');
      header('Access-Control-Allow-Origin:*');
      $servername = "sqlxxx";
      $username = "xxxx";
      $password = "sss";
      $conn = new mysqli($servername, $username, $password);
      if ($conn->connect_error) {
        die( "Connection failed: " . $conn->connect_error);
      }
      $sql = "SELECT email, status, userdata  FROM msi.usersLive";
      $result = $conn->query($sql);
      if ($result->num_rows > 0) {
      while($row = $result->fetch_assoc()) {
        echo $row["email"] . ":" . $row["status"] . ":" . $row["userdata"] .  "<br>";
      }
    } else {
      echo "{ }";
    }
    $conn->close();
    ?>
    </body>
Hundredweight answered 3/11, 2016 at 4:29 Comment(3)
For what it's worth, the Origin header is a request header, not a response header. Your php script should not be setting it.Roborant
Hmmph. This got my hopes up and then dashed them when I saw you're making an AJAX "GET" in your code, when the OP quite clearly said he was trying to AVOID using "GET" and wanted to use "POST".Thromboplastic
CORS headers work the same regardless of the verb involved. We are able to invoke all verbs via $.ajax() against a correctly-configured server. The hardest part is getting the Access-Control-Request-Headers correct, but even that is not too difficult. As noted by previous posters, this must not be a wildcard but a whitelist of headers.Vaduz

© 2022 - 2024 — McMap. All rights reserved.