node-http-server – Rev 38

Subversion Repositories:
Rev:
#!/usr/bin/env node

/*************************************************************************/
/*    Copyright (C) 2017 Wizardry and Steamworks - License: GNU GPLv3    */
/*************************************************************************/

const fs = require('fs')
const stream = require('stream')
const util = require('util')
const tz = require('moment-timezone')
const forge = require('node-forge')
const EventEmitter = require('events').EventEmitter

// Cache constructor.
function Cache(config, client, request, response) {
    // Create events emitters for logging and data.
    EventEmitter.call(this)

    // Pass through objects needed for caching.
    this.config = config
    this.client = client
    this.request = request
    this.response = response
}

// Cache handling.
Cache.prototype.process = function(resource, input, type) {
    EventEmitter.call(this)
    const self = this

    // Read the resource and cache or not depending on configuration settings.
    fs.stat(resource, (error, stats) => {
        var expires = 0
        Object.keys(self.config.site.cache).forEach((key) => {
            self.config.site.cache[key].forEach((expire) => {
                if (expire.test(resource)) {
                    expires = key
                }
            })
        })

        switch (self.request.httpVersion) {
            case '1.1': // HTTP 1.1
                self.response.setHeader('Cache-Control',
                    "max-age=" + expires + ", public"
                )
                const sha1 = forge.md.sha1.create()
                const data = new stream.Readable({
                    objectMode: true,
                    read(size) {}
                })
                input
                    .on('data', (chunk) => {
                        sha1.update(chunk)
                        data.push(chunk)
                    })
                    .on('end', () => {
                        const etag = sha1.digest().toHex()

                        // Set the ETag for the resource.
                        self.response.setHeader('ETag', etag)

                        const ifNoneMatch = Object
                            .getOwnPropertyNames(self.request.headers)
                            .filter((header) => header.toUpperCase() ===
                                'If-None-Match'.toUpperCase())

                        const ifModifiedSince = Object
                            .getOwnPropertyNames(self.request.headers)
                            .filter((header) => header.toUpperCase() ===
                                'If-Modified-Since'.toUpperCase())

                        if ((ifNoneMatch.length !== 0 &&
                                self.request.headers[ifNoneMatch].toUpperCase() === etag.toUpperCase()) ||
                            (ifModifiedSince.length !== 0 &&
                                tz(self.request.headers[ifModifiedSince]).tz('UTC') < tz(stat.mtime).tz('UTC'))) {
                            // Send a cache hit response.
                            self.emit('log', {
                                message: 'Client: ' +
                                    self.client.address + ':' +
                                    self.client.port +
                                    ' cached resource: ' +
                                    resource,
                                severity: 'info'
                            })
                            self.emit('data', {
                                status: 304,
                                data: new stream.Readable({
                                    read(size) {
                                        this.push(null)
                                    }
                                }),
                                type: type
                            })
                            return
                        }

                        // Send the resource.
                        self.emit('log', {
                            message: 'Client: ' +
                                self.client.address + ':' +
                                self.client.port +
                                ' sent resource: ' +
                                resource,
                            severity: 'info'
                        })
                        data.push(null)
                        self.emit('data', {
                            status: 200,
                            data: data,
                            type: type
                        })
                    })

                return
            default:
                self.response.setHeader('Last-Modified',
                    tz(stats.mtime)
                    .tz('UTC')
                    .format("ddd, DD MMM YYYY HH:mm:ss z")
                )
                self.response.setHeader('Expires',
                    tz()
                    .tz('UTC')
                    .add(expires, 'seconds')
                    .format("ddd, DD MMM YYYY HH:mm:ss z")
                )
                // Send the resource.
                self.emit('log', {
                    message: 'Client: ' +
                        self.client.address + ':' +
                        self.client.port +
                        ' sent resource: ' +
                        resource,
                    severity: 'info'
                })
                self.emit('data', {
                    status: 200,
                    data: input,
                    type: type
                })
                break
        }
    })

    return this
}

util.inherits(Cache, EventEmitter)
module.exports = Cache

Generated by GNU Enscript 1.6.5.90.