fluffy – Rev 6

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

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

// Import packages.
const https = require('https');
const http = require('http');
const path = require('path');
const fs = require('fs');
const url = require('url');
const moment = require('moment');
const winston = require('winston');
const yargs = require('yargs');
const dns = require('dns');
const was = require("was.js");

// Local imports.
const GET = require(
    path
    .resolve(
        path.dirname(require.main.filename),
        'src/methods',
        'get'
    )
);
const POST = require(
    path
    .resolve(
        path.dirname(require.main.filename),
        'src/methods',
        'post'
    )
);
const certs = require(
    path
    .resolve(
        path.dirname(require.main.filename),
        'src/server',
        'certs'
    )
);

// Load configuration file.
const config = require(
    path
    .resolve(
        path.dirname(require.main.filename),
        'config'
    )
);

// Get command-line arguments.
const argv = yargs
    .version()
    .option('root', {
        alias: 'd',
        describe: 'Path to the document root',
        demandOption: true
    })
    .help()
    .argv

// Create various logging mechanisms.
// RFC5424 - { emerg: 0, alert: 1, crit: 2, error: 3, warning: 4, notice: 5, info: 6, debug: 7 }
winston.setLevels(winston.config.syslog.levels);
const log = new winston.Logger({
    transports: [
        new winston.transports.File({
            level: 'info',
            filename: path.resolve(
                path.dirname(require.main.filename),
                config.log.file
            ),
            handleExceptions: true,
            json: false,
            maxsize: 1048576, // 1MiB.
            maxFiles: 10, // Ten rotations.
            colorize: false,
            timestamp: () => moment()
                .format('YYYYMMDDTHHmmss')
        }),
        new winston.transports.Console({
            level: 'info',
            handleExceptions: true,
            json: false,
            colorize: true,
            timestamp: () => moment()
                .format('YYYYMMDDTHHmmss')
        })
    ],
    exitOnError: false
});

fs.realpath(argv.root, (error, root) => {
    if (error) {
        log.error('Could not find document root: ' + argv.root);
        process.exit(1);
    }

    // Start HTTP server.
    http.createServer(
        // authentication,
        (request, response) => {
            // Grab connecting address.
            const address = request.socket.address();
            
            // Configuration path requested, so send the server configuration if allowed.
            if(config.configuration.enable === true &&
                url.parse(request.url, true).path === 
                config.configuration.path) {
                log.info('HTTP Server configuration requested by: ' +
                    address.address + ':' +
                    address.port
                );
                response.setHeader('Content-Type', 'application/json');
                response.end(JSON.stringify(config));
                return;
            }
            
            // Switch on HTTP method.
            was.lambda.switch(
                request.method,
                (o) => {
                    log.info('Unsupported HTTP \'' + request.method + '\' method requested by: ' +
                        address.address + ':' +
                        address.port
                    );
                },
                (o) => o === 'GET',
                (o) => {
                    // Send the resource.
                    new GET().process(config, request, response, root)
                        .on('log', (data) => {
                            log.log(data.severity, data.message);
                        })
                        .on('data', (result) => {
                            response.writeHead(result.status);
                            result.data
                                .on('readable', () => result.data.pipe(response))
                                .on('end', () => response.end());
                        });
                    return true;
                },
                (o) => o === 'POST',
                (o) => {
                    new POST().process(config, request, response, root)
                        .on('log', (data) => {
                            log.log(data.severity, data.message);
                        })
                        .on('data', (result) => {
                            response.setHeader('Content-Type', result.type);
                            response.writeHead(result.status);
                            result.data
                                .on('readable', () => result.data.pipe(response))
                                .on('end', () => response.end());
                        });
                    return true;
                }
            );
        }
    ).listen(config.net.port, config.net.address, () => {
        log.info('HTTP Server accessible at: http://' +
            config.net.address +
            ':' +
            config.net.port +
            ' and serving files from directory: ' +
            root
        );
    });

    // Start HTTPs server if enabled.
    if (config.ssl.enable) {
        // Generate certificates for HTTPs.
        certs.generate(
            config.site.name,
            config.net.address,
            config.ssl.privateKeySize,
            (certificates) => {
                https.createServer(
                    // authentication,
                    {
                        key: certificates.privateKey,
                        cert: certificates.certificate,
                    },
                    (request, response) => {
                        // Grab connecting address.
                        const address = request.socket.address();
                        
                        // Configuration path requested, so send the server configuration if allowed.
                        if(config.configuration.enable === true &&
                            url.parse(request.url, true).path === 
                            config.configuration.path) {
                            log.info('HTTP Server configuration requested by: ' +
                                address.address + ':' +
                                address.port
                            );
                            response.setHeader('Content-Type', 'application/json');
                            response.end(JSON.stringify(config));
                            return;
                        }
                        
                        // Switch on HTTP method.
                        was.lambda.switch(
                            request.method,
                            (o) => {
                                log.info('Unsupported HTTP \'' + request.method + '\' method requested by: ' +
                                    address.address + ':' +
                                    address.port
                                );
                            },
                            (o) => o === 'GET',
                            (o) => {
                                // Send the resource.
                                new GET().process(config, request, response, root)
                                    .on('log', (data) => {
                                        log.log(data.severity, data.message);
                                    })
                                    .on('data', (result) => {
                                        response.setHeader('Content-Type', result.type);
                                        response.writeHead(result.status);
                                        result.data
                                            .on('readable', () => result.data.pipe(response))
                                            .on('end', () => response.end());
                                    });
                                return true;
                            },
                            (o) => o === 'POST',
                            (o) => {
                                new POST().process(config, request, response, root)
                                    .on('log', (data) => {
                                        log.log(data.severity, data.message);
                                    })
                                    .on('data', (result) => {
                                        response.setHeader('Content-Type', result.type);
                                        response.writeHead(result.status);
                                        result.data
                                            .on('readable', () => result.data.pipe(response))
                                            .on('end', () => response.end());
                                    });
                                return true;
                            }
                        );
                    }
                ).listen(config.ssl.port, config.ssl.address, () => {
                    log.info('HTTPs Server accessible at: https://' +
                        config.ssl.address +
                        ':' +
                        config.ssl.port +
                        ' and serving files from directory: ' +
                        root
                    );
                })
            }
        );
    }
});