Short story:
I wish to add a custom value to each http response with express using a middleware. I don't want it to be in my body. How to do it?
Long story:
The specific value is a string named apiVersion.
I wrote this middleware:
function apiVersionMiddleware(req, res, next) {
res.apiVersion = "1.1";
next();
}
I see the apiVersion field populated in my response object in my backend. However, I don't see it in my http response.
After reading some express documentation, I found: https://expressjs.com/en/api.html#res.append. But is it a good practice to add a custom response in http headers? Should it be here or in another variable?
Realized that my question was more about the use of http headers.
Found this in the documentation response header. On the headers page, you can find this definition for response's headers:
Response header: Headers with additional information about the response, like its location or about the server itself (name and version etc.).
So it perfectly fits my use case.
That led me to this solution in express documentation: https://expressjs.com/en/api.html#res.set
function apiVersionMiddleware(req, res, next) {
res.set("api-version", "1.1");
next();
}
I also had to add my custom header to my exposedHeaders in cors:
app.use(
cors({
exposedHeaders: ["api-version"]
})
);
Related
Нello! I am looking to call a function which has been passed to an expressRouter.post(...) call.
This expressRouter.post(...) call is occurring in a file which I am unable to modify. The code has already been distributed to many clients and there is no procedure for me to modify their versions of the file. While I have no ability to update this file for remote clients, other developers are able to. I therefore face the issue of this POST endpoint's behaviour changing in the future.
I am also dealing with performance concerns. This POST endpoint expects req.body to be a parsed JSON object, and that JSON object can be excessively large.
My goal is to write a GET endpoint which internally activates this POST endpoint. The GET endpoint will need to call the POST endpoint with a very large JSON value, which has had URL query params inserted into it. The GET's functionality should always mirror the POST's functionality, including if the POST's functionality is updated in the future. For this reason I cannot copy/paste the POST's logic. Note also that the JSON format will never change.
I understand that the issue of calling an expressjs endpoint internally has conventionally been solved by either 1) extracting the router function into an accessible scope, or 2) generating an HTTP request to localhost.
Unfortunately in my case neither of these options are viable:
I can't move the function into an accessible scope as I can't modify the source, nor can I copy-paste the function as the original version may change
Avoiding the HTTP request is a high priority due to performance considerations. The HTTP request will require serializing+deserializing an excessively large JSON body, re-visiting a number of authentication middlewares (which require waiting for further HTTP requests + database queries to complete), etc
Here is my (contrived) POST endpoint:
expressRouter.post('/my/post/endpoint', (req, res) => {
if (!req.body.hasOwnProperty('val'))
return res.status(400).send('Missing "val"');
return res.status(200).send(`Your val: ${req.body.val}`);
});
If I make a POST request to localhost:<port>/my/post/endpoint I get the expected error or response based on whether I included "val" in the JSON body.
Now, I want to have exactly the same functionality available, but via GET, and with "val" supplied in the URL instead of in any JSON body. I have attempted the following:
expressRouter.get('/my/get/endpoint/:val', (req, res) => {
// Make it seem as if "val" occurred inside the JSON body
let fakeReq = {
body: {
val: req.params.val
}
};
// Now call the POST endpoint
// Pass the fake request, and the real response
// This should enable the POST endpoint to write data to the
// response, and it will seem like THIS endpoint wrote to the
// response.
manuallyCallExpressEndpoint(expressRouter, 'POST', '/my/post/endpoint', fakeReq, res);
});
Unfortunately I don't know how to implement manuallyCallExpressEndpoint.
Is there a solution to this problem which excludes both extracting the function into an accessible scope, and generating an HTTP request?
This seems possible, but it may make more sense to modify req and pass it, rather than create a whole new fakeReq object. The thing which enables this looks to be the router.handle(req, res, next) function. I'm not sure this is the smartest way to go about this, but it will certainly avoid the large overhead of a separate http request!
app.get('/my/get/endpoint/:val', (req, res) => {
// Modify `req`, don't create a whole new `fakeReq`
req.body = {
val: req.params.val
};
manuallyCallExpressEndpoint(app, 'POST', '/my/post/endpoint', req, res);
});
let manuallyCallExpressEndpoint = (router, method, url, req, res) => {
req.method = method;
req.url = url;
router.handle(req, res, () => {});
};
How about a simple middleware?
function checkVal(req, res, next) {
const val = req.params.val || req.body.val
if (!val) {
return res.status(400).send('Missing "val"');
}
return res.status(200).send(`Your val: ${val}`);
}
app.get('/my/get/endpoint/:val', checkVal)
app.post('/my/post/endpoint', checkVal)
This code isn't tested but gives you rough idea on how you can have the same code run in both places.
The checkVal function serves as a Express handler, with request, response and next. It checks for params first then the body.
Introduction
I have running Azure Function, which is written as Expressjs application.
I have simple routes there to test
// Test get
router.get('/test', (req, res) => {
return res.status(200).send({ result: req.query });
});
// Test post
router.post('/test', (req, res) => {
return res.status(200).send({ result: req.body });
});
The app configuration is the following
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cors());
Issue
I'm trying to do POST /test request.
With Content-Type: application/javascript it works well
With Content-Type: application/json it calls function, but nothing executes, and timeout is returned.
There is no issue with GET /test route.
There is no issue running locally.
Need help to understand why Azure Function call doesn't work properly for POST requests with Content-Type: application/json. Thank you
use :
app.use(bodyParser.json({ type: 'application/*+json' }));
to allow custom json types
Going deeper how Azure Function and body parser work, I figured out the root of the problem.
Be careful with request object
In regular expressjs app we have request object, which is actually instance of Stream object, and all incoming data goes through this streaming.
So, we have raw data, and we need to parse it right way. bodyParser middleware has several methods to parse this data from stream based on it's type (Content-Type).
For example bodyParser.json() method tries to parse data in JSON format with application/json content type. Once this function parses data req.body is fulfilled with JSON object.
Azure Functions don't support streaming
This is where everything breaks :) Yes, it doesn't support streaming. And since this is true data is accepted different way. So request object is no longer Stream object. And req.body is already fulfilled with incoming data. For application/json it contains JSON object, for other content types it contains raw data which is needed to be parsed. As example I can share my simple middleware code for parsing data with type x-www-form-urlencoded.
'use strict';
// https://github.com/sindresorhus/query-string
const queryString = require('query-string');
/**
* Azure body parser
*/
function azureBodyParser() {
return function(req, res, next) {
// x-www-form-urlencoded
if (req.headers['content-type'] === 'application/x-www-form-urlencoded') {
req.body = queryString.parse(req.body, { arrayFormat: 'bracket' });
}
next();
};
}
module.exports = azureBodyParser;
Then use it like this
const app = require('express')();
const azureBodyParser = require('./middlewares/azureBodyParser');
// ...
app.use(azureBodyParser());
You can change it and add more handlers for other content types.
Also we need to provide some condition whether we run our app on server or as Azure Function, it's possible using ENV variables for example. It's your home task :)
The problem
Since Azure Functions don't support streaming, bodyParser.json() tries to get data from stream, and this is a point where app stuck. As a result function execution ends up with timeout.
Hope it will be helpful for all who struggling with Azure... Good luck :)
Might be late to this thread . Had a similar issue migrating an express app to Azure Functions and had to consume x-form-urlencoded values .
Used
const qs = require('qs');
While evaluating the request checked if the requst has rawBody and the relevant headers
if(req.rawBody)
and then simply
parsedBody = qs.parse(req.rawBody);}
Hope this helps someone.
It is a pain to host an Express API as is due to the discontinued support in Azure Functions for express
I am working on sample application using Node.js for server side and Angular 2 for front end.
To prevent CSRF attacks , I have used "csurf" middleware
Below is the relevant code to set the middleware
// cookie parser
app.use(cookieParser());
// express session middleware , this should be after cookie parser
app.use(session({secret:'clickclick'}));
app.use(session({
secret: 'clickclick',
cookie: {
path:'/',
httpOnly:true,
maxAge:null
}
}));
// CSRF middleware
app.use(csurf());
Below node.js route sets "_csrf" header
router.get('/:id/products/:pid' , wrap(function *(req , res , next) {
try
{
console.log('url' , req.url);
res.setHeader('_csrf', req.csrfToken());
let product = yield category.getProduct(req , res , next);
res.send(product);
}
catch(err)
{
res.status(500).send(err);
}
}))
The above mentioned route '/:id/products/:pid' is called from my below Angular 2 service method
// Get Product
GetProduct(id:string, pid:string):Observable<Product> {
return this.http.get('./categories/' + id + '/products/' + pid)
.map(data =>{ let headers:Headers = data.headers;
this.csrfToken = headers.get('_csrf') ;
return data.json() })
.catch(this.handleError);
}
This method assigns the _csrf header returned from server to "this.csrfToken" property.
And when the below service method makes an AJAX POST request , it uses the "this.csrfToken" property value set by above method and sets header "_csrf" value.
// Add an item to cart
AddTocart(product:Product)
{
let item = { pid:product._id , name:product.name , price:product.price , qty:1 , total:product.price };
//this.cart.push(item);
// make an AJAX call to save the item in server session
let url = './cart/add';
let headers = new Headers({'Content-Type':'application/json' , '_csrf':this.csrfToken});
let requestOptions = new RequestOptions({headers:headers});
this.http.post(url , item , requestOptions)
.map(data => {
this.cart.push(item);
}
)
.catch(this.handleError)
.subscribe( data => { });
}
Below is the Response Header of GetProduct service method.
And below is the request Header of "AddTocart" service method.
Any idea what is causing "ForbiddenError: invalid csrf token" error.
Please let me know if I need to provide more information or if the information provided is not clear.
I know this is an older question, but I'm adding this here in case someone stumbles across it in the future. Working on a similar project and encountered the same error, I fixed it by adding a XSRF-TOKEN header in the POST request, with the value taken from $.cookie("XSRF-TOKEN") (using jquery and the cookies plugin). According to the docs, _csrf should also work though.
From the project page :
The default value is a function that reads the token from the following locations, in order:
req.body._csrf - typically generated by the body-parser module.
req.query._csrf - a built-in from Express.js to read from the URL query string.
req.headers['csrf-token'] - the CSRF-Token HTTP request header.
req.headers['xsrf-token'] - the XSRF-Token HTTP request header.
req.headers['x-csrf-token'] - the X-CSRF-Token HTTP request header.
req.headers['x-xsrf-token'] - the X-XSRF-Token HTTP request header.
As far as I can tell, the error seems to come from POST / PUT requests including the correct cookies, but nodejs / csurf isn't looking for them there.
In your specific case, _csrf should be in the request body along with the cart items, or the header should be renamed to csrf-token, or one of the other options.
As I understand, when you are building a http response in node/express or whatever, the process consists of primarily two non-sequential steps: defining Headers and constructing the body. Headers include Set-Cookie headers. In Express, the following methods are available with the response object for setting headers:
res.append(); // To append/create headers
res.cookie(); // A convenience method to append set-cookie headers.
As headers are only buffered and not actually sent until the response is sent, is there any method or mechanism to get the current list of headers set, along with their values, something like:
headers = res.getHeaders(); //Returns an object with headers and values
headers = res.getHeaders('Set-Cookie'); // To get only select headers
try
console.log("res._headers >>>>>>>" + JSON.stringify(res._headers));
I've managed to inspect what is being sent (including cookies) using response.getHeaders() (available since Node 7.7.0) in combination with on-headers's module. Something like this:
import express from 'express'
import onHeaders from 'on-headers'
const router = express.Router()
function responseDebugger() {
console.log(JSON.stringify(this.getHeaders()))
}
router.post('/', (req, res, next) => {
onHeaders(res, responseDebugger)
res.json({})
})
export default router
In the following Express function:
app.get('/user/:id', function(req, res){
res.send('user' + req.params.id);
});
What are req and res? What do they stand for, what do they mean, and what do they do?
Thanks!
req is an object containing information about the HTTP request that raised the event. In response to req, you use res to send back the desired HTTP response.
Those parameters can be named anything. You could change that code to this if it's more clear:
app.get('/user/:id', function(request, response){
response.send('user ' + request.params.id);
});
Edit:
Say you have this method:
app.get('/people.json', function(request, response) { });
The request will be an object with properties like these (just to name a few):
request.url, which will be "/people.json" when this particular action is triggered
request.method, which will be "GET" in this case, hence the app.get() call.
An array of HTTP headers in request.headers, containing items like request.headers.accept, which you can use to determine what kind of browser made the request, what sort of responses it can handle, whether or not it's able to understand HTTP compression, etc.
An array of query string parameters if there were any, in request.query (e.g. /people.json?foo=bar would result in request.query.foo containing the string "bar").
To respond to that request, you use the response object to build your response. To expand on the people.json example:
app.get('/people.json', function(request, response) {
// We want to set the content-type header so that the browser understands
// the content of the response.
response.contentType('application/json');
// Normally, the data is fetched from a database, but we can cheat:
var people = [
{ name: 'Dave', location: 'Atlanta' },
{ name: 'Santa Claus', location: 'North Pole' },
{ name: 'Man in the Moon', location: 'The Moon' }
];
// Since the request is for a JSON representation of the people, we
// should JSON serialize them. The built-in JSON.stringify() function
// does that.
var peopleJSON = JSON.stringify(people);
// Now, we can use the response object's send method to push that string
// of people JSON back to the browser in response to this request:
response.send(peopleJSON);
});
I noticed one error in Dave Ward's answer (perhaps a recent change?):
The query string paramaters are in request.query, not request.params. (See https://stackoverflow.com/a/6913287/166530 )
request.params by default is filled with the value of any "component matches" in routes, i.e.
app.get('/user/:id', function(request, response){
response.send('user ' + request.params.id);
});
and, if you have configured express to use its bodyparser (app.use(express.bodyParser());) also with POST'ed formdata. (See How to retrieve POST query parameters? )
Request and response.
To understand the req, try out console.log(req);.