Call Express router manually - node.js

Н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.

Related

How to modify a response in Electron

Let's say that I'm using a GET request on https://example.com and the response is this:
This is a response message.
How would I modify it in a way so that in my code, so that it can change the response to say something like this:
This is a MODIFIED response message.
For example, if my Electron app were to navigate to https://example.com, the screen would show me the modified content instead of the original content.
Essentially, I am trying to literally modify the request.
I have based my code off of this question but it only shows a proof of concept with a pre-typed Buffer, as in my situation I'd like modify the response instead of outright replacing it. So, my code looks like this:
protocol.interceptBufferProtocol("http", (req, CALLBACK) => {
if(req.url.includes("special url here")) {
var request = net.request({
method: req.method,
headers: req.headers,
url: req.url
});
request.on("response", (rp) => {
var d = [];
rp.on("data", c => d.push(c));
rp.on("end", () => {
var e = Buffer.concat(d);
console.log(e.toString());
// do SOMETHING with 'e', the response, then callback it.
CALLBACK(e);
});
});
request.end();
} else {
// Is supposedly going to carry out the request without interception
protocol.uninterceptProtocol("http");
}
}
This is supposed to manually request the URL, grab the response and return it. Without the protocol event, it works and gives me a response, but after some debugging, this piece of code consistently calls the same URL over and over with no response.
There is also the WebRequest API, but there is no way of modifying the response body, you can only modify the request headers & related content.
I haven't looked fully into Chromium-based solutions, but after looking at this, I'm not sure if it is possible to modify the response so it appears on my app's end in the first place. Additionally, I'm not familiar with the Chromium/Puppeteer messages that get sent all over the place.
Is there an elegant way to have Electron to get a URL response/request, call the URL using the headers/body/etc., then save & modify the response to appear different in Electron?

NodeJS middleware how to read from a writable stream (http.ServerResponse)

I'm working on a app (using Connect not Express) composed of a set of middlewares plus node-http-proxy module, that is, I have a chain of middlewares like:
midA -> midB -> http-proxy -> midC
In this scenario, the response is wrote by the http-proxy that proxies the request to some target and returns the content.
I would like to create a middleware (say midB) to act as a cache. The idea is:
If url is cached the cache-middleware writes the response and avoids continuing the middleares chain.
If url is not cached the cache-middleware passes the request within the middlewares chain bit requires to read the final response content to be cached.
How can achieve this? Or there is another approach?
Cheers
Answering myself.
If you have a middleware like function(req, res, next){..} and need to read the content of the response object.
In this case the res is a http.ServerResponse object, a writable stream where every middleware in the chain is allowed to write content that will conform the response we want to return.
Do not confuse with the response you get when make a request with http.request(), that is a http.IncomingMessage which in fact is a readable stream.
The way I found to read the content all middlewares write to the response is redefining the write method:
var middleare = function(req, res, next) {
var data = "";
res._oldWrite = res.write;
res.write = function(chunk, encoding, cb) {
data += chunck;
return res._oldWrite.call(res, chunck, encoding, cb);
}
...
}
Any other solutions will be appreciated.

Differentiate between nodejs request and response

There is a generic structure of nodejs callback functions :
function(req,res){
//handle callback
}
I just want, callback should work correctly even if sometimes i write in mistake (res, req)
Given mixture of req or res, how do i find which one is actually request and which one is response.
req is an IncomingMessage object and res is a ServerResponse object.
So check for unique properties on each, for example if the particular object has a writeHead() function, then it's the response object.
You may also be able to use instanceof to check: res instanceof http.ServerResponse.
Functions in JavaScript are not programmatically prototyped by parameter names. The length property of a function only provides the number of parameters specified in the definition:
var fn = function (one,two,three) { return "x"; };
console.log(fn.length); // 3
Although there are ways to retrieve these names (see this question), usually procedures simply ignore how you name the parameters of your functions/closures, and instead assume that you are following the proposed API.
For this reason, it remains as the best practice to pay attention to the API and name parameters accordingly. In a Node.js HTTP request listener, the request comes always before the response (it is documented and many examples are available). As mentioned by other answers, you can dynamically check whether the request is an http.IncomingMessage or whether the response is an http.ServerResponse, but it seems to me that you can avoid introducing an overhead just with proper naming.
With that said, given the variables req and res, it is easy to make a check at the top of a function body, like the code below. However, do note that this would only be remedying what can be prevented by just following the API contracts, and as thus I cannot recommend it (unless you really want to make functions with a more flexible API).
function(res,req) {
if (req instanceof http.ServerResponse) {
// wrong order, swap.
var t = req;
req = res;
res = t;
}
// handle request
}

How to save Backbone JSONP model to MongoDB using Node/Express?

I need a way to parse my JSONP object on server side to save it, due to cross domain origin issue I have shifted my way of communication from JSON to JSONP but not finding any suitable way to parse JSONP on server side to save it to the database.
Following is the Model,
define(['backbone'],function(Backbone){
'use strict';
return Backbone.Model.extend({
url:"http://crossdomain:9847/page",
defaults: {
type:'text',
position:0,
align:'left',
text:{"en":""},
color:"#000",
weight:'normal',
size:"14px",
font:"Verdana",
pageid:'askdkasdkgaskdgks'
},
idAttribute:'_id',
sync: function(method, collections, options) {
options.dataType = "jsonp";
return Backbone.sync(method, collections, options);
}
});
});
Express Server,
var express = require('/root/node_modules/express');
var page = require('./routes/page.js');
var app = express();
app.configure(function () {
app.use(express.json());
app.use(express.urlencoded());
app.set("jsonp callback", true);
})
app.get('/page', page.updatePage);
app.listen(9847);
exports.updatePage = function(req, res) {
console.log(req.query);
// Here how I can parse the req is my problem
// so I can save object to database?
}
URL is generating like,
http://crossdomain:9847/page?callback=jQuery203010156283635587138_1384408493698&{%22text%22:{%22en%22:%22Text%22},%22type%22:%22text%22,%22position%22:0,%22align%22:%22left%22,%22color%22:%22#000%22,%22weight%22:%22normal%22,%22size%22:%2214px%22,%22font%22:%22Verdana%22,%22pageid%22:%22askdkasdkgaskdgks%22}&_=1384408493700
and I am able to receive,
{ callback: 'jQuery203010156283635587138_1384408493698',
'{"text":{"en":"Text"},"type":"text","position":0,"align":"left","color":"': '' }
Now how can I parse this ? I can get callback from callback parameter, but how to get actual data ?
You can't parse the result because it's not valid JSON. Your problem is probably in this line:
app.set("jsonp callback", true);
This is where you set the JSONP callback variable, for example changing it from the default of callback to instead be callbackVariable.
Just comment out that line, and the JSONP you get back will hopefully be parseable. Or, you might also have to fix how Backbone is constructing the JSONP URL. If you instead used a URL like http://crossdomain:9847/page?callback=jQuery203010156283635587138_1384408493698&type=text&position=0&align=left&color=%23000&weight=normal&size=14px&font=Verdana&pageid=askdkasdkgaskdgks I believe it would work. Backbone seems to be adding additional encoding into the values in the URL, which makes parsing harder.
Finally, if you need help easily picking specific values out of a (valid) JSON string that has been parsed into a Javascript object, take a look at the many useful function in lodash.

Express.js routing with optional param?

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.

Resources