How to handle Bad JSON in Firebase Cloud Functions? - javascript

I'm creating a firebase application which uses firebase-cloud-functions.
index.js
exports.auth = functions.https.onRequest((request, response) => {
response.status(200).send({
status : "Some Status"
});
}
This is very simple functions. I want to make a POST request on the endpoint with some payload. When I tested the API using Firebase Cloud Function Emulator and POSTman with bad json
{
"phoneNumber: "9632725300"
}
The server just crashed! My question is how to handle the bad request in firebase functions like these.
with this error

The server did not crash. You have sent it a bad request (malformed JSON) and it responded perfectly with a status code 400 which is "Bad Request".
You'd rather correct your JSON...
EDIT:
If you really wanted to be able to send invalid JSON, you could do so by circumventing the JSON body parser. To do so, you could either change your request to have a content-type header set to "text/plain". This content-type will use the text body parser, which will not parse any JSON.
Note that doing so will require you to handle the JSON parsing yourself, but will permit to handle to error yourself using a try-catch.
let json;
try {
json = JSON.parse(json);
} catch (e) {
// Handle JSON error.
}
Taken from https://firebase.google.com/docs/functions/http-events

What you're experiencing is not actually a server crash. In fact, technically, by using Cloud Functions, you don't have a server to crash. (For this reason they're called "Serverless Infrastructure") Each request / operation you perform on Cloud Functions is kind of like a brand new server. Which is actually what's fantastic about Cloud Functions in general. (This is an overly simplified explanation, I'd suggest reading up a bit more about it for a better in depth explanation)
That being said, from what I understand you're trying to figure out if the JSON you got is invalid (bad) or not. Occasionally, when I have to hook up a bunch of external services, rarely, but sometimes, they return a bad JSON that my Cloud Functions can't parse, therefore throws an error.
The solution is to put your JSON.parse in to a separate function and a try / catch block like this:
function safelyParseJSON (json) {
var parsed;
try {
parsed = JSON.parse(json);
} catch (e) {
// BAD JSON, DO SOMETHING ABOUT THIS HERE.
}
return parsed; // will be undefined if it's a bad json!
}
function doSomethingAwesome () {
var parsedJSON = safelyParseJSON(data);
// Now if parsedJSON is undefined you know it was a bad one,
// And if it's defined you know it's a good one.
}
With this helper function, if you have to deal with a lot of external JSON resources, you can easily determine if the JSON you're trying to parse is good, and if not, you can at least handle the error your way.
Hope this helps :)

{\n\t"phoneNumber: "9632725300"\n}
From the screenshot, I see that the JSON is invalid or malformed. It contains newline (\n) and tab space (\t) characters. Also, the key "phoneNumber" is not wrapped in double quotes, which again invalidates the JSON.
Here's a valid format of the JSON that the server should receive
{
"phoneNumber": "9632725300"
}

Related

Do "POST" requests return XML data?

I am struggling to understand when does the server return XML data vs HTML text data, and how come responseXML can return both? Is XML data returned only when making a POST request?
It's hard to actually test these, because I couldn't properly setup a PHP server, and making a POST request keeps returning 404 bad request, but when I make a GET request, I always get the HTML document in the responseText property, but when I try to use responseXML, I get null. So, if responseXML can return either HTML or XML, why does it not return the HTML document then?
Note: Before you accuse me of not doing any research. Let me tell you that I have been doing research for the past 3 days, and the book I'm reading just doesn't clarify these differences, and does not explain what exactly is XML in the first place. It says that XML data needs to be parsed to be displayed as a text, but doesn't explain why. It's all very ambiguous. So, I would appreciate if someone could clarify things for me.
POST requests return XML data, if the backend server is configured to return XML data. Completely depends on the server you're talking to, there's no way of predicting the behavior otherwise. Also, it's worth noting that every input can change the behavior of the server. E.g. if you provide a query with a specific value, the server could also return a CSS file, instead of an HTML or XML one.
This is an example of getting HTML from server response using fetch API.
fetch('https://someweb.com/api/list').then(function (response) {
// The API call was successful!
return response.text();
}).then(function (html) {
// Convert the HTML string into a document object
var parser = new DOMParser();
var doc = parser.parseFromString(html, 'text/html');
}).catch(function (err) {
// There was an error
console.warn('Something went wrong.', err);
});

Good way to parse error object in catch(e) statement after API call?

When using an api I often find myself with a rather complicated error object.
Depending on the API that I am using the error texts are quite helpful and I would actually sometimes like to display them directly to the user. The problem, of course, is that the error objects can look quite differently so it would be very verbose to go through them and pick individual objects in case they exists (dependant on the status code of the error).
Is this just the nature of the error object or is there a better way to do this?
What I do to handle API calls that end up with error is this:
try {
const response = await axios.post("Your URL");
// Your code to handle the result
} catch (error) {
console.log(error.response.data.error)
// Code to display the error to the user
}
error.response.data.error is the actual error message sent from the server, not the error code

Node.js http.get xml is returned 'escaped'

I am working on a simple AWS Lambda function in Javascript (Node 6.x) which should 'proxy' an RSS of an italian news provider.
This is the code of the function:
var http = require("http")
exports.handler = (event, context, callback) => {
http.get("http://www.milanotoday.it/rss/", (response) => {
response.setEncoding("utf8")
let xml = ""
response.on("data", (chunk) => { xml += chunk })
response.on("end", () => { callback(null, xml) })
})
}
It works, or at least it loads the response inside the xml variable.
I can't get why the string is something like this:
"<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<rss version=\"2.0\" xmlns:content=\"http://purl.org/rss/1.0/modules/content/\"...
It seems to be sort of 'escaped'.
Can somebody help me?
Thank you in advance...
http.get is probably working just fine. If you run your code outside of the AWS ecosystem, you'll see that you are getting unescaped XML from http.get. The problem is that Lambda wants you to return JSON so when you pass the string to the callback it gets escaped.
If you are running this function through API Gateway you can do a transform in the integration response. The way to do this is to return an object from your lambda:
callback(null, {myXML: xml}))
Then in the API Gateway go to the integration response area under your GET (or POST) resource and click the arrow next to the 200 response. This should reveal an area for body mappings. You want to add a mapping for application/xml and then add something like:
#set($inputRoot = $input.path('$'))
$inputRoot.myXML
This should get you nice clean XML. It's a little hard to describe so I'll post a screen shot that might help:
Double quotes have to be escaped inside a double quoted string (Makes sense, right?). Now you just have to parse the XML, using for instance xml2js

Uncaught (in promise) SyntaxError: Unexpected end of JSON input

I am trying to send a new push subscription to my server but am encountering an error "Uncaught (in promise) SyntaxError: Unexpected end of JSON input" and the console says it's in my index page at line 1, which obviously is not the case.
The function where I suspect the problem occurring (because error is not thrown when I comment it out) is sendSubscriptionToBackEnd(subscription) which is called in the following:
function updateSubscriptionOnServer(subscription) {
const subscriptionJson = document.querySelector('.js-subscription-json');
const subscriptionDetails = document.querySelector('.js-subscription-details');
if (subscription) {
subscriptionJson.textContent = JSON.stringify(subscription);
sendSubscriptionToBackEnd(subscription);
subscriptionDetails.classList.remove('is-invisible');
} else {
subscriptionDetails.classList.add('is-invisible');
}
}
The function itself (which precedes the above function):
function sendSubscriptionToBackEnd(subscription) {
return fetch('/path/to/app/savesub.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(subscription)
})
.then(function(response) {
if (!response.ok) {
throw new Error('Bad status code from server.');
}
return response.json();
})
.then(function(responseData) {
if (!(responseData.data && responseData.data.success)) {
throw new Error('Bad response from server.');
}
});
}
I have tried replacing single quotes with double quotes in the fetch call but that yields the same results.
I know that the JSON should be populated because it prints to the screen in the updateSubscriptionOnServer() function with subscriptionJson.textContent = JSON.stringify(subscription);, and I used that output in the google codelab's example server to receive a push successfully.
EDIT: Here is the JSON as a string, but I don't see a mistake in syntax:
{"endpoint":"https://fcm.googleapis.com/fcm/send/dLmthm1wZuc:APA91bGULRezL7SzZKywF2wiS50hXNaLqjJxJ869y8wiWLA3Y_1pHqTI458VIhJZkyOsRMO2xBS77erpmKUp-Tg0sMkYHkuUJCI8wEid1jMESeO2ExjNhNC9OS1DQT2j05BaRgckFbCN","keys":{"p256dh":"BBz2c7S5uiKR-SE2fYJrjPaxuAiFiLogxsJbl8S1A_fQrOEH4_LQjp8qocIxOFEicpcf4PHZksAtA8zKJG9pMzs=","auth":"VOHh5P-1ZTupRXTMs4VhlQ=="}}
Any ideas??
This might be a problem with the endpoint not passing the appropriate parameters in the response's header.
In Chrome's console, inside the Network tab, check the headers sent by the endpoint and it should contain this:
Example of proper response to allow requests from localhost and cross domains requests
Ask the API developer to include this in the headers:
"Access-Control-Allow-Origin" : "*",
"Access-Control-Allow-Credentials" : true
This happened to me also when I was running a server with Express.js and using Brave browser. In my case it was the CORs problem. I did the following and it solved the problem in my case:
(since this is an Express framework, I am using app.get)
-on the server side:
res.set({
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
});
-on client side I used Fetch to get data but disabled the CORS option
// mode: "no-cors" //disabled this in Fetch
That took care of my issues with fetching data with Express
This can be because you're not sending any JSON from the server
OR
This can be because you're sending invalid JSON.
Your code might look like
res.end();
One of the pitfalls is that returned data that is not a JSON but just a plain text payload regardless of headers set. I.e. sending out in Express via something like
res.send({a: "b"});
rather than
res.json({a: "b"});
would return this confusing error. Not easy to detect in network activity as it looks quite legit.
For someone looking here later. I received this error not because of my headers but because I was not recursively appending the response body to a string to JSON.parse later.
As per the MDN example (I've taken out some parts of their example not immediately relevant):
reader.read().then(function processText({ done, value }) {
if (done) {
console.log("Stream complete");
return;
}
result += chunk;
return reader.read().then(processText);
});
For my issue I had to
Use a named function (not an anonymous ()=>{}) inside the .then
Append the result together recursively.
Once done is true execute something else on the total appended result
Just in case this is helpful for you in the future and your issue is not header related, but related to the done value not being true with the initial JSON stream response.
I know this question has already been answered but just thought I add my thoughts.
This will happen when your response body is empty and response.json() is expecting a JSON string. Make sure that your API is returning a response body in JSON format if must be.

ngResource transformResponse how to handle both error and normal data

My API implemented with expressJS returns json as normal response, but when an error occurred, returns error code with a plain text, by simply calling res.sendStatus(401).
This causes a problem on my frontend, I use angular ngResoruce, and the resource code is like this:
svc.authenticateApi = function() {
return $resource(apiEndpoint + 'authenticate', null, {
'save': {
method: 'POST',
transformResponse: function (data, header) {
console.log("transformResponse, header:", header());
console.log("transformResponse, data:", data);
return { data: angular.fromJson(data) };
},
},
});
};
This works fine when normal JSON data is returned, however, when error status is returned, the data parameter is not a serialised JSON string, it's plain text, and I get errors like this:
Apparently transformResponse tries to parse text Unauthorised as JSON and failed. I can work around this by sending every error response as JSON on the server end by calling something like `res.status(401).send({error: "Unauthorised"}, but that feels like a hack, and I don't want to manually repeat error text for each status code.
Is there a better way to handle this? I don't want it sounds like a rant, but ngResource document is really poor and I start to think using $http is a much better solution.
I've moved away from $resource and am using $http everywhere. I just find everything about $resource to be a bit wonky. I do think returning JSON from the server in all scenarios sounds like the best approach though. You could implement a custom handler to test your incoming responses to work around this, but that doesn't really feel right to me.
I'd guess that the error is occurring on the return { data: angular.fromJson(data) }; line though. You could wrap this in a try/catch as well.
I did find success following the suggestion by Mike Feltman to wrap the fromJson call in a try / catch. The idea is if there is an error, there's no need to bother transforming the response.
function transformIgnoringErrors(original) {
var data;
try {
data = angular.fromJson(original);
/* code to perform transformation */
} catch (err) {
/* return what came in because no need to transform */
data = original;
}
return data;
}

Categories