fluffy – Rev 1

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.