Search code examples
jsonrestamazon-web-servicesxmlhttprequestfirefox-addon-webextensions

Error when accessing rest service from WebExtension using XMLHttpRequest


I am trying to access a rest service that I am hosting on an amazon AWS server from a firefox WebExtension.

I have registered a background script in the manifest.json which then tries to access the service.

"background": {
    "scripts": ["OwnerLangBackground.js"]
},
"permissions": [
    "*://ec2-35-158-91-62.eu-central-1.compute.amazonaws.com:9000/*"
]

However, the XMLHttpRequest just returns an error but I don't see what goes wrong. While researching this issue, I stumbled across the following page: https://mathiasbynens.be/notes/xhr-responsetype-json

Replacing my own code with a (slightly modifed) copy of the code from the above link I now have:

// OwnerLangBackground.js
console.log("OwnerLangBackground.js loaded");
var getJSON = function(url, successHandler, errorHandler) {
    var xhr = new XMLHttpRequest();
    xhr.open('get', url, true);
    xhr.onreadystatechange = function() {
        var status;
        var data;
        // https://xhr.spec.whatwg.org/#dom-xmlhttprequest-readystate
        if (xhr.readyState == 4) { // `DONE`
            status = xhr.status;
            if (status == 200) {
                data = JSON.parse(xhr.responseText);
                successHandler && successHandler(data);
            } else {
                errorHandler && errorHandler(status, xhr.responseText);
            }
        }
    };
    xhr.send();
};

/* BLOCK 1: removing the comments for this block works
getJSON('https://mathiasbynens.be/demo/ip', function(data) {
    console.log('Your public IP address is: ' + data.ip);
    console.log('Your response is: ', data);
}, function(status) {
    console.warn('Something went wrong.', status);
});
*/

/* BLOCK 2: removing the comments for this block, does not work
getJSON('http://ec2-35-158-91-62.eu-central-1.compute.amazonaws.com:9000/get-languages', function(data) {
    console.log('Your response is: ', data);
}, function(status) {
    console.warn('Something went wrong.', status);
});
*/

Strangely enough, activating BLOCK 1 works as expected (ip address obscured on purpose).

    OwnerLangBackground.js loaded
    Your public IP address is: xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xx
    Your response is:  Object { ip: "xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:…" }

Activating BLOCK 2 results in the error response.

    OwnerLangBackground.js loaded
    Something went wrong. 0

However, if I call the two URLs using curl directly, they both return valid JSON:

    > curl https://mathiasbynens.be/demo/ip
    {"ip":"xxxx:xxxx:xxxx::xxx"}

    > curl http://ec2-35-158-91-62.eu-central-1.compute.amazonaws.com:9000/get-languages
    [{"language":"??"},{"language":"de"},{"language":"en"},{"language":"fr"},{"language":"it"}]

I have added debugging output to my rest service on the AWS server and I see that it gets called. I also traced the WebExtension call to the rest service using Wireshark on my local machine on which the WebExtension is running and I can see the JSON string being returned, so I am guessing that the error occurs somewhere within firefox/the webextension, but I am at a total loss.

Things I have considered:

  • Permissions in the manifest: as far as I can tell the URL pattern for my aws-url is correctly added. However, the call to mathiasbynens.be works even though I have not added the url to the permissions
  • the call that works uses https while the call that does not work uses http. Could this be the reason?

Can anyone point me in the right direction to get more feedback on what goes wrong? I've tried adding a onerror callback to the xhr request. It is called but as far as I can see doesn't provide more information.

UPDATE: I've come up with two more ideas. Using curl -v provided me with the headers:

> curl -v http://ec2-35-158-91-62.eu-central-1.compute.amazonaws.com:9000/get-languages
* Hostname was NOT found in DNS cache
*   Trying 35.158.91.62...
* Connected to ec2-35-158-91-62.eu-central-1.compute.amazonaws.com (35.158.91.62) port 9000 (#0)
> GET /get-languages HTTP/1.1
> User-Agent: curl/7.38.0
> Host: ec2-35-158-91-62.eu-central-1.compute.amazonaws.com:9000
> Accept: */*
>
< HTTP/1.1 200
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Sun, 23 Apr 2017 06:43:42 GMT
<
* Connection #0 to host ec2-35-158-91-62.eu-central-1.compute.amazonaws.com left intact
[{"language":"??"},{"language":"de"},{"language":"en"},{"language":"fr"},{"language":"it"}]


> curl -v https://mathiasbynens.be/demo/ip
* Hostname was NOT found in DNS cache
*   Trying 2a01:1b0:7999:402::144...
* Connected to mathiasbynens.be (2a01:1b0:7999:402::144) port 443 (#0)
* successfully set certificate verify locations:
*   CAfile: none
  CApath: /etc/ssl/certs
* SSLv3, TLS handshake, Client hello (1):
* SSLv3, TLS handshake, Server hello (2):
* SSLv3, TLS handshake, CERT (11):
* SSLv3, TLS handshake, Server key exchange (12):
* SSLv3, TLS handshake, Server finished (14):
* SSLv3, TLS handshake, Client key exchange (16):
* SSLv3, TLS change cipher, Client hello (1):
* SSLv3, TLS handshake, Finished (20):
* SSLv3, TLS change cipher, Client hello (1):
* SSLv3, TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* Server certificate:
*        subject: OU=Domain Control Validated; OU=PositiveSSL Wildcard; CN=*.mathiasbynens.be
*        start date: 2015-07-28 00:00:00 GMT
*        expire date: 2018-08-12 23:59:59 GMT
*        subjectAltName: mathiasbynens.be matched
*        issuer: C=GB; ST=Greater Manchester; L=Salford; O=COMODO CA Limited; CN=COMODO RSA Domain Validation Secure Server CA
*        SSL certificate verify ok.
> GET /demo/ip HTTP/1.1
> User-Agent: curl/7.38.0
> Host: mathiasbynens.be
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Sun, 23 Apr 2017 06:44:16 GMT
* Server Apache is not blacklisted
< Server: Apache
< Access-Control-Allow-Origin: *
< Strict-Transport-Security: max-age=15768000; includeSubDomains
< Vary: Accept-Encoding
< Cache-Control: max-age=0
< Expires: Sun, 23 Apr 2017 06:44:16 GMT
< X-UA-Compatible: IE=edge
< X-Content-Type-Options: nosniff
< X-Frame-Options: DENY
< X-XSS-Protection: 1; mode=block
< Transfer-Encoding: chunked
< Content-Type: application/json;charset=UTF-8
<
* Connection #0 to host mathiasbynens.be left intact
{"ip":"xxxx:xxxx:xxxx::xxx"}

The one difference that stuck out was that my rest service's response lacks the Transfer-Encoding and Access-Control-Allow-Origin? headers, so I'll look into adding those.

Still, if anyone has a hint on how to get more error information for what goes wrong with XmlHttpRequest I'd be glad to hear it.


Solution

  • Ok, it seems the missing Access-Control-Allow-Origin? header was the root of my problems.

    I have now changed all methods in my Spring-RestControllers by adding another method parameter HttpServletResponse response and then calling setHeader() on that parameter.

    @RequestMapping("/get-languages")
    public @ResponseBody List<Language> getLanguages(HttpServletResponse  response) {
        response.setHeader("Content-Type", "application/json;charset=UTF-8");
        response.setHeader("Access-Control-Allow-Origin", "*");
        return languageRepository.findAll();
    }
    

    Now my WebExtension can use this rest service sucessfully using XmlHttpRequest.

    It would have been helpful if this information (that the CORS header was missing) had been visible somewhere in firefox's debugging or js console, so if anyone can tell me how I could have seen this, I'd still appreciate a hint.