poelstra / mhub

Simple, flexible message hub using websockets
MIT License
9 stars 7 forks source link

Discuss: passing in own http(s) server #20

Open rikkertkoppes opened 4 years ago

rikkertkoppes commented 4 years ago

To enable running a websocket server on an already existing http(s) server (like an express server), consider passing in an optional server instance

I would propose allowing a server instance in WSServerOptions

export interface WSServerOptions extends TlsOptions {
    type: "websocket";
    port?: number; // default 13900 (ws) or 13901 (wss)
    server?: http.Server | https.Server;    //optional server to pass in
}

And using that in startWebSocketServer or creating one if not given

This allows running http and ws over the same port as the rest of the application

Thoughts?

poelstra commented 4 years ago

Hmyeah, my original idea with this kind of stuff is different: the hub is the real core of the thing, and can be used by anyone who wants to publish/subscribe. This hub is then used by both the TCP and websocket transport, of which a 'default' implementation is used in the MServer class. The plan was that if you want to do your own, more custom stuff, you'd basically create your own MyMServer class, and do whatever you like with the Hub in there.

Thus, in a way, the MServer class is 'only' intended to be used with the simple command-line program mhub-server, assuming that if you want to embed it in your own app, you'd rather want to use the Hub and XXXConnection classes directly.

Alternatively, if you want to have the ease-of-use of the MServer config object, you can instantiate your own Hub, and pass it to the constructor of MServer, leave out the websocket transport option in its config, then set up your own websocket transport and connect it to the Hub.

The actual code of such websocket<->hub connection is trivial:

const wss = new ws.Server({ server: yourHttpServer, path: "/" });
wss.on("connection", (conn: ws) => {
    new WSConnection(hub, conn, "websocket" + this.connectionId++);
});

So I think it's already possible to do without further changes today, and that way is also more in the direction of the design.

Curious what you think, though.

poelstra commented 4 years ago

I've improved a few things and now added an example on how to set up a fully custom server.

See https://github.com/poelstra/mhub/blob/master/src/example/custom-server.ts and please let me know if this would work for you.

rikkertkoppes commented 4 years ago

I did not realize I had all the LEGO pieces available to build the entire server and shove in my own http server, so the example is much appreciated.

This approach does work for what I am currently trying to do (host a simple mhub + http server on heroku)

I do feel attaching your own http server would be a pretty common use case though. It kind of feels like an all or nothing scenario with regard to creating an MServer. You can either use MServer with configuration or build it all up yourself

I like the modular approach though, but you might want to add an in-between level. Two approaches come to mind:

// express server
const app = express();
const server = require("http").createServer(app);

class MyMServer extends MServer {
    protected startWebSocketServer(
        hub: Hub,
        options: WSServerOptions
    ): Promise<void> {
        return new Promise<void>((resolve, reject) => {
            const wss = new ws.Server({ server: <any>server, path: "/" });
            wss.on("connection", (conn: ws) => {
                // tslint:disable-next-line:no-unused-expression
                new WSConnection(hub, conn, "websocket" + this.connectionId++);
            });

            server.on("error", (e: Error): void => {
                reject(e);
            });
        });
    }
}

// mhub server
let mserver = new MyMServer({
    listen: [
        {
            type: "websocket"
        }
    ],
    bindings: [],
    nodes: {
        default: "HeaderStore"
    },
    storage: "./storage",
    users: {},
    rights: {
        "": true
    }
});

// start mhub server
mserver.init();

// start web server
server.listen(port, () => {
    console.log(`server running on port ${port}`);
});
poelstra commented 4 years ago

Agreed, while creating the example I was also thinking about making the MServer methods protected instead of private, such that this kind of subclassing is trivially possible too.

Are you in for creating a PR for that?

poelstra commented 4 years ago

Version 2.1.0 is published on NPM which contains the necessary changes to easily run your own custom server as demonstrated in the example :tada:

I'll leave this issue open to implement your other suggestion (protected instead of private methods on MServer class).