I am trying to implement WebSocket into nestjs app and I have problems with messages not reaching my handler in nestjs.
I am able to connect both ends and send message from nestjs to client but not the other way.
this is my nestjs code: (please note that i am not using socket.io i am implementing ws as WebSocket
import {
OnGatewayInit,
WebSocketGateway,
WebSocketServer,
} from '#nestjs/websockets';
import { Logger } from '#nestjs/common';
#WebSocketGateway(5015)
export class ExportFeedsGateway implements OnGatewayInit {
#WebSocketServer() wss: any;
private logger: Logger = new Logger('ExportFeedsGateway');
afterInit(server: any): any {
this.logger.log('Export Feeds Initialized');
}
handleConnection(client) {
client.send('message from server'); // this message is properly send and read on the client side
}
handleMessage(message) {
console.log('message', message); // this is never logged in nest
}
}
and some client code:
const WebSocket = require( 'ws');
...
this.ws = new WebSocket('ws://localhost:5015');
this.ws.on('open', () => {
this.ws.send('message from client') // this message never reaches server
});
...
Question is what is the problem with nestjs messages handler that it doesnt pick up messages?
You're missing the #SubscribeMessage('message') that you'll need for Nest to register the handler to the websocket server. handleConnection is a bit of a special case method as defined inthe lifecycle methods. Also, when sending the message from the ws library, use the form: ws.send('{"event": "message", "data": whateverDataObjectYouWant }', callback). This way, Nest can parse the event and properly route it
Related
The following basic code is used to handle requests from Angular client:
/************************************************************************/
/* Launch HTTP server
/************************************************************************/
http.createServer (function(req,res) {
let data='';
req.on ('data', chunk => {
//console.log (`Data chunk: ${chunk}`);
//append chunk to data (can be multiple chuncks for 1 request)
data += chunk;
});
req.on ('end', chunks => {
//console.log (`End chunks: ${data}`);
//Do something with request
});
}).listen (8000);
The HTTP request is converted to TCP raw message and sent to 3rd party server. This external server sends back TCP response which is sent to the Angular client.
The response sent back from Node.js to the client is not according to the order of original requests.
So an HTTP request in the client is getting a wrong response.
The client has multiple timers each sending a request every 1 second.
I want that while a client request is handled, Node.js will not accept any other new messages.
Server doesn't care about the order of requests, it receives the request, does its job and sends back a response. The order you need to organize should be in client side
The solution I found is to implement an Http interceptor in order to monitor the response from the server. In case the response contains a reply that has to be displayed, from the interceptor I called an Update method implemented in the relevant component.
import { Injectable } from '#angular/core';
import { HttpInterceptor, HttpEvent, HttpResponse, HttpRequest, HttpHandler } from '#angular/common/http';
import { Observable } from 'rxjs';
import { map} from 'rxjs/operators';
import { TelemetryComponent } from '../components/telemetry/telemetry.component';
import { InterruptsComponent } from '../components/interrupts/interrupts.component';
import {SharedobjectService} from 'src/app/service/sharedobject.service';
import { E_PC_TO_TGT_CODE} from 'src/app/icd/Header'
#Injectable()
export class Interceptor implements HttpInterceptor {
telemetry : TelemetryComponent;
interrupts : InterruptsComponent;
/******************************************************************************************/
constructor(private shared : SharedobjectService){
}
/******************************************************************************************/
intercept(httpRequest: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(httpRequest).pipe (
map (resp =>{
if (resp instanceof HttpResponse)
{
var arr32 = new Int32Array (resp.body);
switch (arr32[1])
{
case E_PC_TO_TGT_CODE.READ_TELEMETRY_REQUEST_CODE:
this.shared.telemetry.UpdateView (resp.body);
break;
case E_PC_TO_TGT_CODE.READ_INTERRUPTS_REQUEST_CODE:
this.shared.interrupts.UpdateView (resp.body);
break;
}
}
return resp;
})
)
}
}
According to many links I found, Node.js cannot "wait till client request is handled".
Is there a better solution ?
Thank you,
Zvika
I encountered odd problem when working with websocket. I am not sure exactly what is the reason for whats happening.
I have this simple websocket client that is implemented into nestjs service
import { Injectable } from '#nestjs/common';
import * as WebSocket from 'ws';
import { timer } from 'rxjs';
#Injectable()
export class ImportFeedsGateway {
private ws: WebSocket;
constructor() {
this.connect();
}
private connect() {
this.ws = new WebSocket(process.env.URL);
this.ws.on('open', () => {
this.ws.send(Math.random()); // this message is always send properly and this.ws state is 1 when logged after reconnect
});
this.ws.on('message', (message) => {
...
});
this.ws.on('error', (message) => {
this.ws.close();
});
this.ws.on('close', (message) => {
timer(1000).subscribe(() => {
this.connect();
});
});
}
sendMessage(message) {
this.ws.send(message); // this message is never send after reconnect and this.ws state is 3 when logged
}
}
So the problem is that after reconnect Math.random() message is always properly sent while attempt to use sendMessage method results in fail because this.ws state always is 3.
It seems like this.ws points to different places depending on if its accessed from outside of class (via sendMessage method) or from inside (via on('open', callback). Inside callback its always current this.ws while sendMessage accesses old this.ws in closed state. Its like this.ws reference is not wired properly.
Does anyone know why is that and how to fix it?
---- EDIT
after transforming sendMessage method to its arrow version, its working correctly, but its more of puzzle now why
sendMessage = (message) => {
this.ws.send(message); // with arrow version this.ws is always in state 1 and its working correctly
}
I am working with NestJS and I need to know when a client has forced the disconnection or has canceled it. (either by mistake or because they wanted to).
For exaple, in Express it's as easy as:
const express = require('express')
const app = express()
const port = 3000
app.get('/', (expressRequest, expressResponse) => {
// Detecting close event
expressRequest.on('close', function() {
console.log('Client connection closed....!');
});
// Detecting end event
expressRequest.on('end', function() {
console.log('Client connection end....!');
});
expressResponse.send('Hello World!')
})
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
The question is: working with NestJS, what is the correct way to do it?
The first thing I would try is using the #Req() param decorator. Assuming you're using Nests default Express adapter, then the request object received is the Express req object.
The following should work for you. The rest of this post is just cleaning it up and making it more "Nest".
import { Controller, Get, Req } from '#nestjs/common';
import { Request } from 'express';
#Controller()
export class AppController{
#Get()
test(#Req() req: Request): string {
req.on('close', () => console.log('Doing something with closed connection'))
return "Hello, world!"
}
}
If you're planning to reuse this logic in a few controller methods, then I would also consider creating a custom decorator for it:
import { createParamDecorator, ExecutionContext } from '#nestjs/common';
import { Observable } from 'rxjs';
import { Request } from 'express';
export const OnConnectionClosed = createParamDecorator(
(data: unknown, ctx: ExecutionContext) =>
new Observable((observer) => {
const request = ctx.switchToHttp().getRequest<Request>();
request.on('close', () => observer.complete());
}),
);
And then using it like the following:
#Controller()
export class AppController{
#Get()
test(#OnConnectionClosed() onClosed: Observable<void>): string {
onClosed.subscribe({
complete: () => console.log('Connection closed'),
});
return 'Hello, world!';
}
}
And with that, you've created your own "Nest" way to listen for close events on the incoming request.
Nestjs has many different components that are executed at different times during the life cycle of a request.
The order in which these components are executed would be the following
NestJs request Life cycle
Incoming request
Globally bound middleware
Module bound middleware
Global guards
Controller guards
Route guards
Global interceptors (pre-controller)
Controller interceptors (pre-controller)
Route interceptors (pre-controller)
Global pipes
Controller pipes
Route pipes
Route parameter pipes
Controller (method handler)
Service (if exists)
Route interceptor (post-request)
Controller interceptor (post-request)
Global interceptor (post-request)
Exception filters (route, then controller, then global)
Server response**
The answer to your question:
I think it should be detected in the following points
Global interceptor (post-request)
Controller interceptor (post-request)
I am trying to make a connection with websockets. If I need to connect to the postman I need to include app.useWebSocketAdapter(new WsAdapter(app)); to make it work. But once I include this line it stop connecting with my react native code. When I remove this line it starts connecting with react native code and not with postman. How can I make it work with both react native client side and postman.
Below is my gateway code
import { Logger } from '#nestjs/common';
import {
OnGatewayConnection,
OnGatewayDisconnect,
OnGatewayInit,
SubscribeMessage,
WebSocketGateway,
WebSocketServer,
} from '#nestjs/websockets';
import { Server, Socket } from 'socket.io';
#WebSocketGateway()
export class AppGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {
#WebSocketServer()
server!: Server;
private logger: Logger = new Logger('AppGateway');
#SubscribeMessage('msgToServer')
handleMessage(client: Socket, payload: string): void {
this.server.emit('msgToClient', payload);
}
afterInit(server: Server) {
this.logger.log('Init');
}
handleDisconnect(client: Socket) {
this.logger.log(`Client disconnected: ${client.id}`);
}
handleConnection(client: Socket, ...args: any[]) {
this.logger.log(`Client connected: ${client.id}`);
}
}
main.ts
import { AppModule } from 'app.module';
import { WsAdapter } from '#nestjs/platform-ws';
async function setupApplication(): Promise<INestApplication> {
app.useWebSocketAdapter(new WsAdapter(app));
await app.listen(port);
return app;
}
In the above main.ts file. If I remove app.useWebSocketAdapter(new WsAdapter(app)); then I am able to connect with my react native client code but then not with postman.
react native code
import React, {useEffect} from 'react';
import {Button, SafeAreaView} from 'react-native';
import io from 'socket.io-client';
const socket = io('http://localhost:3000');
export default function App() {
const receiveMessage = () => {
socket.on('msgToClient', msg => {
console.log('msg', msg);
});
};
const sendMessage = () => {
socket.emit('msgToServer','test message');
};
useEffect(() => {
receiveMessage()
}, []);
return (
<SafeAreaView>
<Button title="Message" onPress={sendMessage} />
</SafeAreaView>
);
}
Postman uses the ws: protocol, which is what the WsAdapter is for. By default Nest uses socket.io as the de facto WebSocket adapter which does not connect via ws diectly, but via the socket.io engine. Your react code is using socket.io so your server code should use socket.io too. This does mean that you won't be able to use postman to test your websockets though, unless you keep swapping back and forth.
Postman now has support to Socket.io servers (it's in beta):
Step by step from the provided link:
In the left-hand sidebar, click New.
Select WebSocket Request.
Within the new tab’s header, click the drop-down that says Raw, and select Socket.IO instead.
Type the address of your Socket.IO server into the URL bar.
Click Connect.
As you do not provide a specific namespace on the #WebSocketGateway() decorator and assuming that you are using the default NestJS port 3000, the step 4 address would be:
http://localhost:3000
Additionally, if you are running NestJS in WSL, you might want to see this answer
I am trying to setup websocket by feathers js + primus following from this link: https://docs.feathersjs.com/real-time/primus.html.
But I don't know how to get spark instance from primus in server side. Below is my server code:
class SocketService {
create(data, params, callback){
...
}
}
module.exports = function(){
const app = this
let ss = new SocketService()
app.use('socket-shell', ss);
}
In above code, server can get the message from client in create() method. But how can I get spark instance from primus in that method? I want to use spark.write method to send message back to the client.
Below is the server code for configuring feathers services:
app
.use(compress())
.options('*', cors())
.use(cors())
// .use(favicon(path.join(app.get('public'), 'favicon.ico')))
.use('/', serveStatic(app.get('public')))
.use(bodyParser.json())
.use(bodyParser.urlencoded({extended: true}))
.configure(hooks())
.configure(rest())
.configure(primus({
transformer: 'websockets',
timeout: false,
}, (primus) => {
app.use('socket-shell', function(socket, done){
// Exposing a request property to services and hooks
socket.request.feathers.referrer = socket.request.referrer;
done();
});
}))
.configure(services)
.configure(middleware);
Below code is used to registering a event listener on server side but it can't receive event from client:
class SocketService {
constructor(options) {
this.events = [ 'someevent','serverevent' ]
}
setup(app) {
this.app = app;
let socketService = app.service('socket-shell')
socketService.addListener('serverevent', this.serverevent)
}
serverevent(msg){
console.log('serverevent', msg)
}
In client code, I use below code to emit message to server:
var primus = new Primus('http://localhost:3030');
var app = feathers()
.configure(feathers.hooks())
.configure(feathers.primus(primus));
var messageService = app.service('/socket-shell');
messageService.emit('serverevent', {msg:'this is client'})
what wrong with above code?
Ideally you wouldn't use the socket connection directly in your service. A service shouldn't know about how it is being accessed. There are two options:
Set up and send your own events in app.configure(primus(
Have the service emit its own custom events
--
class SocketService {
constructor() {
this.events = [ 'someevent' ];
}
create(data, params, callback){
this.emit('someevent', 'data');
}
}