Why is the request handler called continuously (like every second) in node? - node.js

I've got this (complete code – well, sort of):
const fs = require('fs');
const http = require('http');
const url = require('url');
const path = require('path');
const Handlebars = require('handlebars');
const Router = require('./router');
const Utils = require('./utils');
const utils = new Utils();
const views = require('./views');
let hostname,
server,
router = new Router();
const port = ( process.argv.length > 2 ) ? parseInt(process.argv[process.argv.length - 1]) : 3000;
function install ( config ) {
let install_processes = [
installControllers( config ),
installModels( config ),
views( config )
];
return Promise.all( install_processes );
}
function installControllers ( config ) {
// To keep example compact, this return a Promise
}
function installModels ( config ) {
// To keep example compact, this return a Promise
}
function handleRequest ( req, res ) {
let r = global.routes.find( ( obj ) => obj.route === req.url );
if ( r ) {
let template = global.routes.views.layout,
output = template( req.body );
r.action();
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html');
res.end( output );
} else {
res.statusCode = 404;
res.setHeader('Content-Type', 'text/plain');
res.end("404: page not found, " + req.url);
}
}
var fw = (function ( ƒ ) {
ƒ.init = function Init ( config ) {
config = config;
hostname = config.hostname ? config.hostname : '127.0.0.1';
install( config )
.then(function () {
ƒ.startServer();
})
.catch(function ( err ) {
console.error("Error", err);
})
}
ƒ.startServer = function () {
server = http.createServer( handleRequest ).listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}`);
});
}
return ƒ;
}( fw || {} ));
module.exports = fw;
Please note, that variables and handlers are set elsewhere.
Everything works fine. But why is the handleRequest handler called every second (without any incoming requests. And it's actually called TWICE within that second)? If I add a console.log it gets output every second or so.
I would expect it to be called ONLY when actual requests are made to the server. Is this some sort of timeout handler that runs in the background or the like?

Why is handle triggered twice when starting the server, that is?
Try adding this to a file (wtf.js) and run: node wtf 9000;
```
const http = require('http');
function handle ( req, res ) {
console.log("Handling request");
}
var fw = (function () {
http.createServer( handle ).listen(9000, '127.0.0.1', () => {
console.log("Server running");
});
}());
module.exports = fw;
```
And actually, while writing this post I can see in the console, that it gets triggered again ... minutes later. Something spooky is going in here.

In case you run into the same 'situation':
Normally working in FireFox. At some point I had opened Chrome to use the debugger, and forgot all about it. THAT was the bugger continuously sending requests in the background ... sigh! Yes, web dev IS hard.
So check all possible incoming channels ; )

Related

Online compiler with node.js

I am trying to build an online compiler IDE and I always get an error that I can't understand or solve. the project/application consists of (index.html - package.json - app.js) the "app.js" refers to the server side which surely causes this error ("File not found: Firefox can’t find the file at /C:/Users/Mr_Tech/Desktop/online_judge_system/compilecode"). this is the code:
// including all packages and modules that will be used
var express = require('express');
var path = require('path');
var bodyParser = require('body-parser');
var compiler = require('compilex');
// create a app variable for using express packages and body-parser
var app = express();
//app.use(bodyParser());
app.use(express.urlencoded({ extended: true }))
//Then initialize comiler options from compilex package and link "/" root page with the "index.html".
var option = {stats : true};
compiler.init(option);
app.get('/' , function (req , res ) {
res.sendfile(__dirname +"/index.html");
});
// define the post funtion for the language you want your compiler to have and with input or without input options
app.post('\compilecode' , function (req , res ) {
var code = req.body.code;
var input = req.body.input;
var inputRadio = req.body.inputRadio;
var lang = req.body.lang;
if((lang === "C") || (lang === "C++"))
{
if(inputRadio === "true")
{
var envData = { OS : "windows" , cmd : "g++"};
compiler.compileCPPWithInput(envData , code ,input , function (data) {
if(data.error)
{
res.send(data.error);
}
else
{
res.send(data.output);
}
});
}
else
{
var envData = { OS : "windows" , cmd : "g++"};
compiler.compileCPP(envData , code , function (data) {
if(data.error)
{
res.send(data.error);
}
else
{
res.send(data.output);
}
});
}
}
if(lang === "Java")
{
if(inputRadio === "true")
{
var envData = { OS : "windows" };
console.log(code);
compiler.compileJavaWithInput( envData , code , function(data){
res.send(data);
});
}
else
{
var envData = { OS : "windows" };
console.log(code);
compiler.compileJavaWithInput( envData , code , input , function(data){
res.send(data);
});
}
}
if( lang === "Python")
{
if(inputRadio === "true")
{
var envData = { OS : "windows"};
compiler.compilePythonWithInput(envData , code , input , function(data){
res.send(data);
});
}
else
{
var envData = { OS : "windows"};
compiler.compilePython(envData , code , function(data){
res.send(data);
});
}
}
}); // end POST function
// set the port for the server to listen and and define the get function to send the output data generated after the executed code to the server.
app.get('/fullStat' , function(req , res ){
compiler.fullStat(function(data){
res.send(data);
});
});
app.listen(8000);
// function to delete all the temporary files created during the process
compiler.flush(function(){
console.log('All temporary files flushed !');
});
Nope. The problem is you are using a file:// path when this needs to be run as a HTTP server and you need to go to localhost:8000. You also used a backslash as a web path which is invalid.

Execute a middleware one-time only at server startup in Koa v2

I created this middleware which executing only once when any route in the website gets the first hit from a visitor:
// pg-promise
const db = require('./db/pgp').db;
const pgp = require('./db/pgp').pgp;
app.use(async (ctx, next) => {
try {
ctx.db = db;
ctx.pgp = pgp;
} catch (err) {
debugErr(`PGP ERROR: ${err.message}` || err);
}
await next();
});
// One-Time middleware
// https://github.com/expressjs/express/issues/2457
const oneTime = (fn) => {
try {
let done = false;
const res = (ctx, next) => {
if (done === false) {
fn(ctx, next);
done = true;
}
next();
};
return res;
} catch (err) {
debugErr(`oneTime ERROR: ${err.message}` || err);
}
};
const oneTimeQuery = async (ctx) => {
const result = await ctx.db.proc('version', [], a => a.version);
debugLog(result);
};
app.use(oneTime(oneTimeQuery));
This code executing on the first-time only when a user visiting the website, resulting:
app:log Listening on port 3000 +13ms
app:req GET / 200 - 24ms +2s
23:07:15 connect(postgres#postgres)
23:07:15 SELECT * FROM version()
23:07:15 disconnect(postgres#postgres)
app:log PostgreSQL 9.6.2, compiled by Visual C++ build 1800, 64-bit +125ms
My problem is that I want to execute it at the server start, when there's no any visit on the site.
The future purpose of this code will be to check the existence of tables in the database.
Solution:
Placing this in ./bin/www before the const server = http.createServer(app.callback()); declaration helped:
const query = async () => {
const db = require('../db/pgp').db;
const pgp = require('../db/pgp').pgp;
const result = await db.proc('version', [], a => a.version);
debugLog(`www: ${result}`);
pgp.end(); // for immediate app exit, closing the connection pool (synchronous)
};
query();
You could start your application using a js script that requires your app and uses node's native http module to fire up the server. Exactly like in koa-generator (click).
This is in your app.js file:
const app = require('koa')();
...
module.exports = app;
And then this is in your script to fire up the server:
const app = require('./app');
const http = require('http');
[this is the place where you should run your code before server starts]
const server = http.createServer(app.callback());
server.listen(port);
Afterwards you start your application with:
node [script_name].js
Of course keep in mind the async nature of node when doing it this way. What I mean by that - run the 'listen' method on 'server' variable in callback/promise.

Superagent with absolute url prefix

I've noticed that I'm writing http://localhost everytime I want to run a node test with superagent.
import superagent from 'superagent';
const request = superagent.agent();
request
.get('http://localhost/whatever')
.end((err, res) => { ... });
Is there any way of avoiding the localhost part?
As far as I've gone is to avoid the request being hardcoded to the host:
const baseUrl = 'http://localhost:3030';
request
.get(`${baseUrl}/whatever`)
But I still have to carry the baseUrl with the agent everytime.
While not so recently updated a package as superagent-absolute, superagent-prefix is officially recommended, and has the highest adoption as of 2020.
It is such a simple package that I would not be concerned with the lack of updates.
Example usage:
import superagent from "superagent"
import prefix from "superagent-prefix"
const baseURL = "https://foo.bar/api/"
const client = superagent.use(prefix(baseURL))
TL;DR: superagent-absolute does exactly that.
Detailed:
You can create one abstraction layer on top of superagent.
function superagentAbsolute(agent) {
return baseUrl => ({
get: url => url.startsWith('/') ? agent.get(baseUrl + url) : agent.get(url),
});
}
⬑ That would override the agent.get when called with a starting /
global.request = superagentAbsolute(agent)('http://localhost:3030');
Now you would need to do the same for: DELETE, HEAD, PATCH, POST and PUT.
https://github.com/zurfyx/superagent-absolute/blob/master/index.js
const OVERRIDE = 'delete,get,head,patch,post,put'.split(',');
const superagentAbsolute = agent => baseUrl => (
new Proxy(agent, {
get(target, propertyName) {
return (...params) => {
if (OVERRIDE.indexOf(propertyName) !== -1
&& params.length > 0
&& typeof params[0] === 'string'
&& params[0].startsWith('/')) {
const absoluteUrl = baseUrl + params[0];
return target[propertyName](absoluteUrl, ...params.slice(1));
}
return target[propertyName](...params);
};
},
})
);
Or you can simply use superagent-absolute.
const superagent = require('superagent');
const superagentAbsolute = require('superagent-absolute');
const agent = superagent.agent();
const request = superagentAbsolute(agent)('http://localhost:3030');
it('should should display "It works!"', (done) => {
request
.get('/') // Requests "http://localhost:3030/".
.end((err, res) => {
expect(res.status).to.equal(200);
expect(res.body).to.eql({ msg: 'It works!' });
done();
});
});

Koajs and Socket.io integration

Please, need help with integration of sockets with this koajs skeleton
Here is the code from server side file (/src/index.js)
'use strict';
// 3rd party
require('dotenv').config(); // Load env vars from .env, always run this early
const koa = require('koa');
const bouncer = require('koa-bouncer');
const nunjucksRender = require('koa-nunjucks-render');
const debug = require('debug')('app:index');
// 1st party
const config = require('./config');
const mw = require('./middleware');
const belt = require('./belt');
const cancan = require('./cancan');
////////////////////////////////////////////////////////////
const app = koa();
app.poweredBy = false;
app.proxy = config.TRUST_PROXY;
////////////////////////////////////////////////////////////
// Configure view-layer (nunjucks)
//
// We can override options send directly to nunjucks.
// https://mozilla.github.io/nunjucks/api.html#configure
////////////////////////////////////////////////////////////
const nunjucksOptions = {
// `yield this.render('show_user')` will assume that a show_user.html exists
ext: '.html',
noCache: config.NODE_ENV === 'development',
// don't throw template errors in development if we try to render
// a null/undefined like {{ x }}. in theory, setting it to true prevents
// bugs and forces you to be explicit about {{ x or '' }}, but in reality,
// often more annoying than it's worth.
throwOnUndefined: false,
// globals are bindings we want to expose to all templates
globals: {
// let us use `can(USER, ACTION, TARGET)` authorization-checks in templates
can: cancan.can,
},
// filters are functions that we can pipe values to from nunjucks templates.
// e.g. {{ user.uname | md5 | toAvatarUrl }}
filters: {
json: x => JSON.stringify(x, null, ' '),
formatDate: belt.formatDate,
nl2br: belt.nl2br,
md5: belt.md5,
toAvatarUrl: belt.toAvatarUrl,
autolink: belt.autolink,
},
};
////////////////////////////////////////////////////////////
// Middleware
////////////////////////////////////////////////////////////
app.use(mw.ensureReferer());
app.use(require('koa-helmet')());
app.use(require('koa-compress')());
app.use(require('koa-static')('public'));
// Don't show logger in test mode
if (config.NODE_ENV !== 'test') {
app.use(require('koa-logger')());
}
app.use(require('koa-body')({ multipart: true }));
app.use(mw.methodOverride()); // Must come after body parser
app.use(mw.removeTrailingSlash());
app.use(mw.wrapCurrUser());
app.use(mw.wrapFlash('flash'));
app.use(bouncer.middleware());
app.use(mw.handleBouncerValidationError()); // Must come after bouncer.middleware()
app.use(nunjucksRender('views', nunjucksOptions));
// Provide a convience function for protecting our routes behind
// our authorization rules. If authorization check fails, 404 response.
//
// Usage:
//
// router.get('/topics/:id', function*() {
// const topic = yield db.getTopicById(this.params.id);
// this.assertAuthorized(this.currUser, 'READ_TOPIC', topic);
// ...
// });
app.use(function*(next) {
this.assertAuthorized = (user, action, target) => {
const isAuthorized = cancan.can(user, action, target);
const uname = (user && user.uname) || '<Guest>';
debug('[assertAuthorized] Can %s %s: %s', uname, action, isAuthorized);
this.assert(isAuthorized, 404);
};
yield* next;
});
////////////////////////////////////////////////////////////
// Routes
////////////////////////////////////////////////////////////
app.use(require('./routes').routes());
app.use(require('./routes/authentication').routes());
app.use(require('./routes/admin').routes());
////////////////////////////////////////////////////////////
// If we run this file directly (npm start, npm run start-dev, node src/index.js)
// then start the server. Else, if we require() this file (like from
// our tests), then don't start the server and instead just export the app.
if (require.main === module) {
app.listen(config.PORT, function() {
console.log('Listening on port', config.PORT);
});
} else {
module.exports = app;
}
On client side (/views/master.js) :
....
<script src="https://cdn.socket.io/socket.io-1.4.5.js"></script>
<script>
var socket = io();
socket.on('news', function (data) {
console.log('received news with data: ');
console.log(data);
});
function myclick () {
console.log("click");
socket.emit('click', { clickdata: 'someone clicked on the button' });
}
</script>
<button type="button" onclick="myclick();">Click Me and watch console at server and in browser.</button>
....
Please, can someone explain to me hot to integrate socket.io with koas server? I've installed ["socket.io": "^1.4.5"]. What are the next steps?
PS: Sory about my ugly english.
I've founded a solution here. Bellow is a general code sample
const Koa = require( 'koa' )
const IO = require( 'koa-socket' )
const app = new Koa()
const io = new IO()
app.use( ... )
io.attach( app )
io.on( 'join', ( ctx, data ) => {
console.log( 'join event fired', data )
})
app.listen( process.env.PORT || 3000 )

express logging response body

The title should be pretty self explanetory.
For debugging purposes, I would like express to print the response code and body for every request serviced. Printing the response code is easy enough, but printing the response body is trickier, since it seems the response body is not readily available as a property.
The following does NOT work:
var express = require('express');
var app = express();
// define custom logging format
express.logger.format('detailed', function (token, req, res) {
return req.method + ': ' + req.path + ' -> ' + res.statusCode + ': ' + res.body + '\n';
});
// register logging middleware and use custom logging format
app.use(express.logger('detailed'));
// setup routes
app.get(..... omitted ...);
// start server
app.listen(8080);
Of course, I could easily print the responses at the client who emitted the request, but I would prefer doing at the server side too.
PS: If it helps, all my responses are json, but hopefully there is a solution that works with general responses.
Not sure if it's the simplest solution, but you can write a middleware to intercept data written to the response. Make sure you disable app.compress().
function logResponseBody(req, res, next) {
var oldWrite = res.write,
oldEnd = res.end;
var chunks = [];
res.write = function (chunk) {
chunks.push(chunk);
return oldWrite.apply(res, arguments);
};
res.end = function (chunk) {
if (chunk)
chunks.push(chunk);
var body = Buffer.concat(chunks).toString('utf8');
console.log(req.path, body);
oldEnd.apply(res, arguments);
};
next();
}
app.use(logResponseBody);
I ran into an issue using the approach suggested by Laurent. Sometimes chunk is a string, and therefore causes problems in the call to Buffer.concat(). Anyways, I found a slight modification fixed things:
function logResponseBody(req, res, next) {
var oldWrite = res.write,
oldEnd = res.end;
var chunks = [];
res.write = function (chunk) {
chunks.push(new Buffer(chunk));
oldWrite.apply(res, arguments);
};
res.end = function (chunk) {
if (chunk)
chunks.push(new Buffer(chunk));
var body = Buffer.concat(chunks).toString('utf8');
console.log(req.path, body);
oldEnd.apply(res, arguments);
};
next();
}
app.use(logResponseBody);
The above accepted code has issues with ES6.
Use the below code
function logReqRes(req, res, next) {
const oldWrite = res.write;
const oldEnd = res.end;
const chunks = [];
res.write = (...restArgs) => {
chunks.push(Buffer.from(restArgs[0]));
oldWrite.apply(res, restArgs);
};
res.end = (...restArgs) => {
if (restArgs[0]) {
chunks.push(Buffer.from(restArgs[0]));
}
const body = Buffer.concat(chunks).toString('utf8');
console.log({
time: new Date().toUTCString(),
fromIP: req.headers['x-forwarded-for'] ||
req.connection.remoteAddress,
method: req.method,
originalUri: req.originalUrl,
uri: req.url,
requestData: req.body,
responseData: body,
referer: req.headers.referer || '',
ua: req.headers['user-agent']
});
// console.log(body);
oldEnd.apply(res, restArgs);
};
next();
}
module.exports = logReqRes;
You can use express-winston and configure using:
expressWinston.requestWhitelist.push('body');
expressWinston.responseWhitelist.push('body');
Example in coffeescript:
expressWinston.requestWhitelist.push('body')
expressWinston.responseWhitelist.push('body')
app.use(expressWinston.logger({
transports: [
new winston.transports.Console({
json: true,
colorize: true
})
],
meta: true, // optional: control whether you want to log the meta data about the request (default to true)
msg: "HTTP {{req.method}} {{req.url}}", // optional: customize the default logging message. E.g. "{{res.statusCode}} {{req.method}} {{res.responseTime}}ms {{req.url}}"
expressFormat: true, // Use the default Express/morgan request formatting, with the same colors. Enabling this will override any msg and colorStatus if true. Will only output colors on transports with colorize set to true
colorStatus: true, // Color the status code, using the Express/morgan color palette (default green, 3XX cyan, 4XX yellow, 5XX red). Will not be recognized if expressFormat is true
ignoreRoute: function (req, res) { return false; } // optional: allows to skip some log messages based on request and/or response
}));
This solution might not be heavyweight enough for some use cases, but I think it's the simplest. It's also typescript compatible. If you only want logging for JSON responses, all you have to do is substitute the send method with the json method in the code below. Note, I took inspiration from Jonathan Turnock's answer, but made it simpler.
app.use((req, res, next) => {
let send = res.send;
res.send = c => {
console.log(`Code: ${res.statusCode}`);
console.log("Body: ", c);
res.send = send;
return res.send(c);
}
next();
});
I found the simplest solution to this problem was to add a body property to the res object when sending the response, which can later be accessed by the logger. I add this to my own namespace that I maintain on the req and res objects to avoid naming collisions. e.g.
res[MY_NAMESPACE].body = ...
I have a utility method that formats all responses to my standardized API/JSON response, so adding this one liner there exposed the response body when the logging gets triggered by onFinished event of res.
Most of the suggestions seemed a little sledgehammer, Spent some time with this issue tonight and wrote up my findings after digging into a few libs to help make something bespoke.
//app.js
...
app.use(requestLoggerMiddleware({ logger: console.log }));
app.get(["/", "/api/health"], (req, res) => {
res.send({ message: "OK", uptime: process.uptime() });
...
});
// middleware.js
/**
* Interceptor function used to monkey patch the res.send until it is invoked
* at which point it intercepts the invokation, executes is logic such as res.contentBody = content
* then restores the original send function and invokes that to finalize the req/res chain.
*
* #param res Original Response Object
* #param send Original UNMODIFIED res.send function
* #return A patched res.send which takes the send content, binds it to contentBody on
* the res and then calls the original res.send after restoring it
*/
const resDotSendInterceptor = (res, send) => (content) => {
res.contentBody = content;
res.send = send;
res.send(content);
};
/**
* Middleware which takes an initial configuration and returns a middleware which will call the
* given logger with the request and response content.
*
* #param logger Logger function to pass the message to
* #return Middleware to perform the logging
*/
const requestLoggerMiddleware = ({ logger }) => (req, res, next) => {
logger("RECV <<<", req.method, req.url, req.hostname);
res.send = resDotSendInterceptor(res, res.send);
res.on("finish", () => {
logger("SEND >>>", res.contentBody);
});
next();
};
module.exports = { requestLoggerMiddleware };
Full working example and article in the git repo
https://github.com/JonathanTurnock/ReqResLoggingExample
I actually made this nifty little npm to solve this exact problem, hope you like it!
https://www.npmjs.com/package/morgan-body
May be this would help someone who is looking to get the response logged
So, we use the middleware to intercept the request just before being served to the client. Then if we are using res.send method to send the data, override the method in the middleware and make sure to console log the body. If you are planning to use res.send alone then this should work fine, but incase if you use res.end or res.sendFile, then overwrite those methods and log only the required things (obviously logging the entire octet stream of file should never be logged for perfomance purposes.
Here I use pino as the logger. Created it as singleton service.
// LoggingResponseRouter.js
var loggingResponseRouter = require('express').Router();
var loggingService = require('./../service/loggingService');
var appMethodInstance = require('./../constants/appMethod');
var path = require('path');
var fs = require('fs');
var timeZone = require('moment-timezone');
var pino = require('pino')();
loggingResponseRouter.use((req, res, next) => {
// set the fileName it needs to log
appMethodInstance.setFileName(__filename.substring(__filename.lastIndexOf(path.sep) + 1, __filename.length - 3));
//loggingService.debugAndInfolog().info('logging response body', appMethodInstance.getFileName());
let send = res.send;
res.send = function(body){
loggingService.debugAndInfolog().info('Response body before sending: ', body);
send.call(this, body);
}
next();
});
module.exports = loggingResponseRouter;
Main file - Main.js
const corsRouter = require('./app/modules/shared/router/corsRouter');
const logRequestRouter = require('./app/modules/shared/router/loggingRequestRouter');
const loggingResponseRouter = require('./app/modules/shared/router/loggingResponseRouter');
const express = require('express');
var path = require('path');
const app = express();
// define bodyparser middleware
const bodyParser = require('body-parser');
const port = process.env.PORT || 3000;
// Now use the middleware prior to any others
app.use(bodyParser.json());
// use this to read url form encoded values as wwell
app.use(bodyParser.urlencoded({extended:true}));
console.log('before calling cors router in main js');
app.use(corsRouter);
app.use(logRequestRouter);
app.use(loggingResponseRouter);
app.get('/api', (req, res) => {
console.log('inside api call');
res.send('aapi');
});
app.listen(port, () => {
console.log('starting the server');
});
And this is the loggingService - loggingService.js
var pino = require('pino');
var os = require('os');
var appMethodInstance = require('./../constants/appMethod');
var pinoPretty = require('pino-pretty');
var moment = require('moment');
var timeZone = require('moment-timezone');
class Logger{
constructor(){
this.appName = 'Feedback-backend';
this.filenameval = '';
}
getFileName(){
console.log('inside get filename');
console.log(appMethodInstance.getFileName());
if(appMethodInstance.getFileName() === null || appMethodInstance.getFileName() === undefined){
this.filenameval = 'bootstrapping...'
}else {
this.filenameval = appMethodInstance.getFileName();
}
console.log('end');
return this.filenameval;
}
debugAndInfolog(){
return pino({
name: 'feedback-backend',
base: {
pid: process.pid,
fileName: this.getFileName(),
moduleName: 'modulename',
timestamp: timeZone().tz('America/New_York').format('YYYY-MM-DD HH:mm:ss.ms'),
hostName: os.hostname()
},
level: 'info',
timestamp: timeZone().tz('America/New_York').format('YYYY-MM-DD HH:mm:ss.ms'),
messageKey: 'logMessage',
prettyPrint: {
messageKey: 'logMessage'
}
});
}
errorAndFatalLog(){
return pino({
name: 'feedback-backend',
base: {
pid: process.pid,
fileName: this.getFileName(),
moduleName: 'modulename',
timestamp: timeZone().tz('America/New_York').format('YYYY-MM-DD HH:mm:ss.ms'),
hostName: os.hostname()
},
level: 'error',
timestamp: timeZone().tz('America/New_York').format('YYYY-MM-DD HH:mm:ss.ms'),
prettyPrint: {
messageKey: 'FeedbackApp'
}
});
}
}
module.exports = new Logger();
Typescript solution based on Laurent's answer:
import { NextFunction, Request, Response } from 'express-serve-static-core';
//...
app.use(logResponseBody);
function logResponseBody(req: Request, res: Response, next: NextFunction | undefined) {
const [oldWrite, oldEnd] = [res.write, res.end];
const chunks: Buffer[] = [];
(res.write as unknown) = function(chunk) {
chunks.push(Buffer.from(chunk));
(oldWrite as Function).apply(res, arguments);
};
res.end = function(chunk) {
if (chunk) {
chunks.push(Buffer.from(chunk));
}
const body = Buffer.concat(chunks).toString('utf8');
console.log(new Date(), ` ↪ [${res.statusCode}]: ${body}`);
(oldEnd as Function).apply(res, arguments);
};
if (next) {
next();
}
}
I have similar need to this question.
Based on accepted answer, I modify it with proxy and trace response body only when it's json.
const traceMiddleware = (req, res, next) => {
const buffers = []
const proxyHandler = {
apply(target, thisArg, argumentsList) {
const contentType = res.getHeader('content-type')
if (
typeof contentType === 'string' && contentType.includes('json') && argumentsList[0]
) {
buffers.push(argumentsList[0])
}
return target.call(thisArg, ...argumentsList)
}
}
res.write = new Proxy(res.write, proxyHandler)
res.end = new Proxy(res.end, proxyHandler)
res.on('finish', () => {
// tracing logic inside
trace(req, res, Buffer.concat(buffers).toString('utf8'))
})
next()
}

Resources