Transform response of fastify-static served files

3.6k views Asked by At

I'm using fastify's fastify-static plugin, and need to transform files that it is serving. For example, I'd like to replace "this-link" with "that-link". I've tried various fastify.addHook() events listed here, https://www.fastify.io/docs/latest/Hooks/, and the sensible one to me is the "onSend" where they demonstrate [string].replace(), but all have failed. For the onSend handler, the payload is not a mutable string, but instead it appears to be a PassThrough readable stream. With that in mind, I tried inspecting the data with payload.on('data', (chunk) => {...}). That was insightful. I could see the file text, but I'm treading a bit deep into streams and fastify plugins and not sure how to proceed.

  1. Is there a simple way to transform responses before they are sent when using fastify-static? (Why are the documented addHook()'s failing?)
  2. Assuming I properly interpreted the payload as a readable stream, how can I transform that before fastify-static sends it?
1

There are 1 answers

1
Manuel Spigolon On BEST ANSWER

An endpoint can send strings, buffer or streams.

So the onSend hook will receive one of those data type.

As example:

const fastify = require('fastify')()
const fs = require('fs')
const path = require('path')

fastify.addHook('onSend', async (request, reply, payload) => {
  console.log(`Payload is a ${payload}`);
  return typeof payload
})

fastify.get('/string', (req, reply) => { reply.send('a string') })
fastify.get('/buffer', (req, reply) => { reply.send(Buffer.from('a buffer')) })
fastify.get('/stream', (req, reply) => { reply.send(fs.createReadStream(__filename)) })


fastify.inject('/string', (_, res) => console.log(res.payload))
fastify.inject('/buffer', (_, res) => console.log(res.payload))
fastify.inject('/stream', (_, res) => console.log(res.payload))

fastify-static sends files as a stream, so you would need to implement a Transform stream. Here a quick and dirty example assuming a static/hello file exists with content:

Hello %name

const { Transform } = require('stream')
const fastify = require('fastify')()
const fs = require('fs')
const path = require('path')

const transformation = new Transform({
  writableObjectMode: false,
  transform(chunk, encoding, done) {
    const str = chunk.toString()
    this.push(str.replace('%name', 'foo bar'))
    done()
  }
})

fastify.addHook('onSend', async (request, reply, payload) => {
  if (typeof payload.pipe === 'function') {
    // check if it is a stream
    return payload.pipe(transformation)
  }
  return payload
})

fastify.register(require('fastify-static'), {
  root: path.join(__dirname, 'static'),
  prefix: '/static/',
})

fastify.inject('/static/hello', (_, res) => console.log(res.payload))

As a suggestion, I would use a templating system from the point-of-view plugins, since it supports these kinds of features out of the box.