loopbackio / loopback-next

LoopBack makes it easy to build modern API applications that require complex integrations.
https://loopback.io
Other
4.95k stars 1.07k forks source link

RestServer and Socket.io #4496

Closed TheZwieback closed 3 years ago

TheZwieback commented 4 years ago

Hi,

I want to implement a WebSocket server on a loopback rest server. As the rest server is based on an express app with a node http server this should be straight forward. Unfortunately I found a minor issue. Accessing the node http server object is not possible in the RestApplication constructor as it is protected inside the RestServer class. I changed the properties on my machine locally and tested the standard deployment and it works as expected:

import { BootMixin } from '@loopback/boot';
import { ApplicationConfig } from '@loopback/core';
import {
  RestExplorerBindings,
  RestExplorerComponent,
} from '@loopback/rest-explorer';
import { RepositoryMixin } from '@loopback/repository';
import { RestApplication } from '@loopback/rest';
import { ServiceMixin } from '@loopback/service-proxy';
import * as path from 'path';
import { MySequence } from './sequence';
import { Server as ioServer, ServerOptions as ioServerOptions, Socket } from "socket.io";
import SocketIOServer = require('socket.io');

export class Bff extends BootMixin(
  ServiceMixin(RepositoryMixin(RestApplication)),
) {
  private ioServer: ioServer;

  constructor(options: ApplicationConfig = {}) {
    super(options);

    // Set up the custom sequence
    this.sequence(MySequence);

    // Set up default home page
    this.static('/', path.join(__dirname, '../public'));

    // Customize @loopback/rest-explorer configuration here
    this.bind(RestExplorerBindings.CONFIG).to({
      path: '/explorer',
    });
    this.component(RestExplorerComponent);

    this.projectRoot = __dirname;
    // Customize @loopback/boot Booter Conventions here
    this.bootOptions = {
      controllers: {
        // Customize ControllerBooter Conventions here
        dirs: ['controllers'],
        extensions: ['.controller.js'],
        nested: true,
      },
    };

    this.ioServer = SocketIOServer();
  }

  async start(): Promise<void> {
    await super.start();
    console.log(this.restServer.listening);
    this.ioServer.attach(this.restServer._httpServer!.server);
    console.log(this.ioServer);
  }
}

Would it be possible to make the base http Server object (this.restServer._httpServer) available through a getter? Using the RestServer instance in the ioServer.attach method does not work.

Thanks and cheers, Christian

raymondfeng commented 4 years ago

@TheZwieback Can you build on top of https://github.com/strongloop/loopback-next/tree/socketio/packages/socketio?

TheZwieback commented 4 years ago

@raymondfeng I looked at the package and it seems to instantiate a new http server to use in its middleware. It would be nice to just use an existing one, i.e. like the rest server described above. I do not need to expose the socket.io server through dedicated controller endpoints, instead I want the regular http rest endpoints to deliver progress updates through socket.io. Maybe a mixin would be possible to enhance the RestServer cleanly?

gemyero commented 4 years ago

@TheZwieback I write some code to solve this problem. I write a mixin called RevealServerMixin which can be invoked with RestApplication class and it do it. Here is the code.

// RevealServerMixin
import { Constructor } from '@loopback/core';
import { CustomRestComponent } from '../services/CustomRestComponent';
import { CustomRestServer } from '../services/CustomRestServer';

export function RevealServerMixin<T extends Constructor<any>>(
  BaseClass: T,
) {
  class Subclass extends BaseClass {
    constructor(...args: any[]) {
      super(...args);

      this.unbind('components.RestComponent');
      this.unbind('servers.RestServer');

      this.component(CustomRestComponent);
    }

    get restServer(): CustomRestServer {
      return this.getSync('servers.CustomRestServer');
    }
  }
  return Subclass;
}
// CustomRestComponent
import { RestComponent } from '@loopback/rest';
import { Constructor, Server } from '@loopback/core';
import { CustomRestServer } from './CustomRestServer';

export class CustomRestComponent extends RestComponent {
  servers: {
    [name: string]: Constructor<Server>
  } = {
    CustomRestServer,
  };
}
// CustomRestServer
import { RestServer } from '@loopback/rest';
import { HttpServer } from '@loopback/http-server';

export class CustomRestServer extends RestServer {
  get httpServer(): HttpServer | undefined {
    return this._httpServer;
  }
}

And in application.ts

export class MyApplication extends RevealServerMixin(RestApplication)
) {}
alexkander commented 4 years ago

Maybe this can help #4044

stale[bot] commented 3 years ago

This issue has been marked stale because it has not seen activity within six months. If you believe this to be in error, please contact one of the code owners, listed in the CODEOWNERS file at the top-level of this repository. This issue will be closed within 30 days of being stale.

stale[bot] commented 3 years ago

This issue has been closed due to continued inactivity. Thank you for your understanding. If you believe this to be in error, please contact one of the code owners, listed in the CODEOWNERS file at the top-level of this repository.