BackendStack21 / restana

Restana is a lightweight and fast Node.js framework for building RESTful APIs.
MIT License
467 stars 27 forks source link

Multiple route handlers #16

Closed kaesar closed 5 years ago

kaesar commented 5 years ago

I was wondering if restana (or find-my-way, or other) could provide multiple callback functions to handle a request, like route handlers in express. This is the main impact that I have for migrating to restana. restana is great, well thought, thanks.

sarriaroman commented 5 years ago

This is maybe helpful for you. It's a compatibility class that exposes an Express like interface. You can use the "serverCompat" variable to replace the express app.

const ServerCompat = require('./compat'),
        serverCompat = new ServerCompat({
            http: { allowHTTP1: true, key: /* KEY */, cert: /* CERT */ },
            env: 'dev'
        });
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
const restana = require('restana'), url = require('url'), https = require('https'), http2 = require('http2');
;
class ServerCompat {
    constructor(config = {
        server: {},
        http: {
            key: null,
            cert: null
        },
        useHttp2: true,
        env: 'prod'
    }) {
        this.config = config;
        this._router = {
            stack: []
        };
        this.disabledHeaders = [];
        this.type = 'restana';
        this.locals = {};
        this.httpServer = this.createHttpServer(config.useHttp2, config.http);
        this.server = restana(Object.assign({ ignoreTrailingSlash: true, server: this.httpServer }, config.server));
        this.server.use((...args) => {
            args[1].redirect = (url) => {
                args[1].send('', 301, {
                    Location: encodeURI(url)
                });
            };
            args[1].json = (data, statusCode) => {
                args[1].setHeader('Content-Type', 'application/json');
                args[1].send(data, statusCode || args[1].statusCode || 200);
            };
            args[1].set = (key, value) => {
                args[1].setHeader(key, value);
                return args[1];
            };
            args[1].status = (statusCode) => {
                try {
                    args[1].statusCode = statusCode;
                }
                catch (e) {
                    console.error(e);
                }
                return args[1];
            };
            args[1].cookie = (...params) => {
                console.info('Set Cookie', ...params);
            };
            args[0].get = (header) => {
                return args[0].headers[header] || args[0].headers[header.toLowerCase()];
            };
            args[0].query = args[0].query || {};
            args[1].on('response', e => {
                if (e.code >= 400) {
                    console.error(e);
                }
            });
            args[2]();
        });
    }
    isDev() {
        return this.config.env == 'dev';
    }
    get Router() {
        return this;
    }
    getRouter() {
        return this.server;
    }
    getServer() {
        return this.server;
    }
    getHttpServer() {
        return this.httpServer;
    }
    createHttpServer(useHttp2 = true, options) {
        if (useHttp2) {
            console.info('###### HTTP 2 ######');
            return http2.createSecureServer(Object.assign({}, options, { settings: { enablePush: true } }));
        }
        else {
            return https.createServer(options);
        }
    }
    manipulateHeadersMiddleware() {
        const disableHeadersMiddleware = (req, res, next) => {
            for (let key of this.disabledHeaders) {
                try {
                    res.removeHeader(key);
                }
                catch (e) { }
            }
            next();
        };
        return disableHeadersMiddleware;
    }
    listen(port) {
        return __awaiter(this, void 0, void 0, function* () {
            console.info('Registering routes & listen');
            this.use(this.manipulateHeadersMiddleware());
            let server = yield this.server.start(port);
            console.info('------------------------------------------------');
            console.info(`Server is running on http://0.0.0.0:${port}`);
            console.info('------------------------------------------------');
        });
    }
    compatHandler(method, url, ...params) {
        try {
            params = params.flat();
            let handler = params.pop();
            this.server[method.toLowerCase()](url, handler, {}, [...params]);
        }
        catch (e) {
            console.error(e);
        }
    }
    get(url, ...params) {
        this.compatHandler('GET', url, ...params);
    }
    post(url, ...params) {
        this.compatHandler('POST', url, ...params);
    }
    options(url, ...params) {
        this.compatHandler('OPTIONS', url, ...params);
    }
    put(url, ...params) {
        this.compatHandler('PUT', url, ...params);
    }
    head(url, ...params) {
        this.compatHandler('HEAD', url, ...params);
    }
    delete(url, ...params) {
        this.compatHandler('DELETE', url, ...params);
    }
    patch(url, ...params) {
        this.compatHandler('PATCH', url, ...params);
    }
    all(url, ...params) {
        this.get(url, ...params);
        this.post(url, ...params);
        this.options(url, ...params);
        this.put(url, ...params);
        this.head(url, ...params);
        this.delete(url, ...params);
        this.patch(url, ...params);
    }
    use(...params) {
        try {
            if (params.length == 1) {
                this.setMiddleware(params[0]);
            }
            else {
                this.setPathMiddleware(...params);
            }
        }
        catch (e) {
            console.error(e);
        }
    }
    setMiddleware(middleware) {
        if (this.isDev()) {
            console.info(middleware.name);
        }
        if (middleware && typeof (middleware) == 'function') {
            this.server.use((...args) => {
                try {
                    middleware(...args);
                }
                catch (e) {
                    console.error(middleware.name, e);
                    args[2]();
                }
            });
        }
        else {
            console.info('*** Undefined middleware ***');
        }
    }
    setPathMiddleware(...params) {
        params = params.flat();
        const path = params.shift();
        console.info('Creating middleware for path: ', path);
        for (let i = 0; i < params.length; i++) {
            if (params[i] && typeof (params[i]) == 'function') {
                this.setMiddleware((req, res, next) => {
                    try {
                        let parsedUrl = url.parse(req.url);
                        if (parsedUrl.pathname == path) {
                            return params[i](req, res, next);
                        }
                        else {
                            next();
                        }
                    }
                    catch (e) {
                        console.error(e);
                        next();
                    }
                });
            }
        }
    }
    disable(key) {
        this.disabledHeaders.push(key);
    }
    enable(key) {
        console.info(`Can't enable ${key}`);
    }
    set(key, value) {
        console.info('Nothing to do here with ', key, ' => ', value);
    }
}

module.exports = ServerCompat;
kaesar commented 5 years ago

Thank you, I think it will work and I will be able to continue, only it can be nice within restana. So this could be closed if it's not necessary for restana.

sarriaroman commented 5 years ago

I think that the idea behind restana is to keep it minimal in order to be really fast, and it works really great. But is something for @jkyberneees :)

jkyberneees commented 5 years ago

Hi guys, thanks for your input here. @sarriaroman you are right, this feature is not supported either by the routers or by restana itself. We can seat an adapter on top, as you propose. If this can be part of restana project? I think so. Let's follow up here again...

Thanks for collaborating.

jkyberneees commented 5 years ago

Hi @kaesar, I was wondering if route level middlewares is a feature that satisfies your requirements: Can you please have a look at: https://github.com/jkyberneees/ana#route-level-middlewares Here you actually can "provide multiple callback functions" to handle your request.

Thanks

kaesar commented 5 years ago

Yeah, it could be. Just that for migrating is for routes in a simpler style, something like: app.get('/cool', fn1, fn2, fn3). I unknown the impact for restana, so I leave it to your discretion if you find it interesting to facilitate a migration from express.

jkyberneees commented 5 years ago

Hi @kaesar , JFYI: https://github.com/jkyberneees/ana/pull/20 Landing in 2.8.0 ;)

Regards

kaesar commented 5 years ago

That's great news. Thanks a lot.