EcmaScript WebSocket and HTTP Server
This is a low level WebSocket and HTTP server side library built using only EcmaScript 2015 features. While there are more fully featured WebSocket servers built specifically for their environments, this library aims to make a WebSocket server that can be implemented anywhere there is javascript with access to a TCP server. This makes it a lot easier to port from one environment to another or to use with a javascript runtime in your own application (only needing to add TCP bindings).
Help
This library needs a lot of tests written. If you want to help testing the code by writing tests, listing tests that need to be written or any other kind of help. Please head over to the pinned issue on Github.
Usage
To use this library, there needs to be some kind of socket server available for javascript. It can work with TCP sockets or HTTP sockets that have access to the underlying TCP socket.
Install
This library does not rely on any package manager. To install it, clone the repo into some directory for your external libraries.
git clone insert-clone-path-here
To access the library, just import the file you are interested in by using its path.
import { isWebSocket } from "./lib/ecmascript-websocketserver.js"
Documentation
Documentation for all the functions can be found at https://fanatiqs.github.io/EcmaScript-WebSocketServer/
Initialising a WebSocket connection
Since a WebSocket connection starts of as an HTTP upgrade, you first need to handle an HTTP request. If you are using TCP data, you first needs to parsed the HTTP request with either the function parseHttp
from this librar or some other HTTP parser. If you are getting data from another HTTP parser, make sure the request object is structured the same way as the HttpRequest
Type in this library. The function and type definition is located in the http.js
file and more details can be found in the documentation.
If you want to limit connection to same origin only, use the isSameOrigin
function to help with that. More info in documentation.
Before upgrading an HTTP socket to WebSocket, you need to know if the request is a WebSocket upgrade or not. This is done with the function isWebSocketUpgrade
, it checks if the request is an upgrade or not. If the request is not a WebSocket upgrade. You can do whatever you want with it. For TCP sockets, there are functions for creating your own HTTP responses to send back, more info in the next chapter.
If isWebSocketUpgrade
returned true, you need to send back a response that can be generated with makeWebSocketUpgradeResponse
. This response should be sent to the client and if the TCP socket only takes array buffers, there is a helper function stringToBuffer
to convert the upgrade response from a string to a buffer. After this response is sent, the client and server must now use the WebSocket protocol instead of HTTP for all further communication.
Make non-WebSocket HTTP response
For TCP sockets, there are functions for making HTTP responses. More details about these functions can be found in the documentation.
- If you want to use an http status code that does not exist in
httpStatusCodes
, you should add it yourself so the response gets a reason text. - If your TCP socket can only write array buffers back,
stringToBuffer
can convert the HTTP response string to an array buffer. makeHttpHtmlResponse
takes an HTML string and puts it in an HTTP response. By default, it uses status code 200 OK but it can be customised with the second argument.makeHttpHeaderResponse
takes an HTTP status code and an object of headers.
Parsing incoming WebSocket data
To parse the WebSocket data, we start by getting the opCode with the function getWebSocketOpCode
from the TCP buffer. More info about the function and how to handle its return values can be found in the documentation.
Close
For close frames, there is one things the server MUST do. If the close was initiated by the client (the server did not send a close frame), the server MUST send a close frame back to the client. If the close frame is a response to a server initiated close, the server should close the TCP socket.
Ping
If a server gets a ping frame, it MUST send back a pong frame. This can easily be created with the makeWebSocketPingResponse
function. Ping frames can also contain a payload. Not exactly sure what for.
Pong
This is just a response for a ping request sent to the client.
Text
This is a text frame and the content can be extracted using the getWebSocketTextPayload
function.
Binary
This is a binary frame and the content can be extracted using the getWebSocketBinaryPayload
function.
Fragmented frames
Fragmented frames have 4 opCodes: fragmentedText
, fragmentedBinary
, fragmentedContinue
and fragmentedEnd
. Buffering is not handled by the library and needs to be implemented separately. More info in documentation.
Sending WebSocket data
Close
Close frames can be created using makeWebSocketCloseFrame
and can take 2 arguments. The first one is a close code that follows the WebSocket spec for how they are used. The second arguments is a close reason and requires the close code to be set.
Throws error if reason is longer than 125 characters or if close code is outside valid range.
If a close frame is sent, the TCP socket should not be closed until the server receives a close frame back from the server.
Ping
Ping frames can be created using makeWebSocketPingFrame
and has no arguments.
Text
Text frames can be created using makeWebSocketTextFrame
and it takes a string as its argument.
Throws error if contents length is 32 bit or longer
Binary
Binary frames can be created using makeWebSocketBinaryFrame
and it takes an array buffer as its argument.
Throws error if contents length is 32 bit or longer
Not supported WebSocket features
HTTP stream
Currently, the HTTP parser only works with the entire HTTP request in the buffer in one shot. There is no support for getting the HTTP request in multiple TCP packets. This is a feature I would like to get working
WebSocket extensions
The WebSocket protocol is built to be extended with extra functions. This is not supported in this library and is currently not under consideration. If it is a feature you want, open an issue on Github.
WebSocket subprotocols
During the WebSocket handshake, the client and the server can agree on a specific subprotocol to use. This is not supported in this library and is currently not under consideration. If it is a feature you want, open an issue on Github.
Payload length of 4 294 967 296 or longer
The WebSocket protocol supports payload lengths up to 64 bit values. Unfortunately, javascript clamps its values to 32 bit when doing bit shifting, so the maximum length for payload in this library is 32 bit (4 294 967 296). This feature is probably not going to be implemented, mainly because NodeJS crashed when trying to create a string of current max length and if successful, the memory for the string alone would be around 8GB since javascript stores its strings as UTF16.
Ping frame payload
Ping frames can currently not be created with a payload. This is mainly because I don't know how it would be used and for what. If it is a feature you want, open an issue on Github.
The Sha1 and Base64
This library uses modified versions of other libraries for Sha1 and Base64. The functions are modified to only support what is needed for the length and content it is going to be used for. When "warmed up", it is actually faster than the native built-in crypto functions in NodeJS.
TODO
- Add streaming support for HTTP (multipacket)
- Test error catching in examples
- Make sure http proxies work
- Make sure http filtering works (whatever that is, but it was mentioned in the spec)
- A good way for implementors to be forced to handle control frame correctly and to not send data after socket is closed. This might require a constructor. A constructor approach would be another way of doing it and only using the function should still be valid.
- Should the server close socket on close frame from client? In spec, it said that the initiating peer closes the socket
- Compare length of WebSocket payload and its offset with the actual length of the buffered chunk. They should be the same but might not be. How should errors like this be handled? Let implementor buffer the current chunk until the next?
- Find a way to get rid of package.json, keep .js file extension and still have nodejs examples work with src files.
- Add examples to jsDoc
- Change jsdoc template to a nicer one
- Add git submodule instructions