Skip to content

Commit 72c9f34

Browse files
committed
WebSocket server creates new connection from existing socket #755
1 parent 90ee606 commit 72c9f34

File tree

4 files changed

+153
-10
lines changed

4 files changed

+153
-10
lines changed

Diff for: documentation/network/network.md

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Networking
22

33
Copyright 2017-2022 Moddable Tech, Inc.<BR>
4-
Revised: February 14, 2022
4+
Revised: March 8, 2022
55

66
**Warning**: These notes are preliminary. Omissions and errors are likely. If you encounter problems, please ask for assistance.
77

@@ -675,7 +675,7 @@ The WebSocket server implementation is designed for sending and receiving small
675675

676676
### `constructor(dictionary)`
677677

678-
A new WebSocket `Server` is configured using a dictionary of properties. The dictionary is a super-set of the `Listener` dictionary. The server is a Socket Listener. If no port is provided in the dictionary, port 80 is used.
678+
A new WebSocket `Server` is configured using a dictionary of properties. The dictionary is a super-set of the `Listener` dictionary. The server is a Socket Listener. If no port is provided in the dictionary, port 80 is used. If port is set to `null`, no listener is created which is useful when sharing a listener with an http server (see `attach` below).
679679

680680
At this time, the WebSocket `Server` does not define any additional properties for the dictionary.
681681

@@ -697,6 +697,14 @@ ws.close();
697697

698698
***
699699

700+
### `attach(socket)`
701+
702+
The `attach` function creates a new incoming WebSockets connection from the provided socket. The server issues the `Server.connect` callback and then performs the WebSockets handshake. The status line has been read from the socket, but none of the HTTP headers have been read as these are required to complete the handshake.
703+
704+
See the [httpserverwithwebsockets](../../examples/network/http/httpserverwithwebsockets/main.js) for an example of sharing a single listener socket between the HTTP and WebSockets servers.
705+
706+
***
707+
700708
### `callback(message, value)`
701709

702710
The WebSocket server callback is the same as the WebSocket client callback with the addition of the "Socket connected" (`1` or `Server.connect`) message. The socket connected message for the server is invoked when the server accepts a new incoming connection.
+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* Copyright (c) 2016-2022 Moddable Tech, Inc.
3+
*
4+
* This file is part of the Moddable SDK.
5+
*
6+
* This work is licensed under the
7+
* Creative Commons Attribution 4.0 International License.
8+
* To view a copy of this license, visit
9+
* <http://creativecommons.org/licenses/by/4.0>.
10+
* or send a letter to Creative Commons, PO Box 1866,
11+
* Mountain View, CA 94042, USA.
12+
*
13+
*/
14+
15+
import {Server as HTTPServer} from "http"
16+
import {Server as WebsocketsServer} from "websocket"
17+
18+
const indexHTML = `
19+
<html>
20+
<head>
21+
<meta charset="utf-8">
22+
<title>test</title>
23+
</head>
24+
<body>
25+
26+
<script type="module" src="./ws.js"></script>
27+
</body>
28+
</html>
29+
`;
30+
31+
const wsJS = `
32+
const url = "ws://" + window.location.hostname + "/ws";
33+
const ws = new WebSocket(url);
34+
35+
ws.onopen = function() {
36+
console.log("ws open");
37+
ws.send(JSON.stringify({"hello": "world"}));
38+
}
39+
40+
ws.onclose = function() {
41+
console.log("ws close");
42+
}
43+
44+
ws.onerror = function() {
45+
console.log("ws error");
46+
}
47+
48+
ws.onmessage = function(event) {
49+
const data = event.data;
50+
console.log("ws message: ", data);
51+
}
52+
`;
53+
54+
// WebSockets server without a listener (signaled with port set to null)
55+
// the http server hands-off incoming connections to this server
56+
const websockets = new WebsocketsServer({port: null});
57+
websockets.callback = function (message, value) {
58+
switch (message) {
59+
case WebsocketsServer.connect:
60+
trace("ws connect\n");
61+
break;
62+
63+
case WebsocketsServer.handshake:
64+
trace("ws handshake\n");
65+
break;
66+
67+
case WebsocketsServer.receive:
68+
trace(`ws message received: ${value}\n`);
69+
break;
70+
71+
case WebsocketsServer.disconnect:
72+
trace("ws close\n");
73+
break;
74+
}
75+
};
76+
77+
// HTTP server on port 80
78+
const http = new HTTPServer;
79+
http.callback = function(message, value, etc) {
80+
if (HTTPServer.status === message) {
81+
this.path = value;
82+
83+
// request for "/ws" is handed off to the WebSockets server
84+
if ("/ws" === value) {
85+
const socket = http.detach(this);
86+
websockets.attach(socket);
87+
}
88+
89+
return;
90+
}
91+
92+
// return "/", "/index.html" and "/ws.js". all other paths are 404
93+
if (HTTPServer.prepareResponse === message) {
94+
if (("/" === this.path) || ("/index.html" === this.path))
95+
return {headers: ["Content-type", "text/html"], body: indexHTML};
96+
if ("/ws.js" === this.path)
97+
return {headers: ["Content-type", "text/javascript"], body: wsJS};
98+
99+
return {status: 404};
100+
}
101+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"include": [
3+
"$(MODDABLE)/examples/manifest_base.json",
4+
"$(MODDABLE)/examples/manifest_net.json",
5+
"$(MODULES)/network/http/manifest.json",
6+
"$(MODULES)/network/websocket/manifest.json"
7+
],
8+
"modules": {
9+
"*": [
10+
"./main"
11+
]
12+
}
13+
}

Diff for: modules/network/websocket/websocket.js

+29-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2016-2021 Moddable Tech, Inc.
2+
* Copyright (c) 2016-2022 Moddable Tech, Inc.
33
*
44
* This file is part of the Moddable SDK Runtime.
55
*
@@ -29,6 +29,7 @@ import {Socket, Listener} from "socket";
2929
import Base64 from "base64";
3030
import Logical from "logical";
3131
import {Digest} from "crypt";
32+
import Timer from "timer";
3233

3334
/*
3435
state:
@@ -56,6 +57,7 @@ export class Client {
5657
this.headers = dictionary.headers ?? [];
5758
this.protocol = dictionary.protocol;
5859
this.state = 0;
60+
this.flags = 0;
5961

6062
if (dictionary.socket)
6163
this.socket = dictionary.socket;
@@ -104,6 +106,10 @@ export class Client {
104106
close() {
105107
this.socket?.close();
106108
delete this.socket;
109+
110+
if (this.timer)
111+
Timer.clear(this.timer);
112+
delete this.timer;
107113
}
108114
};
109115

@@ -287,23 +293,38 @@ trace("partial header!!\n"); //@@ untested
287293
export class Server {
288294
#listener;
289295
constructor(dictionary = {}) {
296+
if (null === dictionary.port)
297+
return;
298+
290299
this.#listener = new Listener({port: dictionary.port ?? 80});
291300
this.#listener.callback = () => {
292-
const socket = new Socket({listener: this.#listener});
293-
const request = new Client({socket});
294-
request.doMask = false;
295-
socket.callback = server.bind(request);
296-
request.state = 1; // already connected socket
297-
request.callback = this.callback; // transfer server.callback to request.callback
301+
const request = addClient(new Socket({listener: this.#listener}), 1, this.callback);
298302
request.callback(Server.connect, this); // tell app we have a new connection
299303
};
300304
}
301305
close() {
302-
this.#listener.close();
306+
this.#listener?.close();
303307
this.#listener = undefined;
304308
}
309+
attach(socket) {
310+
const request = addClient(socket, 2, this.callback);
311+
request.timer = Timer.set(() => {
312+
delete request.timer;
313+
request.callback(Server.connect, this); // tell app we have a new connection
314+
socket.callback(2, socket.read());
315+
});
316+
}
305317
};
306318

319+
function addClient(socket, state, callback) {
320+
const request = new Client({socket});
321+
delete request.doMask;
322+
socket.callback = server.bind(request);
323+
request.state = state;
324+
request.callback = callback; // transfer server.callback to request.callback
325+
return request;
326+
}
327+
307328
/*
308329
callback for server handshake. after that, switches to client callback
309330
*/

0 commit comments

Comments
 (0)