Closed alejandroSuch closed 6 years ago
@alejandroSuch https://docs.nestjs.com/quick-start/gateways.html
By default - server runs on port 80 and with default namespace. We can easily change those settings:
How about do not add port in config?
Yes but what I want to do is to listen the same port as the express application (i.e. port 3000). To do this, you have to attach the socket.io server to a HttpServer instance instead of doing it to a port.
@alejandroSuch did you end up wiring together a solution for this?
I need to know if there's any way to get the http server instance from the websocket initializer. If there's a way, it's quite simple
Hi @alejandroSuch, Not possible now, definitely needed, marked as a todo. Thanks!
For now I just wrote my own adapter: (I also use ws rather than socket.io)
class WebSocketAdapter
implements NestWebSocketAdapter {
private readonly logger = this.loggingService.create(WebSocketAdapter.name);
constructor(
private readonly server: http.Server,
private readonly path: string,
private readonly authService: AuthService,
private readonly loggingService: LoggingService
) {}
public create() {
return new WebSocket.Server({
server: this.server,
path: this.path,
verifyClient: ({ req }: { req: http.IncomingMessage }) => {
const jwt = this.getJwtFromRequest(req);
return (
jwt && !(this.authService.verifyAndDecode(jwt) instanceof Error)
);
}
});
}
public bindClientConnect(
server: WebSocket.Server,
callback: (client: Client) => void
) {
server.on("connection", (socket, req) => {
// Coercion to nonnull and Claims is possible because we provide
// `verifyClient` to WebSocket.Server.
const jwt = this.getJwtFromRequest(req);
const claims = this.authService.skipVerifyAndDecode(jwt!) as Claims;
const client = new Client(socket, claims);
callback(client);
});
}
// Explicit `this` preservation due to https://github.com/kamilmysliwiec/nest/issues/150
public bindClientDisconnect = (
client: Client,
callback: (client: Client) => void
) => {
client.ws.on("close", () => {
callback(client);
});
};
public bindMessageHandlers(
client: Client,
handlers: MessageMappingProperties[]
) {
client.ws.on("message", async data => {
if (typeof data !== "string") {
this.logger.warn(
`Received "${typeof data}" message, expected: "string"`
);
return;
}
let json: { [k: string]: any };
// Parse
try {
json = JSON.parse(data);
} catch (e) {
this.logger.error(
`Error while JSON decoding message`,
e instanceof Error ? e.stack : undefined
);
return;
}
// Verify shape
const type = json["type"];
if (typeof type !== "string") {
this.logger.error(
`Invalid "type" field ("${typeof type}"), expected: "string"`
);
return;
}
// Find a message handler
const handler = handlers.find(h => h.message === type);
if (!handler) {
this.logger.warn(`Received unknown message "${type}"`);
return;
}
// Execute handler
try {
await handler.callback(json);
} catch (e) {
this.logger.error(
`Error occurred during "${type}" handler`,
e instanceof Error ? (e.stack ? e.stack : e.message) : undefined
);
return;
}
});
}
private getJwtFromRequest(req: http.IncomingMessage): string | null {
if (req.url) {
const { query } = url.parse(req.url, true);
if (query && typeof query.jwt === "string") {
return query.jwt;
}
}
return null;
}
}
/**
* A wrapper to associate claims (from the URL) with a WebSocket. This allows us
* to know who is connected to each socket.
*/
class Client {
constructor(public readonly ws: WebSocket, public readonly claims: Claims) {}
public send(message) {
this.ws.send(JSON.stringify(message));
}
}
And then use my own HTTP server:
const express = Express();
const server = http.createServer(express);
express.use(morgan(config.DEBUG ? "dev" : "combined"));
express.use(bodyParser.json());
express.use(cors());
express.use(compression());
const nest = NestFactory.create(ApplicationModule, express);
const environmentService = new EnvironmentService();
nest.useGlobalFilters(new DbErrorFilter(), new JsonValidationErrorFilter());
nest.useWebSocketAdapter(
new RealtimeGateway.WebSocketAdapter(
server,
"/realtime",
new AuthService(environmentService),
new LoggingService(environmentService)
)
);
nest.init();
server.listen(config.PORT, () => {
new Logger("http").log(`Listening on port ${config.PORT}`);
});
Cool! Wouln't be difficult to adapt the same philosophy to any implementation (socket.io, sockjs).
there is an easily way to extend IoAdapter
import { IoAdapter } from '@nestjs/websockets';
import * as io from 'socket.io'
import * as http from 'http'
export class ExtendedSocketIoAdapter extends IoAdapter {
protected ioServer: SocketIO.Server
constructor(protected server: http.Server) {
super()
this.ioServer = io(server)
}
create (port: number) {
console.log('websocket gateway port argument is ignored by ExtendedSocketIoAdapter, use the same port of http instead')
return this.ioServer
}
}
and server.js bootstrap
const expressApp = require('express')();
const server = require('http').createServer(expressApp);
const app = await NestFactory.create(ApplicationModule, expressApp);
app.useWebSocketAdapter(new ExtendedSocketIoAdapter(server))
await app.init()
server.listen(8000);
and I suggest "WebSocketGateway" default to create an io instance with the same port.
@xujif does your solution doesn't throws you an error:
GET http://localhost:3000/socket.io/?EIO=3&transport=polling&t=Lyv3Nn7 404 (Not Found)
?
I'm running with https.Server wrapping io server in a very similar manner on 4433 for everything & it works just fine.
@d3viant0ne can you show your code?
Hi @alejandroSuch,
It's available from v4.2.0. (just left @WebSocketGateway()
empty)
This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
Hi there!
Is there any way to make an equivalent to this classic configuration in nest?
My application has only one gateway and I would like it to use the same port as the main express application.
Thanks a lot! Alex