I'm trying to send a POST request to my api but it gives me following error:
I'm using express-http-proxy package for node to send request. Here is my code:
app.use('/api', proxy(targetUrl, {
forwardPath: (req, res) => {
return require('url').parse(req.url).path;
}
}));
In my ReactJs application, I'm using superagent to pass my request and here is request creation code:
methods.forEach((method) =>
this[method] = (path, data = {}, params = {}) => new Promise((resolve, reject) => {
const request = superagent[method](formatUrl(path));
request.set('Token', 'cb460084804cd40');
if (params) {
request.query(params);
}
if (__SERVER__ && req.get('cookie')) {
request.set('cookie', req.get('cookie'));
}
if (data) {
request.send(data);
}
console.log('sending: ', request);
// request.end((err, { text } = {}) => {console.log('ended: ', text, err);});
// reject();
request.end((err, { text } = {}) => err ? reject(text || err) : resolve(text));
}));
formatURL Function
function formatUrl(path) {
const adjustedPath = path[0] !== '/' ? '/' + path : path;
if (__SERVER__) {
return 'https://' + config.apiHost + adjustedPath;
}
return '/api' + adjustedPath;
}
In addition to this, when I send GET request, it gives me following error:
Also, when I try to send POST request on my live APIserver it gives 404 Not Found error but if I try to send POST request on my localhost APIServer it gives 504 Gateway_Timeout error.
I am not confident about this behaviour. Therefore, need your help to find the problem. Thanks for your time in advance.
Related
I am trying to send a POST request using axios to the backend but it is throwing a 404 for the path and i dont know why
Here is the react/redux code calling the axios request
export const addGoal = (newGoal: Goal) => {
return (dispatch: any) => {
authMiddleWare(history)
const newValues = newGoal
const authToken = localStorage.getItem('AuthToken')
axios.defaults.headers.common = { Authorization: `${authToken}` }
axios
.post('/goal', newValues)
.then((response) => {
console.log('success', response.data)
dispatch({
type: ADD_GOAL,
payload: response.data,
})
})
.catch((err) => {
console.error('\nCould not submit goal\n', err.response)
})
}
}
This is the nodejs path i have in my main backend file for calling the paths
app.post("/goal", auth, postOneGoal);
This is the backend function for the node path
// ADDS A SINGLE WORKOUT
exports.postOneGoal = (request, response) => {
if (request.body.id.trim() === "" || request.body.text.trim() === "") {
return response.status(400).json({ body: "Must not be empty" });
}
const newGoalItem = {
username: request.user.username,
id: request.body.id,
text: request.body.text
};
db.collection("goals")
.add(newGoalItem)
.then((doc) => {
const responseNewGoalItem = newGoalItem;
responseNewGoalItem.id = doc.id;
doc.update(responseNewGoalItem);
return response.json(responseNewGoalItem);
})
.catch((err) => {
response.status(500).json({ error: "Couldn't add the goal" });
console.error(err);
});
};
I am using a firebase url proxy in my package.json as well.
Let me know if any more info is needed
Posting this as Community Wiki, based in the comments.
Considering the fact that you are using Cloud Functions, you will need to redeploy the functions everytime you update your code. You can check more details on deploying your functions in the official documentation accessible here. There you will have the options regarding how and where you can deploy your functions for better testing.
I am working on creating a zip of multiple files on the server and stream it to the client while creating. Initially, I was using ArchiverJs It was working fine if I was appending buffer to it but it fails when I need to add streams into it. Then after having some discussion on Github, I switched to Node zip-stream which started working fine thanks to jntesteves. But as I deploy the code on GKE k8s I Started getting Network Failed errors for huge files.
Here is my sample code :
const ZipStream = require("zip-stream");
/**
* #summary Adding readable stream provided by https module into zipStreamer using entry method
*/
const handleEntryCB = ({ readableStream, zipStreamer, fileName, resolve }) => {
readableStream.on("error", () => {
console.error("Error while listening readableStream : ", error);
resolve("done");
});
zipStreamer.entry(readableStream, { name: fileName }, error => {
if (!error) {
resolve("done");
} else {
console.error("Error while listening zipStream readableStream : ", error);
resolve("done");
}
});
};
/**
* #summary Handling downloading of files using native https, http and request modules
*/
const handleUrl = ({ elem, zipStreamer }) => {
return new Promise((resolve, reject) => {
let fileName = elem.fileName;
const url = elem.url;
//Used in most of the cases
if (url.startsWith("https")) {
https.get(url, readableStream => {
handleEntryCB({ readableStream, zipStreamer, url, fileName, resolve, reject });
});
} else if (url.startsWith("http")) {
http.get(url, readableStream => {
handleEntryCB({ readableStream, zipStreamer, url, fileName, resolve, reject });
});
} else {
const readableStream = request(url);
handleEntryCB({ readableStream, zipStreamer, url, fileName, resolve, reject });
}
});
};
const downloadZipFile = async (data, resp) => {
let { urls = [] } = data || {};
if (!urls.length) {
throw new Error("URLs are mandatory.");
}
//Output zip name
const outputFileName = `Test items.zip`;
console.log("Downloading using streams.");
//Initialize zip-stream instance
const zipStreamer = new ZipStream();
//Set headers to response
resp.writeHead(200, {
"Content-Type": "application/zip",
"Content-Disposition": `attachment; filename="${outputFileName}"`,
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, OPTIONS"
});
//piping zipStreamer to the resp so that client starts getting response
//as soon as first chunk is added to the zipStreamer
zipStreamer.pipe(resp);
for (const elem of urls) {
await handleUrl({ elem, zipStreamer });
}
zipStreamer.finish();
};
app.post(restPrefix + "/downloadFIle", (req, resp) => {
try {
const { data } = req.body || {};
downloadZipFile(data, resp);
} catch (error) {
console.error("[FileBundler] unknown error : ", error);
if (resp.headersSent) {
resp.end("Unknown error while archiving.");
} else {
resp.status(500).end("Unknown error while archiving.");
}
}
});
I tested for 7-8 files of ~4.5 GB each on local, it works fine and when I tried the same on google k8s, I got network failed error.
After some more research, I Increased server timeout on k8s t0 3000 seconds, than it starts working fine, but I guess the increasing timeout is not good.
Is there anything I am missing on code level or can you suggest some good GKE deployment configuration for a server that can download large files with many concurrent users?
I am stuck on this for the past 1.5+ months. please help!
Edit 1: I edited the timeout in the ingress i.e Network services-> Load Balancing ->edit the timeout in the service
The why
We're using the axios-retry library, which uses this code internally:
axios.interceptors.response.use(null, error => {
Since it only specifies the error callback, the Axios documentation says:
Any status codes that falls outside the range of 2xx cause this function to trigger
Unfortunately we're calling a non-RESTful API that can return 200 with an error code in the body, and we need to retry that.
We've tried adding an Axios interceptor before axios-retry does and changing the result status in this case; that did not trigger the subsequent interceptor error callback though.
What did work was specifying a custom adapter. However this is not well-documented and our code does not handle every case.
The code
const axios = require('axios');
const httpAdapter = require('axios/lib/adapters/http');
const settle = require('axios/lib/core/settle');
const axiosRetry = require('axios-retry');
const myAdapter = async function(config) {
return new Promise((resolve, reject) => {
// Delegate to default http adapter
return httpAdapter(config).then(result => {
// We would have more logic here in the production code
if (result.status === 200) result.status = 500;
settle(resolve, reject, result);
return result;
});
});
}
const axios2 = axios.create({
adapter: myAdapter
});
function isErr(error) {
console.log('retry checking response', error.response.status);
return !error.response || (error.response.status === 500);
}
axiosRetry(axios2, {
retries: 3,
retryCondition: isErr
});
// httpstat.us can return various status codes for testing
axios2.get('http://httpstat.us/200')
.then(result => {
console.log('Result:', result.data);
})
.catch(e => console.error('Service returned', e.message));
This works in the error case, printing:
retry checking response 500
retry checking response 500
retry checking response 500
retry checking response 500
Service returned Request failed with status code 500
It works in the success case too (change the URL to http://httpstat.us/201):
Result: { code: 201, description: 'Created' }
The issue
Changing the URL to http://httpstat.us/404, though, results in:
(node:19759) UnhandledPromiseRejectionWarning: Error: Request failed with status code 404
at createError (.../node_modules/axios/lib/core/createError.js:16:15)
at settle (.../node_modules/axios/lib/core/settle.js:18:12)
A catch on the httpAdapter call will catch that error, but how do we pass that down the chain?
What is the correct way to implement an Axios adapter?
If there is a better way to handle this (short of forking the axios-retry library), that would be an acceptable answer.
Update
A coworker figured out that doing .catch(e => reject(e)) (or just .catch(reject)) on the httpAdapter call appears to handle the issue. However we'd still like to have a canonical example of implementing an Axios adapter that wraps the default http adapter.
Here's what worked (in node):
const httpAdapter = require('axios/lib/adapters/http');
const settle = require('axios/lib/core/settle');
const customAdapter = config =>
new Promise((resolve, reject) => {
httpAdapter(config).then(response => {
if (response.status === 200)
// && response.data contains particular error
{
// log if desired
response.status = 503;
}
settle(resolve, reject, response);
}).catch(reject);
});
// Then do axios.create() and pass { adapter: customAdapter }
// Now set up axios-retry and its retryCondition will be checked
Workaround with interceptor and custom error
const axios = require("axios").default;
const axiosRetry = require("axios-retry").default;
axios.interceptors.response.use(async (response) => {
if (response.status == 200) {
const err = new Error("I want to retry");
err.config = response.config; // axios-retry using this
throw err;
}
return response;
});
axiosRetry(axios, {
retries: 1,
retryCondition: (error) => {
console.log("retryCondition");
return false;
},
});
axios
.get("https://example.com/")
.catch((err) => console.log(err.message)); // gonna be here anyway as we'll fail due to interceptor logic
I am trying to call a rest API from Firebase function which servers as a fulfillment for Actions on Google.
I tried the following approach:
const { dialogflow } = require('actions-on-google');
const functions = require('firebase-functions');
const http = require('https');
const host = 'wwws.example.com';
const app = dialogflow({debug: true});
app.intent('my_intent_1', (conv, {param1}) => {
// Call the rate API
callApi(param1).then((output) => {
console.log(output);
conv.close(`I found ${output.length} items!`);
}).catch(() => {
conv.close('Error occurred while trying to get vehicles. Please try again later.');
});
});
function callApi (param1) {
return new Promise((resolve, reject) => {
// Create the path for the HTTP request to get the vehicle
let path = '/api/' + encodeURIComponent(param1);
console.log('API Request: ' + host + path);
// Make the HTTP request to get the vehicle
http.get({host: host, path: path}, (res) => {
let body = ''; // var to store the response chunks
res.on('data', (d) => { body += d; }); // store each response chunk
res.on('end', () => {
// After all the data has been received parse the JSON for desired data
let response = JSON.parse(body);
let output = {};
//copy required response attributes to output here
console.log(response.length.toString());
resolve(output);
});
res.on('error', (error) => {
console.log(`Error calling the API: ${error}`)
reject();
});
}); //http.get
}); //promise
}
exports.myFunction = functions.https.onRequest(app);
This is almost working. API is called and I get the data back. The problem is that without async/await, the function does not wait for the "callApi" to complete, and I get an error from Actions on Google that there was no response. After the error, I can see the console.log outputs in the Firebase log, so everything is working, it is just out of sync.
I tried using async/await but got an error which I think is because Firebase uses old version of node.js which does not support async.
How can I get around this?
Your function callApi returns a promise, but you don't return a promise in your intent handler. You should make sure you add the return so that the handler knows to wait for the response.
app.intent('my_intent_1', (conv, {param1}) => {
// Call the rate API
return callApi(param1).then((output) => {
console.log(output);
conv.close(`I found ${output.length} items!`);
}).catch(() => {
conv.close('Error occurred while trying to get vehicles. Please try again later.');
});
});
I'm using the Uber API to create a ride request (with node-uber)
I first get the user authenticated (uber.authorization after getting a calback from uber.getAuthorizeUrl).
Then I call the estimate API and get a response (estimates.getPriceForRoute)
I then use the same start & end coordinates with the product ID returned in the estimate (requests.create).
I always get back: HTTP Response with error code: 409
Any suggestions?
UPDATE
Here is the code for the estimate request (which works perfectly)
router.get('/estimate', function(request, response, next) {
var query = url.parse(request.url, true).query;
if (!query || !query.start_lat || !query.start_lng || !query.end_lat || !query.end_lng) {
response.sendStatus(400);
} else {
uber.estimates.getPriceForRoute(query.start_lat, query.start_lng, query.end_lat, query.end_lng , 1, function(err, res){
if (err) {
console.error(err);
response.sendStatus(500);
} else {
var productType = getProductType(query);
var selectedProduct = _.find(res.prices, function(price){
return price.display_name == productType;
});
response.json(selectedProduct);
}
});
}
});
Here is the response I get
{"localized_display_name":"uberX","high_estimate":7,"minimum":7,"duration":240,"estimate":"$7-7","distance":0.95,"display_name":"uberX","product_id":"a1111c8c-c720-46c3-8534-2fcdd730040d","low_estimate":7,"surge_multiplier":1,"currency_code":"USD"}
Now the part that has the problem:
router.get('/request', function(request, response, next) {
var query = url.parse(request.url, true).query;
if (!query || !query.start_lat || !query.start_lng || !query.end_lat || !query.end_lng || !query.productId) {
response.sendStatus(400);
} else {
uber.requests.create({
"product_id": query.productId,
"start_latitude": query.start_lat,
"start_longitude": query.start_lng,
"end_latitude": query.end_lat,
"end_longitude": query.end_lng
}, function (err, res) {
if (err) {
console.error("error: " + err);
response.sendStatus(400);
} else {
//console.log(res);
response.json(res);
}
});
}
});
the line console.error("error: " + err); returns this output:
error: HTTP Response with error code: 409
Could you please share your source code? I assume you try to call /v1/requests/estimate endpoint. This endpoints can return a 409 - Conflict. This status indicates that there is a surge in place right now. Here is a description of the surge flow:
The node-uber project does not actively support with handling a surge in the released version. However, there is a branch for v1.0.0 and someone started working on a direct support, as listed in this issue conversation.