In the previous section we created a WebSocket application with the WS module. But it can only respond to echo:xxx messages in a simple way, and it belongs to the Hello, world-level app.
To create a real websocket application, first of all, there must be an MVC-based Web application, which is the web that we created in front of KOA2 and Nunjucks, on which the websocket is added in order to be complete.
Therefore, the goal of this section is to create an online chat room based on WebSocket.
First, we'll copy the MVC project we wrote earlier, creating a complete MVC Web application with the following structure:
ws-with-koa/
|
+-vscode/
| |
| +-Launch.json <--vscode configuration file
|
+-controllers/<--Controller
|
+-views/<--html template file
|
+-static/<--static resource file
|
+-app.js <--using KOA js
|
+-controller.js <--Scan Register Controller
|
+-static-files.js <--handling static files
|
+-Templating.js <--stencil engine Entry
|
+-Package.json <--Project Profile
|
+-node_modules/<--NPM installs all dependent packages
Then, add the dependent packages we need to the Package.json:
"Dependencies": {
"ws": "1.1.1",
"KOA": "2.0.0",
"Koa-bodyparser": "3.2.0",
"Koa-router": " 7.0.0 ",
" nunjucks ":" 2.4.2 ",
" MIME ":" 1.3.4 ",
" mz ":" 2.4.0 "
}
After installing with NPM install, we first get a standard MVC-based KOA2 application. At the heart of the app is an app variable that represents the KOA app:
Const APP = new Koa ();
TODO:app.use (...);
App.listen (3000);
Now the first question is: KOA in response to HTTP via Port 3000, we want the new websocketserver to be able to use port 3000.
The answer is yes. Although Websocketserver can use other ports, the unified port has one of the biggest benefits:
In practice, both HTTP and WebSocket use standard 80 and 443 ports, do not need to expose new ports, and do not need to modify firewall rules.
After Port 3000 is KOA occupied, websocketserver how to use the port.
In fact, the 3000 port is not monitored by KOA, but KOA is created by invoking the node standard HTTP module http.server. KOA just registers the response function with the HTTP. In the server. Similarly, Websocketserver can also register its own response function in Http.server, so that the same port, according to the protocol, can be processed by KOA and WS, respectively:
The key code to bind Websocketserver to the same port is to obtain a reference to the Http.server created by KOA, and then create Websocketserver according to Http.server:
The KOA app's Listen () method returns HTTP. Server: let
server = App.listen (+);
Create Websocketserver: let
wss = new Websocketserver ({
server:server
});
Always note that a standard HTTP request is still sent when the browser creates websocket. Either the WebSocket request or the normal HTTP request will be HTTP. Server processing. The specific processing method is implemented by the callback function injected by KOA and Websocketserver. Websocketserver will first determine if the request is a WS request, if it is, it will process the request, and if not, the request will still be processed by KOA.
Therefore, the WS request is handled directly by Websocketserver, and it does not go through Koa,koa any middleware will have no opportunity to process the request.
Now the second question comes: In KOA applications, users can be easily authenticated, for example, through sessions or cookies, but how to identify users in response to WebSocket requests.
A simple and feasible solution is to write the user's identity to the cookie, in KOA, you can use middleware to parse the cookie, bind the user to Ctx.state.user.
The WS request is also a standard HTTP request, so the server will also send the cookie so that when we use Websocketserver to process the WS request, we can identify the user based on the cookie.
First, the logic to identify the user's identity is extracted as a separate function:
function Parseuser (obj) {
if (!obj) {
return;
}
Console.log (' Try parse: ' + obj);
let S = ';
if (typeof obj = = = ' String ') {
s = obj;
} else if (obj.headers) {let
cookies = new Cookie (obj, null);
s = cookies.get (' name ');
}
if (s) {
try {let
user = Json.parse (buffer.from (S, ' base64 '). toString ());
Console.log (' User: ${user.name}, ID: ${user.id} ');
return user;
} catch (E) {
//Ignore
}
}
}
Note: For demonstration purposes, the cookie is not hashed, in fact it is a JSON string.
In KOA's middleware, we can easily identify the user:
App.use (CTX, next) = {
Ctx.state.user = Parseuser (ctx.cookies.get (' name ') | | '');
await next ();
});
In Websocketserver, you need to respond to the connection event and then identify the user:
Wss.on (' Connection ', function (WS) {
///Ws.upgradereq is a Request object: let
user = Parseuser (ws.upgradereq);
if (!user) {
//cookie does not exist or is invalid, close websocket directly:
ws.close (4001, ' Invalid user ');
}
Identify success, bind user to the WebSocket object:
ws.user = user;
Bind Websocketserver object:
ws.wss = WSS;
});
Next, we're going to bind the message, close, error, and other event handlers to each websocket that is successfully created. For a chat app, each time a message is received, the message needs to be broadcast to all websocket connections.
Add a Broadcase () method to the WSS object first:
Wss.broadcast = function (data) {
Wss.clients.forEach (function (client) {
client.send (data);
});
};
After a websocket receives a message, it can call Wss.broadcast () to broadcast it:
Ws.on (' message ', function (message) {
console.log (message);
if (Message && Message.trim ()) {let
msg = createmessage (' chat ', This.user, Message.trim ());
This.wss.broadcast (msg);
}
});
There are many types of messages, not necessarily chat messages, but also a list of users, user joins, user exits, and many other messages. So we use CreateMessage () to create a JSON-formatted string that is sent to the browser, and the browser-side JavaScript can be used directly:
Message ID:
var messageindex = 0;
function createmessage (type, user, data) {
Messageindex + +;
Return json.stringify ({
id:messageindex,
type:type,
user:user,
data:data
});
}
writing a page
The JavaScript code for the page is more complex than the server-side code.
The chat room page can be divided into two sections: the left-hand session list and the right-hand user list.
The DOM here needs to be updated dynamically, so state management is at the heart of the page logic.
To simplify state management, we use Vue to control the left and right two lists:
var vmmessagelist = new Vue ({
el: ' #message-list ',
data: {
messages: []
}
});
var vmuserlist = new Vue ({
el: ' #user-list ',
data: {
users: []
}
});
The session list and user list are initialized to an empty array.
Immediately thereafter, create a websocket connection, respond to the server message, and update the session list and user list:
var ws = new WebSocket (' Ws://localhost:3000/ws/chat ');
Ws.onmessage = function (event) {
var data = Event.data;
Console.log (data);
var msg = json.parse (data);
if (Msg.type = = = ' list ') {
vmuserlist.users = msg.data;
} else if (msg.type = = = ' Join ') {
addtouserlist (vmuser List.users, msg.user);
AddMessage (Vmmessagelist.messages, msg);
} else if (msg.type = = = ' Left ') {
removefromuserlist (vmuserlist.users, msg.user);
AddMessage (Vmmessagelist.messages, msg);
} else if (msg.type = = = ' Chat ') {
addmessage (vmmessagelist.messages, msg);}
};
In this way, JavaScript is responsible for updating the State and Vue is responsible for refreshing the DOM based on state. Take the user list for example, the HTML code is as follows:
<div id= "User-list" >
<div class= "Media" v-for= "user in Users" >
<div class= "Media-left" >
</div>
<div class= "Media-body" >
When testing, if you are testing natively, you need to use several different browsers at the same time, so that cookies do not interfere with each other.
The final chat room effect is as follows:
Configuring the Reverse proxy
If the Web site is configured with a reverse proxy, such as Nginx, both HTTP and WebSocket must connect to the node server through a reverse proxy. The reverse proxy for HTTP is very simple, but to connect websocket properly, the proxy server must support the WebSocket protocol.
In the case of Nginx, we write a simple reverse proxy configuration file.
Detailed configuration can refer to Nginx's official blog: Using Nginx as a WebSocket Proxy
First of all to ensure that the Nginx version >=1.3, and then, through the Proxy_set_header command, set:
Proxy_set_header Upgrade $http _upgrade;
Proxy_set_header Connection "Upgrade";
Nginx can understand that the connection will use the WebSocket protocol.
A sample configuration file reads as follows:
server {
listen ;
server_name localhost;
# processing static resource files: Location
^~/static/{
root/path/to/ws-with-koa;
}
# processing WebSocket Connection: location
^~/ws/{
proxy_pass http://127.0.0.1:3000;
Proxy_http_version 1.1;
Proxy_set_header Upgrade $http _upgrade;
Proxy_set_header Connection "Upgrade"