We are using restify 6.4.0 and instead of 404 for non-existent endpoints we get 405 (method not allowed).
I have identified the core issue which is this line server.opts('.*', (req, res) => res.send(204));. For some reason, when it is present, the 405 issue is present. When I remove it, then restify starts working as expected (404 for nonexisting endpoints, 405 for existing endpoints with different HTTP Method existing)
This is working example
var restify = require('restify');
const server = restify.createServer({
name: 'myapp',
version: '1.0.0'
});
server.use(restify.plugins.acceptParser(server.acceptable));
server.use(restify.plugins.queryParser());
server.use(restify.plugins.bodyParser());
server.opts('.*', (req, res) => res.send(204));
server.get('/echo/:name', function (req, res, next) {
res.send(req.params);
return next();
});
server.listen(8080, function () {
console.log('%s listening at %s', server.name, server.url);
});
When I use postman and call localhost:8080/noo, this is the response
{
"code": "MethodNotAllowed",
"message": "GET is not allowed"
}
However when I remove the server.opts('.*', (req, res) => res.send(204)); line, I got expected result
{
"code": "ResourceNotFound",
"message": "/noo does not exist"
}
Its not clear to me, why this is happening - tried Postman and also another Node.js code to make sure its not caused by some pre-flight request done by Chrome or other browsers. Calling the server with this code:
var request = require('request');
var options = {
'method': 'GET',
'url': 'http://localhost:8080/noo',
'headers': {
}
};
request(options, function (error, response) {
if (error) throw new Error(error);
console.log(response.body);
});
Is getting me this response
{"code":"MethodNotAllowed","message":"GET is not allowed"}
(and same as with postman, when I delete the server.opts... line, I get proper response
{"code":"ResourceNotFound","message":"/noo does not exist"}
Can someone explain whats happening?
The line:
server.opts('.*', (req, res) => res.send(204));
Is a catch all handler to return 204 response for all HTTP OPTONS requests.
Looks like this results in restify always concluding that you do have a handler for a given route (since it is a catch all for every route), just not for the method that is being asked for - i.e. anything but OPTIONS requests.
Hence you get 405 "Method not allowed" rather than 404 "Not found"
Related
I was working on admin registration and admin data retrieving react app. The registration works fine but retrieving admin data is crushing my backend. I have encountered this error when call the given endpoint from my react app. But when I call it from Postman it works very fine. And when I see the console on my browser my react app sends two calls simultaneously instead of one. On these calls my app crushes. If any one can show me how to solve this problem?
For backend = Node.js with express.js framework
For frontend = React
This is the error I am getting
node:internal/errors:465
ErrorCaptureStackTrace(err);
^
Error [ERR_HTTP_HEADERS_SENT]: Cannot remove headers after they are sent to the client
at new NodeError (node:internal/errors:372:5)
at ServerResponse.removeHeader (node:_http_outgoing:654:11)
at ServerResponse.send (C:\Users\Momentum\Documents\The Technologies\Madudi-App-Api\node_modules\express\lib\response.js:214:10)
at C:\Users\Momentum\Documents\The Technologies\Madudi-App-Api\api\index.js:22:72
at processTicksAndRejections (node:internal/process/task_queues:96:5) {
code: 'ERR_HTTP_HEADERS_SENT'
}
[nodemon] app crashed - waiting for file changes before starting...
This is how I setup my endpoint and changed the data to a string in order to get simple response but it crushes
const makeHttpRequest = (controller, helper) => {
const makeRequest = (req, res) => {
try {
var data = "Trying response";
res.status(200).send({ status: true, data: data });
} catch (error) {
console.log(`ERROR: ${error.message}`);
res.status(400).send({ status: false, error: error.message });
}
};
return { makeRequest };
};
const makeApi = ({router, controller, helper}) => {
router.get("/test", (req, res) => res.send("Router is Woking..."));
router.get("/admin/get_all_admins", async (req, res) => res.send(await makeHttpRequest(controller, helper).makeRequest(req, res)));
}
module.exports = { makeApi }
And this is the call from my react app
export default function GetAllUsers() {
useEffect(() =>{
try{
const response = axios.get('http://localhost:5000/admin/get_all_admins').then(async (response) => {
console.log('response ', response)
return response.data;
});
}catch(error) {
return [];
}
}, [])
I'm not familiar with this method of responding to requests, but in my own opinion the error you are facing happens when you're sending multiple response.
This may be the asynchronous nature of JavaScript, there by causing another request to be sent after the function is done.
You should also try to return the response, so that once it's done it cancels out of the function. You can use the example below
const handler = (req,res) => {
return res.status(200).json(data)}
This particular error happens when you try to send more than one response for the same incoming request (something you are not allowed to do).
You are calling res.send() more than one for the same request on your server.
The first happens in the makeRequest() function.
The second time happens in this line of code:
router.get("/admin/get_all_admins", async (req, res) => res.send(await makeHttpRequest(controller, helper).makeRequest(req, res)));
You can't do that. You get ONE response per incoming request. So, either send the response in makeRquest() and don't send it in the caller. Or, don't send the response in makeRequest() and just return what the response should be and let the caller send it. Pick one model or the other.
I am not familiar with this way of setting up the server. Looks strange to me. However, in router.get("/admin/get_all_admins" your sending a response which calls a function makeHttpRequest that also sends a response. Thus you get an error Cannot remove headers after they are sent to the client because you're sending a response twice.
I am building a rest API with nodejs and express.
I am trying to implement a small CORS system to the endpoints:
cors.js
export const cors = ({ allowedMethods }) => {
return (req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
if(!allowedMethods.includes(req.method)){
return res.sendStatus(405);
}
next();
}
}
server.js
const app = express()
app.use(express.json())
app.post('/', cors({allowedMethods: ['POST']}), (req, res) => {
})
app.put('/photo', cors({allowedMethods: ['PUT']}), (req, res) => {
})
Let's say I have these 2 endpoints, every time I go to a URL with an unallowed method, I get a 404 response but I want "405 Not Allowed". (makes sense, because of app.<method>())
How is it possible to look up the allowedMethods for each endpoint and decide what to do next?
I've tried using app.use(cors()), but that catches all endpoints and I'll never know about the allowedMethod specified for a specific endpoint. And I don't think that node cors package would do it.
You're conflating two different things here.
CORS has a mechanism for stating what HTTP methods are allowed to be used to make a cross origin request from JavaScript which is used in combination with a preflight OPTIONS request.
HTTP has a mechanism to say that a request is using an HTTP method that is not allowed at all.
Since you are attempting to generate 405 Not Allowed responses, you are dealing with the latter. This has nothing to do with CORS and should be kept separate from CORS middleware.
all will match all requests for a path, no matter what HTTP method is used, so you can write:
const method_not_allowed = (req, res) => {
return res.sendStatus(405);
};
app.post('/', cors(), (req, res) => { });
app.all('/', method_not_allowed);
Any POST request will get caught by app.post('/' and any other request to / will carry on and get matched by app.all('/').
That said, it looks like you are reinventing the wheel and could just use this existing module.
If you need to also deal with Access-Control-Allow-Methods then you need to take care of that separately.
That does need to be handled with the rest of your CORS logic, and you need to handle both the methods you want requests to be made with (post in this example) and OPTIONS (for the preflight).
If you don't mind a bit of configuration:
const routes = {
"/": {
get: (request, response) => { ... },
post: (request, response) => { ... },
},
"/photo": {
put: (request, response) => { ... },
},
};
app.use((request, response, next) => {
const route = routes[request.url] || {};
if (!Object.keys(route).includes(request.method.toLowerCase())) {
return response.sendStatus(405);
}
next();
});
for (let route in routes) {
for (let method in routes[route]) {
app[method](route, routes[route][method]);
}
}
Even though you'll get in trouble soon with request params (/photos/:photo_id).
EDIT: didn't know about app.all, much cleaner!
I have an Express server with a simple error handler. All the err object passed to it are Error objects.
const errorHandler = (err, req, res, next) => {
logError(`Express Error Handler: ${err}`);
res.status(500).json(err);
};
I am calling the server's routes using request:
request
.post(
{
uri: url,
timeout,
json: true,
form: {
currentStepUid,
sourceStepUid: previousStepUid,
config,
},
},
(error, response, body) => {
// Handle errors
}
)
});
My problem is that the error are not coming through as error objects, but rather as an error property on the body object.
How should I have Express set up so that I get error objects back?
A 500 status is NOT reported by request.post() as an error. The http server was contacted and a response was supplied. That is not what it considers an error. It is up to your own code to detect that condition from the http response and then treat it as an error in your own code.
You will need to look at the actual response to see the http 500 status code. The error object IS the http response body so that's where it should be. That's where you need to get it from.
If you have to do this same logic in multiple places, you could make your own wrapper function for request.post() that would examine the http status code and if it's in a range that you consider an error, then get the http response body and make that into an error.
I am making a POST API request via Axios to my Node server to send a string value into my MongoDB.
The below code works well locally at localhost:8080 but as soon as I push my code to Heroku, the requests start to return a 404. My node server runs over https and I have configured CORS on its Express framework.
Here is the output I receive when I make the POST request to my node server:
POST https://www.mynodeserverurl.com/api/upload-csv 404 (Not Found)
And here is my code for the Node server that processes this API request.
NodeServer/app.js
// Add headers so we can make API requests
app.use(function (req, res, next) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
res.setHeader('Access-Control-Allow-Credentials', true);
// intercept OPTIONS method
if ('OPTIONS' == req.method) {
res.sendStatus(200);
} else {
next();
}
});
NodeServer/routes/api/api.js
router.post('/upload-csv/', (req, res) => {
let csv = req.body.csv_string.split('$');
let csv_name = csv[0];
let csv_string = csv_name+csv[1];
CSVStore.findOne({url: csv_name})
.then((csv) => {
console.log('IN POST REQUEST');
console.log(csv_string);
return res.status(204).json({msg: 'All good!'});
})
.catch((err) => {
console.log('There was an error!', err);
});
});
Making a POST request to the above route doesn't even display the console.log() results in the Node server console.
Here is my code for the webapp that makes the POST request to the NodeServer
axios.post(`${utils.fetchURL()}/upload-csv`, {
csv_string: csv_string
})
.then(function(response) {
console.log('Response', response);
return {};
})
.catch(function(error) {
console.log('There was an error!', error);
return {};
});
The URL I am making the POST request on is correct and csv_string is not empty; it has data.
Is there some kind of configuration I need to make in my NodeServer's app.js to allow CORS on https?
UPDATE: I can confirm that a GET request works. I am able to make a GET request to retrieve a string from my MongoDB. Why does GET work and POST does not?
I was having this same problem. All my API endpoints worked locally and in prod, but my one POST endpoint would 404 in production, but worked for a GET.
It turned out that before running "git push heroku master" I had forgot to stage and commit my most recent changes. Once I did that and ran "git push heroku master" again, everything was published and working in production.
So try that if you're having this issue
Error handling in Node. Argh!
I'm trying to layout a basic Node app like this...
Cluster -> Worker -> Server Domain -> Express Request Domain
So, if an error is thrown 18 layers deep into a call stack because someone misspelled their name on a login form, the entire server doesn't crash.
Here's some basic code to simulate the worker part:
var domain, server;
domain = require('domain');
server = domain.create();
server.on('error', function(e) {
console.log('total meltdown...', e.stack);
});
server.run(function() {
var express = require('express')();
express.configure(function() {
// Domain on EVERY request
express.use(function(req, res, next) {
var d = domain.create();
d.on('error', function(e) {
console.log('fired REQUEST error', e.stack);
next(e);
});
d.run(next);
});
// Generic error handler
express.use(function(e, req, res, next) {
res.status(500);
res.end('oops');
});
// Serve the request with a blatent error
express.get('/', function(req, res) {
this_function_does_not_exist();
res.end('we will never get here');
});
});
// Fire 'er up
express.listen(3000);
});
What I'm expecting...
I curl http://localhost:3000/, get a nice little 'oops' error, and see 'fired REQUEST error' and the error stack in the console.
What actually happens...
I get this both as the browser response, and in the console...
ReferenceError: this_function_does_not_exist is not defined
at /Stuff/test.js:38:13
at callbacks (/Stuff/node_modules/express/lib/router/index.js:161:37)
at param (/Stuff/node_modules/express/lib/router/index.js:135:11)
at pass (/Stuff/node_modules/express/lib/router/index.js:142:5)
at Router._dispatch (/Stuff/node_modules/express/lib/router/index.js:170:5)
at Object.router (/Stuff/node_modules/express/lib/router/index.js:33:10)
at next (/Stuff/node_modules/express/node_modules/connect/lib/proto.js:190:15)
at next (/Stuff/node_modules/express/node_modules/connect/lib/proto.js:192:9)
at b (domain.js:183:18)
at Domain.run (domain.js:123:23)
Now why would it go and do something like that?
Ok, solved - Express has a try/catch block, which is getting to my non-existent function call first.
To have the domain catch it, it needs to be taken out of the current call stack, like...
process.nextTick(function() {
this_function_does_not_exist();
res.end('we will never get here');
});
THEN the domain will grab it.