app.get('/users/:userId/profile', ProfileHandler);
app.get('/page/:userId/profile', ProfileHandler);
app.get('/photo/:userId/profile', ProfileHandler);
If I have the above 3 routes, how can I capture the first part so that the handler knows what is being requested? I'd like to have users or page or photo sent to the handler as part of the request object.
Ideally I'd like to avoid making this a single route with a regex as this is just a dumbed down example of my real use case.
If you know ahead of time due to your bindings, why not just pass the info in there?
app.get('/users/:userId/profile', ProfileHandler.bind(null, 'users'));
function ProfileHandler(pageRoot, req, res, next){
switch (pageRoot){
case 'users':
break;
case 'page':
break;
}
});
Based on the pattern you are using , ProfileHandler will be passed a req and res object. req has a url property that you can then split and switch-case:
app.get('/users/:userId/profile', ProfileHandler);
app.get('/page/:userId/profile', ProfileHandler);
app.get('/photo/:userId/profile', ProfileHandler);
function ProfileHandler(req,res){
var reqType = req.url.split('/')[1];
switch(reqType){
case 'users':
//DO SOMETHING COOL
break;
}
}
Alternatively, you could add middleware that set that value on the request.
app.use(function (req, res, next) {
var reqType = req.url.split('/')[1];
req.handlerTarget = reqType;
});
function ProfileHandler(req,res){
switch(req.handlerTarget){
case 'users':
//DO SOMETHING COOL
break;
}
}
Related
I'm handling a client access by IP ACL.
function localAcl (options) {
let {allowedMethods} = options
if (!Array.isArray(allowedMethods)) {
allowedMethods = []
}
return function(req, res, next) {
if (allowedMethods.includes(req.method)) {
next()
} else {
(ipAccessControl(options))(req, res, next)
}
}
}
app.use('/api/v1', localAcl(options), apiV1Router)
The above reads a config file and decides whether to accept or not.
Now, I want to add a new middleware which reads another config file from a web file and decides the permission.
function newAcl () {
...
switch(result) {
case SUCCESS:
next();
break;
case FAIL:
next(new Error('Access Denied'));
break;
}
}
//something like this..
I want to check the permission with the localAcl first.
If localAcl accepts, I don't want the newAcl to be checked and the request will be handled.
If localAcl denies, I want the newAcl to check once more and if the newAcl accepts, the request should be handled.
How can I connect these two?
I see two options
Use next('route') to skip the rest of the middleware functions
Handle programmatically the authorization to let the last middleware decide
The first option seems like a better option for what you're describing
To skip the rest of the middleware functions from a router middleware
stack, call next('route') to pass control to the next route.
NOTE:
next('route') will work only in middleware functions that were loaded
by using the app.METHOD() or router.METHOD() functions.
Source
This means that you should probably call next('route') in your ipAccessControl function.
I am submitting a simple contact form in my website's footer (in footer.pug):
form(method="POST" action="contact_form")
input(type='email' name='ct_email' data-name='ct_email' required)
textarea(type='text' name='ct_message' data-name='ct_message' required)
button(type='submit') Send
Since the form is in a template, and the footer template is used throughout the site, the form can be submitted from various routes:
/contact_form
/route1/contact_form
/route1/de/contact_form
and so on...
So now it seems I have to create a handler for all the possible routes:
router.post('/contact_form', function(req, res, next) {
// ...
}
router.post('/route1/contact_form', function(req, res, next) {
// ...
}
How can I easily handle POST requests from all the routes they may be coming from without writing a handler for each?
You can use absolute path reference in your form and it will always submit to the same route even though the form is in different pages.
Try this
form(method="POST" action="/contact_form")
Notice the action changed from contact_form to /contact_form. When you add /, you start referencing the path as an absolute path to the domain. So now, from all pages, the form will be submitted to http://your-domain/contact-form.
Not entirely sure if this is what you mean, but the first argument to ExpressJS's router (I assume that's what router is doing here) can be an array. So instead of:
router.post('/contact_form', function(req, res, next) {
// ...
}
router.post('/route1/contact_form', function(req, res, next) {
// ...
}
You can just do:
router.post(['/contact_form','route1/contact_form'],function(req,res,next){
//some fancy logic to handle both routes.
})
Of course, this requires that you keep a list of these possible routes. On the other hand, you can follow Dinesh Pandiyan's advice, and just use an absolute path. So instead of page1.html, page2.html, page3.html, etc. all having their own own router (or own entry in your router array), you'd essentially be saying "Go to the domain route, then go to this address".
Each request should be handled in separated functions because each request has its own logic. However if you want
function request(req, res, next) {
// Your logic
}
router.post('/contact_form', request) {
// ...
}
router.post('/route1/contact_form', request) {
// ...
}
Right now, I don't have a way to test this code, but I think that will help you.
Here is yet another potential solution - use an independent function as a route handler.
router.post('/a', handlePost);
router.post('/b', handlePost);
router.post('/c', handlePost);
function handlePost(req, res, next){
// use req.path here to figure out what url was called
}
I am trying to create a meta tag handlebars helper that grabs the url pathname and uses that value for a switch statement which will then return the string to my meta tag in HTML head, but I am not sure the best way to currently grab the url path for my switch statement. I tried window.location.pathname, but get an error that window is not defined. I know that the path module requires you to pass something to it to parse out, but I'm not sure what the best value would be for that. Can anyone help me?
Here is my handlebars helper file:
var path = require('path');
var metaHelpers = function(hbs) {
hbs.registerHelper('metaTitle', function(){
var urlPath = path.dirname();
console.log(urlPath);
switch(urlPath) {
case "/": {
return 'Index Test'
}
break;
case "/login": {
return 'Login Test'
}
break;
default: {
return 'No Meta Tag'
}
}
});
};
module.exports = metaHelpers;
Since your templates are executed on the server there is no window object to query. Instead you must get the URL path from the current Request object. Fortunately, in Express the Request object has a path property.
However in order to implement your solution as a Handlebars helper you would need to find a way to pass the Request object (or at least its .path) to the helper from the view. I think a better solution would be to execute your helper logic and construct the title before the response is rendered.
Express has the concept of middleware which are functions that can modify the Request and Response objects on a per request basis. We could write a middleware function to construct the title for each request and then add it to the locals property of the Response object. Properties of the res.locals object will be available to the view(s) rendered for the current response.
app.use(function (req, res, next) {
switch (req.path) {
case '/':
res.locals.title = 'Index Test';
break;
case '/login':
res.locals.title = 'Login Test';
break;
default:
res.locals.title = 'No Meta Tag';
}
next();
});
In our layout, we can access the title property as we would any other property of our view model.
<title>{{title}}</title>
For reference, this answer provides a similar solution, except that it assigns the req object to the res.locals.
In my restify server I have a route that I want to add a filter to. The way this would work in Express (this does work in Express) is the response.on would add my handler as the callback. Then before the response is returned to the requestor my handler would be called. This is not the behavior I am seeing in Restify. My guess is I am not registering my handler to the event machine properly.
restifyServer.get({ path: "/api/v1/readings", version: ["1.0.0"]}, Reading.getReadings);
I add a handler:
function zipkinTracing(request, response, next){
// handle the callback request and record the trace
var trace = zipkin.getTraceFromRequest(request, "ids-api", {address: config.externalIP, port: 80});
if (trace){
zipkin.sendAnnotationReceive(trace);
zipkin.sendAnnotation(trace, 'request.headers',JSON.stringify(request.headers));
trace.parentSpanId = trace.spanId;
zipkin.sendAnnotation(trace, 'http.uri', request.url);
var queryObj = urlLib.parse(request.url, true).query;
Object.keys(queryObj).forEach( function(key){
zipkin.sendAnnotation(trace, key, queryObj[key]);
});
request.zipkinTrace = trace;
}
response.on('after', function(request, response, route, error){
var t = request.zipkinTrace;
if (t) {
zipkin.sendAnnotation(t, 'http.response.code',response.code);
zipkin.sendAnnotationSend(t);
}
});
return next();
}
Now my routes look like this:
restifyServer.use(zipkinTracing);
restifyServer.get({ path: "/api/v1/readings", version: ["1.0.0"]}, Reading.getReadings);
The problem is the response.on('after', is never fired. What am I doing wrong here?
Reading.getReadings looks like this:
makeHttpQuery(request, response, function(error, message) {
....
response.send(message['data']['data']);
return next();
}
I was able to resolve this issue. In order to create a true filter I registered the handler function to the response.on('finish', handler) as you would do in Express. The alternate solution would be to place
restifyServer.on('after', function(request, response){
var t = request.zipkinTrace;
if (t) {
zipkin.sendAnnotation(t, 'http.response.code',response.code);
zipkin.sendAnnotationSend(t);
}
});
after the route to be filtered. I prefer the response.on('finish', handler). Any other solutions or interesting ways to create a filter for requests and responses?
You can also add multiple handlers to the request. One handler will finish and pass on the the next one, much like a filter.
See: http://restify.com/#routing
I have two situations to get data from DB
To show normal data
http://exampleapp.com/task/{{taskId}}
To edit data via posting
http://exampleapp.com/task/{{taskId}}/?state={{app.state}}
Both url have the same http://exampleapp.com/task/{{taskId}} just a little bit different with last phrase ?state={{app.state}}
I use Express routing as followed:
app.get('/task/:taskId/(?state=:status(pending|cancel|confirmed|deleted))?', routes.task.show);
But I dont know why it does not work ?
For example error: Cannot GET /task/51d2c53f329b8e0000000001 when going to h**p://exampleapp.com/task/51d2c53f329b8e0000000001
Query strings cannot be defined in routes. You access query string parameters from req.query.
app.get('/task/:taskId', function(req, res) {
if (req.query.state == 'pending') { ... }
});
However, if you're modifying a task, this is not the appropriate way to do it. GET requests SHOULD be idempotent: the request SHOULD NOT modify state. That's what POST requests are for.
app.get('/task/:taskId', function(req, res) {
// show task info based on `req.params.taskId`
});
app.post('/task/:taskId', function(req, res) {
// set task `req.params.taskId` to state `req.body.state`
});
You could either have a <form> that posts to the task, or make an ajax request:
$.post('/task/1', { state: 'pending' }, function() { ... });
According to the Express API, you cannot mix RegExp routes with string routes.
You should do something like this (I'm assuming taskId is an integer):
app.get(/^\/task/([0-9]+)/(?state=:status(pending|cancel|confirmed|deleted))?, routes.task.show);
However, I don't see why you cannot only check if req.query.state is defined in your route. It's probably less error prone and easier:
app.get("/task/:taskId", function( req, res, next ) {
if (req.query.state) {
// Do things
}
next();
});
Your problem is that query strings are not considered in routing. You will either have to redesign your urls (ie, include the state into the url itself, instead of the query string) or check the query string in your route handler function.