1

Update: Code updated with the solution based on Eamonn Gahan's answer

I'm trying to fetch some files that are user generated and can't be trusted to be properly formatted. I thought this would be straightforward, but I'm stumped on what to do when the response is kind of like JSON, but isn't.

For example if a server responds with: a simple string {with brackets} everything works as expected. But, if the server responds with: { not_json: { malformed on purpose }, then I can't figure out how to get access to the response, either as a success or failure response.

What seems to happen is the Angular JSON parser throws a Syntax error without rejecting the $http promise.

Below is some code that highlights the issue.

var myApp = angular.module('myApp', []);

function MyCtrl($scope, $http){

    // --- Added this block based on Eamonn Gahan's answer
    function txfmParseText(data, hdrGetter){
        var contentType = hdrGetter()['content-type'];
        if ( contentType.match(/^text\/plain/) ){
            data = JSON.stringify(data);
        } 
        return data ;
    }

    $http.defaults.transformResponse.unshift(txfmParseText);
    // --- end of updated block

    $scope.sanityCheck="Alive";

    // URL returns (as text/plain):
    //   { not_json: { malformed on purpose }
    var urlOpts1 = {
        method: 'GET',
        url: 'https://s3.amazonaws.com/forforf-cdn/not_json_test'
    };

    // URL returns (as text/plain):
    //   a simple string {with brackets}
    var urlOpts2 = {
        method: 'GET',
        url: 'https://s3.amazonaws.com/forforf-cdn/simple_string_with_brackets'
    };

    // First request fails  [update: passes with transformResponse function]
    $http(urlOpts1)
    .then(function(resp){
        //never gets here
        console.log(resp);
        $scope.notJson = resp.data;
    })
    .catch(function(err){
        //nor is the promise rejected
        console.log(err)
    });

    // Second request works as expected
    $http(urlOpts2).then(function(resp){
        //works as expected
        console.log(resp);
        $scope.stringWithBrackets = resp.data;
    });

}

and the jsFiddle

updated jsFiddle with solution

Here's the HTTP of the failing Request and Response. The data is being served by S3, and the content type of the data is set to text/plain.

Request URL:https://s3.amazonaws.com/forforf-cdn/not_json_test
Request Method:GET
Status Code:304 Not Modified

Request Headers
GET /forforf-cdn/not_json_test HTTP/1.1
Host: s3.amazonaws.com
Connection: keep-alive
Cache-Control: max-age=0
Accept: application/json, text/plain, */*
Origin: http://fiddle.jshell.net
User-Agent: Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.107 Safari/537.36
Referer: http://fiddle.jshell.net/_display/
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
If-None-Match: "84cf04d01a85ed58af77293ea7b1884a"
If-Modified-Since: Sat, 15 Feb 2014 20:52:30 GMT

Response Headers
HTTP/1.1 304 Not Modified
x-amz-id-2: FvdtpH6WmRvEAFIt3clAsXC133iyGQ/Qezlzt/5P6UDFbZvDfUC7WeuPv+re0ywE
x-amz-request-id: FA51FED4F6A70DA2
Date: Sat, 15 Feb 2014 22:02:22 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET
Access-Control-Max-Age: 3000
Vary: Origin, Access-Control-Request-Headers, Access-Control-Request-Method
Last-Modified: Sat, 15 Feb 2014 20:52:30 GMT
ETag: "84cf04d01a85ed58af77293ea7b1884a"
Server: AmazonS3

Thinking it might be S3 wasn't setting the proper Content-Type, I also had S3 respond with: Content-Type: text/plain; charset="utf-8", but it didn't help.

Anyone have any ideas?

1 Answer 1

3

This is basically happening because what angular does on a $http response is pass it through an array of functions on the $httpProvider.defaults.transformRequest property. As you can see here: https://github.com/angular/angular.js/blob/7aef2d54e0a48fae18a289813f699962d8310565/src/ng/http.js#L94 there's one default function in the array. This function checks against some basic regexs to see if it should JSON.parse() the response. The malformed one unfortunately passes but you can just overwrite the transformResponse property in a config block or you can specify a transformResponse property on the actual $http call like this (from your fiddle):

var urlOpts1 = { method: 'GET', url: 'https://s3.amazonaws.com/forforf-cdn/not_json_test', transformResponse: specialTransform };

http://jsfiddle.net/GBD2v/

In the fiddle above you can see that "{ not_json: { malformed on purpose }" gets printed as a regular string because we just returned the string straight in the custom transformResponse function.

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks! I had tried tranformRequest to set the Accept header to just text/plain (it didn't work), but it didn't occur to me to do it on the response.
Np! BTW if you go with the config option you might want to do something like $http.defaults.transformResponse.unshift(someFunc); as well, rather than overwriting the entire property with your single function.
Yes I ended up going with adding a custom function to the front of the $http.defaults chain and updated my question with that solution. What I did was to check the Content-Type, and if it was plain text, I JSON.stringify the data, so that the Angular JSON parser would treat it like a string. This way the request could handle any plain text or JSON reponse in a predictable way. In the off chance that JSON was sent as plain text, I could always just JSON.parse it again (which seems appropriate to me).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.