I'm trying to return a JSON result to my client using IHttpActionResult.
My .Net code, looks like this:
[AllowAnonymous, HttpPost, Route("")]
public IHttpActionResult Login(LoginRequest login)
{
if (login == null)
return BadRequest("No Data Provided");
var loginResponse = CheckUser(login.Username, login.Password);
if(loginResponse != null)
{
return Ok(new
{
message = "Login Success",
token = JwtManager.GenerateToken(login.Username, loginResponse.Roles),
success = true
});
}
return Ok( new
{
message = "Invalid Username/Password",
success = false
});
}
This doesn't work though, as I never seem to see the JSON on the response after my JavaScript fetch:
const fetchData = ( {method="GET", URL, data={}} ) => {
console.log("Calling FetchData with URL " + URL);
var header = {
'Content-Type': "application/json",
}
// If we have a bearer token, add it to the header.
if(typeof window.sessionStorage.accessToken != 'undefined')
{
header['Authorization'] = 'Bearer ' + window.sessionStorage.accessToken
}
var config = {
method: method,
headers: header
};
// I think this adds the data payload to the body, unless it's a get. Not sure what happens with a get.
if(method !== "GET") {
config = Object.assign({}, config, {body: JSON.stringify(data)});
}
// Use the browser api, fetch, to make the call.
return fetch(URL, config)
.then(response => {
console.log(response.body);
return response;
})
.catch(function (e) {
console.log("An error has occured while calling the API. " + e);
});
}
There is no JSON available in the body.
How do I get aresult back to my client to parse? response.body doesn't have the json object.
The console.log shows:
While the request/response shows:
Using striped's advice: console.log(response.json())
I see the message there. It seems to be in the wrong place. Shouldn't it be in the body?
Fetch works like this
Body methods
Each of the methods to access the response body returns a Promise that
will be resolved when the associated data type is ready.
text() - yields the response text as String
json() - yields the result of JSON.parse(responseText)
blob() - yields a Blob
arrayBuffer() - yields an ArrayBuffer
formData() - yields FormData that can be forwarded to another request
I think you need to
return fetch(URL, config)
.then(response => response.json())
.catch(e => console.log("An error has occured while calling the API. " + e));
doc here: https://github.github.io/fetch/
Your are making a GET request but your controller method is expecting a POST request.
Related
I was writing a little wrapper for fetch method in JavaScript (I am very aware of libs like Axios that can do the same thing). I got the idea from a blog post
My code looks like this
async function apiCall(
endpoint,
{ data, headers: customHeaders, ...customConfig } = {}
) {
console.log("endpoint", endpoint);
const config = {
method: data ? "POST" : "GET",
body: data ? JSON.stringify(data) : undefined,
headers: {
"content-type": data ? "application/json" : undefined,
...customHeaders
},
...customConfig
};
return window.fetch(endpoint, config).then(async (response) => {
if (response.ok) {
return response.json();
} else {
// By default, window.fetch will only reject a promise if the actual request itself failed (network error), not if it returned a "Client error response".
const error = await response
.json()
.catch(() => new Error("invalid json"));
return Promise.reject(error);
}
});
}
export function requestMovies(query) {
const endpoint = `${apiULR}?apikey=${API_KEY}&s=${encodeURIComponent(query)}`;
return apiCall(endpoint);
}
However, I encountered TypeError Failed to fetch which I believed is caused by CORS.
If I take out config from window.fetch as in
async function apiCall(
endpoint,
{ data, headers: customHeaders, ...customConfig } = {}
) {
return window.fetch(endpoint).then(async (response) => {
if (response.ok) {
return response.json();
} else {
// By default, window.fetch will only reject a promise if the actual request itself failed (network error), not if it returned a "Client error response".
const error = await response
.json()
.catch(() => new Error("invalid json"));
return Promise.reject(error);
}
});
}
The problem would be gone. Not sure which part exactly triggered this CORS problem...
Here is a live demo: https://codesandbox.io/s/charming-saha-4c2bh?file=/src/index.js
follow the data not given path:
the ternary goes into the false case
headers gets an entry content-type: undefined
the request gets this header added
request is rejected by api because it contains a content-type header (probably with the string 'undefined' in it)
Solution: Dont use a ternary here, and replace it with an if, to get rid of the undefined entry.
Also: read up on differences between null, undefined values and "has own property" in javascript objects
I have the following React Native client code:
confirmMatchRecord(userId, matchedUserData['userId'], matchRecordData['matchRecord']['matchWinner'], matchRecordData['matchType'], currentUserRating, matchedUserRating, matchData['_id'], matchRecordData['matchRecord']['_id'], airbnbRatingValue, true, new Date())
.then((results) => {
// Do stuff
})
.catch((error) => {
Alert.alert('Error', 'There was an issue with confirming the record. Please check your connection and/or try again later.');
});
And the following code in my confirmMatchRecord function:
export async function confirmMatchRecord(userId, matchedUserId, matchWinner, matchType, currentUserRating, matchedUserRating, matchId, matchRecordId, matchRating, matchConfirmed, timestamp) {
console.log('Attempting to record match');
info = { userId, matchedUserId, matchWinner, matchType, currentUserRating, matchedUserRating, matchId, matchRecordId, matchRating, matchConfirmed, timestamp }
const firebaseIdToken = await AsyncStorage.getItem('#firebaseIdToken')
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + firebaseIdToken },
body: JSON.stringify(info)
};
const response = await fetch(ngrokOrLocalhost + '/confirmmatchrecord', requestOptions)
if (response['Status']==='Failure') {
// throw new Error(`HTTP error! status: ${response.status}`);
throw new Error(400);
} else if (response['Status']==='Success') {
const data = await response.json()
return data
}
}
Server code:
router.post('/confirmmatchrecord', async (req, res) => {
// Do a lot of stuff
if (response==='Success') {
return res.status(200).json({'Status': 'Success'})
} else {
return res.status(400).json({'Status': 'Failure'})
console.log('Match record was not confirmed successfully');
}
When response['Status']==='Failure (sent by server) it throws an error 400 as you can see, I was hoping to trigger the .catch in the client code then. But that does not happen, because the client code continues to run on the .then part.
How should I do this instead? Not sure if using .catch here is even correct or if I should do this type of work another way.
You seem to be aware of the bit of a footgun in the fetch API (I write about it here) where fetch only rejects its promise on network errors, not HTTP errors, but your check is incorrect in a couple of ways:
It's status, not Status (capitalization matters), and
It's the HTTP code (400 for instance), not a string
The Response object provides a convenient ok flag that's true for any successful response and false otherwise, so:
const response = await fetch(ngrokOrLocalhost + '/confirmmatchrecord', requestOptions)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`); // This will be "HTTP error! status: 400" if the HTTP error code is 400
}
const data = await response.json();
return data;
In a comment you've said:
My response['Status'] was checking for a custom server message I had sent (res.status(400).json({'Status': 'Failure'}), I updated the post with it. Not sure why it didn't catch that
Ah! Okay. The reason it didn't catch it is that you're looking for it on the Response object, but your JSON is in the response body.
I suspect you don't want to use your own Status anymore since you know about response.ok and response.status now, but if you ever do want to include your own information in an error response as JSON, you can do that. You'd do it like this:
const response = await fetch(ngrokOrLocalhost + '/confirmmatchrecord', requestOptions)
const data = await response.json(); // Expects JSON in *both* the success response and the error response
if (data.Status === "Failure") {
throw new Error(`HTTP error! status: ${response.status}`); // This will be "HTTP error! status: 400" if the HTTP error code is 400
}
return data;
But I'd stick with just the built-in ok and status for pure success/failure information. This could be handy if you wanted to provide more details of the failure, though.
I have this function that reads a csv file that contains a list of url.
For every line I do a get request to the current url to read the response header.
function readCSV(csv) {
var lines = csv.split("\n");
var table = lines.map((line) => line.split(","));
var requests = table.map((row) =>
request({
method: "GET",
uri: "https://www." + row[1],
resolveWithFullResponse: true,
})
.catch((err) => null) // Errors are ignored and resolved as `null`
);
return Promise.all(requests)
.then((responses) => {
responses.forEach((response) => {
if(response === null) return; // If the response is null, skip it
// ... handle successful responses here
var hrds = response.headers;
//console.log(hrds.url)
console.log(hrds['content-security-policy'])
console.log(hrds['x-frame-options'])
});
})
.catch((err) => console.log(err));
}
I would like to do something like: console.log(hrds.url) to see the url that sent me the response. If I try to print hrds.url it gives me undefined.
EDIT:
I tried response.url but it prints blank line
According to MDN docs you can get the URL from the response it self.
response.url
I have written the following HTTP firebase JS function which is returning the incorrect status 500 error response using Postman even though the axios GET call response from the API service has returned the correct 200 status response (confirmed by the console output screenshot below)
exports.doshiiMenuUpdatedWebhook = functions.https.onRequest((req, res) => {
if (req.method === 'PUT') {
return res.status(403).send('Forbidden!');
}
return cors(req, res, () => {
let verify = req.query.verify;
if (!verify) {
verify = req.body.verify;
}
let locationId = req.body.data.locationId
let posId = req.body.data.posId
let type = req.body.data.type
let uri = req.body.data.uri
let itemUri = req.body.data.itemUri
console.log('locationId', locationId);
console.log('posId', posId);
console.log('type', type);
console.log('uri', uri);
console.log('itemUri', itemUri);
const options = {
headers: {'authorization': 'Bearer ' + req.query.verify}
};
return axios.get(uri, options)
.then(response => {
console.log('response data: ', response.data);
console.log('response status: ', response.status);
console.log('response statusText: ', response.statusText);
console.log('response headers: ', response.headers);
console.log('response config: ', response.config);
return res.status(200).json({
message: response
})
})
.catch(err => {
return res.status(500).json({
error: err
})
});
});
});
In Postman I'm expecting to see "Status: 200" response, but I get this:
There is no error report in the Firebase console other than this:
As explained in the Express documentation:
res.json([body])
Sends a JSON response. This method sends a response (with the correct
content-type) that is the parameter converted to a JSON string using
JSON.stringify().
The parameter can be any JSON type, including object, array, string,
Boolean, number, or null, and you can also use it to convert other
values to JSON.
Following the "debugging" we did through the comments/chat, it seems that the
{message: response}
object that you pass to json() generates the error.
Following the HTTP Cloud Functions documentation, which states:
Important: Make sure that all HTTP functions terminate properly. By
terminating functions correctly, you can avoid excessive charges from
functions that run for too long. Terminate HTTP functions with
res.redirect(), res.send(), or res.end().
and since you explained in the chat that you "only need to return the status code" and that you "want to save the json data to: admin.database().ref(/venue-menus/${locationId}/menu)",
I would advise you do as follows:
exports.doshiiMenuUpdatedWebhook = functions.https.onRequest((req, res) => {
if (req.method === 'PUT') {
return res.status(403).send('Forbidden!');
}
cors(req, res, () => {
let verify = req.query.verify;
if (!verify) {
verify = req.body.verify;
}
let locationId = req.body.data.locationId
let posId = req.body.data.posId
let type = req.body.data.type
let uri = req.body.data.uri
let itemUri = req.body.data.itemUri
const options = {
headers: { 'authorization': 'Bearer ' + req.query.verify }
};
axios.get(uri, options)
.then(response => {
console.log('response data: ', response.data);
return admin.database().ref(`/venue-menus/${locationId}/menu`).set(response.data)
})
.then(response => {
return res.status(200).end()
})
.catch(err => {
return res.status(500).send({
error: err
})
})
})
});
I have the following code for making POST Requests.
I'm not 100% sure about error handling here, but it was important for me that I get body text when request is not successful.
One issue that I still do have is - if server responds with 200 OK but invalid json - can I log that payload?
What would be the correct way of logging for Fetch?
Fetch(data.notificationUrl, {
method: 'POST',
body: post_data,
headers: {
'Content-Type': 'application/json'
}
}).then((res) => {
if (!res.ok) {
// Could reject the promise here but than response text wouldn't be available
//return Promise.reject(`Response was not OK. Status code: ${res.status} text: ${res.statusText}`);
return res.text().then((txt) => `Response was not OK. Status code: ${res.status} text: ${res.statusText}.\nResponse: ${txt}`);
}
// response ok so we should return json, could follow https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch and determine the payload type by content-type header...
return res.json();
}).then((response) => {
if (response) {
// set result
// ...
// redirect
return reply.redirect(data.redirectUrlDirectory);
}
return reply(Boom.preconditionFailed(`Did not reply with correct payload! json:'${JSON.stringify(response)}'`));
}).catch((err) => {
return reply(Boom.badData(`Could not notify on url ${data.notificationUrl} about the payment ${id}.\nError: "${err}"`));
});
I would use something like this.
This fist option asumes your service response always the header "application/json" and a the pay load simple text which I mock it like this.
var app = new express();
app.get('/valid', function(req, res){
res.json({ok: "ok"});
});
app.get('/invalid', function(req, res){
res.json("bad json body");
});
and the fetch json handling should looks like this. The other part of your code looks like good for me.
var response2 = res.clone();
return res.json().then((json) => {
// log your good payload
try {
// here we check json is not an object
return typeof json === 'object' ? json : JSON.parse(json);
} catch(error) {
// this drives you the Promise catch
throw error;
}
}).catch(function(error) {
return response2.text().then((txt) => `Response was not OK. Status code: ${response2.status} text: ${response2.statusText}.\nResponse: ${txt}`);
//this error will be capture by your last .catch()
});
xxx.clone() allows you to resolve multiple times the same response and create your own combinations like the previous one.