Middlewares - rejetto/hfs GitHub Wiki

A middleware is a mechanism you can add to the server, doing some work on each request.

This can be inside a plugin, but also directly written into Admin > Options > Server code.

Language is JavaScript, executed by Node.js. More details in the plugins documentation.

A middleware has the form of a function that receives a Context object as a parameter. The context object contains all information about the http request and the http response.

Examples

Setting http header

exports.middleware = ctx => ctx.set('x-my-header', 'some value')

Custom reply to some address

exports.middleware = ctx => {
  if (ctx.path === '/some/address')
    ctx.body = 'my content'
}

Allow only Chrome as browser

exports.middleware = ctx => ctx.get('user-agent').includes('Chrome') || ctx.socket.destroy()

Ban Facebook spiders

exports.init = api => {
  return {
    middleware(ctx) {
      if (ctx.get('user-agent').includes('Facebook')) {
         api.addBlock({ ip: ctx.ip, comment: 'facebook' })
         ctx.socket.destroy()
      }
    }
  }
}

Calculate MD5 on uploads

(from version 0.53.0)

Sending back MD5 with in HTTP header X-MD5.

Method 1: calculate by reading file after it has been written

exports.init = api => {
    const { createHash } = api.require('crypto')
    const { createReadStream } = api.require('fs')
    return {
        // double arrow = return a function = operate at the end of the request, when upload is completed
        middleware: ctx => () => { 
            let f = ctx.state.uploadDestinationPath
            if (!f) return
            const hasher = createHash('md5')
            f = createReadStream(f)
            f.on('data', x => hasher.update(x))
            return new Promise((resolve, reject) => {
                f.once('end', () => {
                    ctx.set({ 'Content-Digest': `md5=:${hasher.digest('hex')}:` })
                    resolve()
                }).on('error', reject)
            })
        }
    }
}

Method 2: processing incoming stream

While technically this is not a middleware, it still acts similarly to one.

exports.init = api => {
    const { createHash } = api.require('crypto')
    const { Transform } = api.require('stream')
    return { // at exit time, "unload" will ensure the event "uploadStart" is unsubscribed
        unload: api.events.on('uploadStart', obj => {
            const hasher = createHash('md5')
            const original = obj.writeStream
            // replace the stream with ours. We'll both calculate md5 and pass data to the original stream
            obj.writeStream = new Transform({
                transform(chunk, encoding, cb) {
                    hasher.update(chunk)
                    original.write(chunk, encoding, cb)
                },
                flush(cb) {
                    obj.ctx.set({ 'Content-Digest': `md5=:${hasher.digest('hex')}:` })
                    cb()
                },
            })            
        })
    }
}

Manipulate text file content

In this example we want to replace NAME with John from all *.txt files as they are served, without changing the actual file on disk.

exports.init = api => {
  const consumers = api.require('stream/consumers') // nodejs standard library
  return {
    middleware(ctx) { // on every request
      return async () => { // act after the request has been worked by hfs, and we have ctx.body 
        if (!ctx.path.endsWith('.txt')) return // if it's not right extension, then quit
        const content = await consumers.text(ctx.body) // convert body from a stream to a string
        ctx.body = content.replaceAll('NAME', 'John') // make replacements
      }
    }
  }
}