6

So I am trying to submit data to an API endpoint using a $.ajax() call. I have verified that I can POST using a curl command, in the format of the following:

curl -X POST -H "Content-Type: application/json" -d "{\"uname\":\"myusername2\",\"emailAddr\":\"user2@ex.com\"}" <url to my API endpoint>

That curl command returns an empty JSON response (looks like {}), and my data gets put into the DB (which is what I configured the API Gateway endpoint to do).

However, when I try to do this using a $.ajax() command, which is programmed to fire on a button being clicked after form data is filled out, I can't get it to work.

    var obj = new Object();
    obj.uname = $uname;
    obj.emailAddr = $email;
    var stringedObj = JSON.stringify(obj);
    alert("stringified: " + stringedObj);
    $.ajax({
        url: '<same url as in curl>',
        type: 'POST',
        contentType: 'application/json',
        dataType: 'json',
        data: JSON.stringify(obj),
        success: function(data) {
            alert(JSON.stringify(data));
        },
        error: function(e) {
            alert("failed" + JSON.stringify(e));
        }
    });

Whenever I run this, I can see from the first alert message that my data is properly stringified. However, I always just get an error message that looks like this:

failed{"readyState":0,"responseText":"","status":0,"statusText":"error"}

I would like to know if I can get a more detailed error response. Also, I have tried with parseData: false and crossdomain: true. Once again, it works when I use curl so I am pretty sure this is not an API Gateway configuration issue.

Thanks for any and all help!

EDIT: I learned from the comment below about the javascript console. Now I have found out that this was due to CORS not being enabled. I enabled CORS in AWS Api Gateway, waited for that to complete, then changed my request to:

    $.ajax({
        url: '<my url>',
        type: 'POST',
        data: JSON.stringify(obj),
        dataType: 'json',
        crossDomain: true,
        success: function(data) {
            alert(JSON.stringify(data));
        },
        error: function(e) {
            alert("failed" + JSON.stringify(e));
        }
    });

And I get the same response in the javascript console about needing CORS. Other threads on CORS have shown this as the example. I am wondering if I need custom headers, although I tried that with adding headers: { "Access-Control-Allow-Origin": "*" } and still got the same response from the javascript console:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://k6i6wk8487.execute-api.us-west-2.amazonaws.com/prod/comments. (Reason: CORS header 'Access-Control-Allow-Origin' missing).
Community
  • 1
  • 1
Eric S
  • 171
  • 1
  • 7
  • 2
    Are there any errors in the Javascript console? Like errors about cross-domain not being allowed? – Barmar Nov 05 '16 at 20:26
  • 1
    I agree with @Barmar - CORS... – jcuenod Nov 05 '16 at 20:49
  • @Barmar Thanks for the tip! Yes, I found out that CORS is being rejected, so I enabled it in API gateway. However, I am still getting the message that the header Access-Control-Allow-Origin is missing. I have added crossDomain: true and dataType: 'json' set and I get the same error message from the javascript console. Other threads on StackOverflow are saying that adding those two options are all that's necessary. Do I need to add custom headers, and if so what? I tried with headers: { "Access-Control-Allow-Origin": "*" } but that didn't help either. Thanks again – Eric S Nov 05 '16 at 23:56
  • `Access-Control-Allow-Origin: "*"` needs to be set at the endpoint, not by your Ajax request. – AM Douglas Nov 06 '16 at 00:11
  • Ok, that's what I thought, I do have AWS Api Gateway set to CORS Enabled so this should be taken care of. With just crossDomain: true and dataType: 'json' I still get the error however. – Eric S Nov 06 '16 at 00:27
  • Hi Eric. Just so you know the way this site is that if you find an answer to your question then you should submit it as an answer rather than editing your question. This way the Question and Answer structure is maintained and it has the bonus that upvotes on your answer will get you rep! – Chris Nov 06 '16 at 02:38
  • 1
    The `Access-Control-Allow-Origin` header needs to be configured on your AWS server. While @amdouglas provides one option for specifying this, I would suggest that you lock that header down to the single necessary domain as an extra security measure (i.e. `Access-Control-Allow-Origin: http://www.example.com`) as oppose to wild-carding it for all domains... – War10ck Nov 06 '16 at 02:55
  • Yes, definitely do as @War10ck advises. – AM Douglas Nov 06 '16 at 02:59

2 Answers2

5

Figured it out, I had not hit 'deploy' in AWS API Gateway after updating it to Enable CORS. Doh! Thanks to all who helped. Here is my final ajax call:

$.ajax({
                url: $invokeUrl,
                type: 'POST',
                data: JSON.stringify(obj),
                dataType: 'json',
                crossDomain: true,
                contentType: 'application/json',
                success: function(data) {
                    alert(JSON.stringify(data));
                },
                error: function(e) {
                    alert("failed" + JSON.stringify(e));

Thanks to all who helped, and let me know if you are running into this issue with API Gateway and I will try to help.

Eric S
  • 171
  • 1
  • 7
  • Actually, your Doh! comment helped me the most.. because when creating the resource in the first place, you ARE given the option to enable CORS. So when you do that, you don't realise that you still have to enable CORS through the action (..and then then re-deploy..). Thanks. – Randhir Rawatlal Aug 25 '20 at 19:19
0

You can make the request with the JSONP technique:

<script>
  function parseJSONP(response) {
    // Process the JSON response here.
  }
  function loadScript(url, data, callback) {
    script = document.createElement('script');
    document.getElementsByTagName('head')[0].appendChild(script);
    script.src = url
      +'?callback='+callback
      +'&data='+encodeURIComponent(JSON.stringify(data))
    ;
  }
  loadScript('http://yourdifferentdomain', yourdata, 'parseJSONP');
</script>

You may need to tweak the remote service to accept and decode the data parameter in the URL.

Another approach, probably better, would be wrapping your foreign URL as a parameter, part of the POST data to your local CURL, letting the server fetch the other domain's response, and putting it back in the response from the same domain. Following your example:

JS:

var curlBridge = 'http://samedomain/curl-resource/';
var remoteURL = 'http://otherdomain/target-resource/';
var obj = {}; // Your data to cross-domain post.
$.ajax({
  url: curlBridge,
  type: 'POST',
  data: JSON.stringify({
    "url": remoteURL,
    "headers": null, // Headers you may want to pass
    "post": obj
  }),
  dataType: 'json',
  crossDomain: true,
  contentType: 'application/json',
  success: function(data) {
    alert(JSON.stringify(data));
  },
  error: function(ev) {
    alert("failed" + JSON.stringify(ev));
  }
});

PHP:

<?php
function callUrl($url, $post = null, $headers = null) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
    curl_setopt($ch, CURLOPT_VERBOSE, 0);
    curl_setopt($ch, CURLOPT_TIMEOUT_MS, 60000); // DEF 1000*60... // Hide/Show
    $post_str = "";
    if (is_array($post)) {
        if (count($post)) {
            foreach ($post as $key => $val) {
                if (is_array($val) && count($val)) {$val = json_encode($val);}
                $post_str .= "&".$key."=".$val;
            }
            if ($post_str[0] == "&") {$post_str = substr($post_str, 1);}
        }
    } else {$post_str = "".$post;}
    if ($post_str || is_array($post)) {
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $post_str);
    }
    if (is_array($headers) && count($headers)) {
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    } else {$headers = array();}
    $result = curl_exec($ch);
    curl_close($ch);
    return $result;
}

$input = array_merge($_GET, $_POST);
echo callUrl($input['url'], $input['post'], $input['headers']);
Javier Rey
  • 1,539
  • 17
  • 27
  • Thanks for your reponse Javier. However, is that code snippet a GET request? Is there a way to convert it to a POST? In the last paragraph do you mean constructing two separate calls to the server because of CORS? I haven't seen any other examples do two separate calls, although that would make sense, one for OPTIONS and one for POST. Thanks again. – Eric S Nov 06 '16 at 15:14
  • Unfortunately not. For cross-domain posting I would rather use the second approach. i did it a few years ago, in PHP. It's a CURL-based bridge between the local client and the remote resource. Short example added to the answer. – Javier Rey Nov 06 '16 at 18:41