Chat service
Client 2
Server
Client 3
Client 1
Client 4
WS
HTTP
DNS
FTP
TCP
UDP
IP
ICMP
ARP
DHCP
Ethernet
Wi-Fi
DSL
Application
Transport
Network
Data Link and Physical
Collect the data from the application software and format it for further processing
Packetize the data. Add sequecing and error correction info to each packet
Add source and destination IP address to each packet
Add source and destination MAC address to each packet and pass to NIC drivers
Client
Server
GET / poll
GET / poll
XHR polling
Client
Server
Connection
Events
SSE
EventSource protocol
The WebSocket protocol, described in the specification RFC 6455 provides a way to exchange data between browser and server via a persistent connection. The data can be passed in both directions as “packets”, without breaking the connection and additional HTTP-requests.
WebSocket is especially great for services that require continuous data exchange, e.g. online games, real-time trading systems and so on.
// To open a websocket connection, we need to create new
// WebSocket using the special protocol ws in the url:
let socket = new WebSocket("ws://chat.com");
// There’s also encrypted wss:// protocol.
// It’s like HTTPS for websockets.
When new WebSocket(url) is created, it starts connecting immediately.
During the connection, the browser (using headers) asks the server: “Do you support Websocket?” And if the server replies “yes”, then the talk continues in WebSocket protocol, which is not HTTP at all.
Client
Server
"Hey, server, let's talk WebSocket?"
HTTP-request
HTTP-response
"Okay!"
WebSocket protocol
If the server agrees to switch to WebSocket, it should send code 101 response
Handshake (HTTP upgrade)
Bi-directional messages
GET /chat
Host: ourchat.net
Origin: https://javascript.info
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Key: Iv8io/9s+lYFgZWcXczP8Q==
Sec-WebSocket-Version: 13
new WebSocket("wss://ourchat.net/chat");
If the server agrees to switch to WebSocket, it should send code 101 response:
101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: hsBlbuDTkk24srzEOTBUlZAlC2g=
WebSocket communication consists of “frames” – data fragments, that can be sent from either side and can be of several kinds:
Once the socket is created, we should listen to events on it. There are totally 4 events:
…And if we’d like to send something, then socket.send(data) will do that.
RFC 6455 "The WebSocket Protocol", 2011
Support in all browsers
Working phases:
opening handshake
data transferring (bi-directional messages)
The 2-ways permanent connection between client and server
permanent TCP connection
Ports 80(ws)/443(wss)
Prefix URI ws/wss (ws://ourchat.net/chat)
...there are plenty of them
Socket.IO is the most feature-rich! But you may not need it on the simple project.
Socket.IO is a JavaScript library for realtime web applications. It enables realtime, bi-directional communication between web clients and servers.
WebSocket should reconnect automatically in case of disconnection due to server failure or some other reason.
WebSocket doesn't work ideally in presence of Firewall, Proxy and Load Balancer either and Antivirus. (the only HTTP may be allowed)
Old browsers
Disconnection detection
Multiplexing support
Room support
These problems solved by the Socket.IO
few words about NestJS gateways
@UsePipes(new ValidationPipe())
@UseInterceptors(new ClearCacheInterceptor())
@SubscribeMessage('events')
handleEvent(client: Socket, data: string) {
const event = 'events';
return { event, data };
}
You can change the protocol with one annotation
Most of the concepts such as dependency injection, decorators, exception filters, pipes, guards and interceptors, apply equally to gateways.
step by step!
Chat
config
Chat
overview
protocol
#create new project
nest new chat-service
#navigate to new folder
cd chat-service
#install deps
yarn add @nestjs/platform-socket.io @nestjs/websockets
yarn add -D @types/socket.io
#generate new module
nest g module chat
#take a look what we have
cd src/chat/
ll
#generate new gateway
nest g ga chat
const capitalize = require('lodash/capitalize');
/**
* this file is just for fun, to generate random nckname while we don't have Auth system
*/
const intesifiers = [
'absolutely',
'quite',
'totally',
'utterly',
'particilarly',
'complettely',
'totally',
'really',
];
const adjectives = [
'great',
'cool',
'ambitious',
'generous',
'cute',
'dear',
'nice',
'reliable',
'solid',
'trusty',
'simple',
'pure',
'brave',
'manly',
'fearless',
'artful',
'vivid',
'utopic',
'lucid',
'radiant',
'stinky',
'supreme',
'successful',
'holly',
'happy',
'giant',
'lucky',
'weird',
'extreme',
];
const names = [
'phoenix',
'centaur',
'mermaid',
'leviathan',
'dragon',
'pegasus',
'siren',
'hydra',
'sphinx',
'unicorn',
'wyvern',
'behemoth',
'griffon',
'dodo',
'mammoth',
'pirate',
'eminem',
'hacker',
'parrot',
'derp',
];
function random(limit) {
return Math.floor(Math.random() * limit);
}
function getRandomElement(arr) {
return capitalize(arr[random(arr.length)]);
}
function randomLogin() {
return []
.concat(Math.random() > 0.5 ? getRandomElement(intesifiers) : '')
.concat(getRandomElement(adjectives))
.concat(getRandomElement(names))
.join('');
}
export default randomLogin;
import {
ConnectedSocket,
MessageBody,
OnGatewayConnection,
OnGatewayDisconnect,
SubscribeMessage,
WebSocketGateway,
WebSocketServer,
} from '@nestjs/websockets';
import randomLogin from '../utils/genNickname';
import { Logger } from '@nestjs/common';
import { Socket } from 'socket.io';
@WebSocketGateway({ namespace: 'chat' })
export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {
private readonly logger = new Logger(ChatGateway.name);
private connected: Map<string, string> = new Map();
@WebSocketServer() server;
handleDisconnect(client: Socket) {
const nick = this.connected.get(client.id);
this.logger.log(nick + ' Client disconnected ' + client.id);
this.connected.delete(client.client.id);
this.server.emit('userDisconnected', nick);
this.server.emit('message', { msg: `~ ${nick} disconnected` });
}
handleConnection(client: Socket) {
const nick = randomLogin();
this.logger.log(client.id + ' Client connected ' + nick);
this.connected.set(client.id, nick);
client.emit('message', { msg: `Welcome #${nick}`, currUserId: nick });
client.emit('activeUsers', Array.from(this.connected.values()));
client.broadcast.emit('message', { msg: `${nick} connected to the chat` });
this.server.emit('userConnected', nick);
}
@SubscribeMessage('message')
async handleMessage(
@MessageBody() msg: string,
@ConnectedSocket() client: Socket,
) {
const time = new Date();
const nick = this.connected.get(client.id);
this.server.emit('message', {
msg,
user: nick,
time: `${time.getHours()}:${time.getMinutes()}`,
});
}
}
WS vs SSE
Server Sent Events
Is this a WebSocket ?
SSE is unidirectional. When you open a SSE connection, only the server can send data to the client (browser, etc.). The client cannot send any data.
@Sse('sse')
sse(): Observable<MessageEvent> {
return interval(1000).pipe(map((_) => ({ data: { hello: 'world' } })));
}
// client:
const eventSource = new EventSource('/sse');
eventSource.onmessage = ({ data }) => {
console.log('New message', JSON.parse(data));
};