Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added spec defined websocket close enum #1990

Merged
merged 8 commits into from
Dec 11, 2017
145 changes: 141 additions & 4 deletions http/vibe/http/websockets.d
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,137 @@ HTTPServerRequestDelegateS handleWebSockets(void function(scope WebSocket) @syst
});
}

/**
* Provides the reason that a websocket connection has closed.
*
* Further documentation for the WebSocket and it's codes can be found from:
* https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
*
* ---
*
* void echoSocket(scope WebSocket sock)
* {
* import std.datetime : seconds;
*
* while(sock.waitForData(3.seconds))
* {
* string msg = sock.receiveText;
* logInfo("Got a message: %s", msg);
* sock.send(msg);
* }
*
* if(sock.connected)
* sock.close(WebSocketCloseReason.policyViolation, "timeout");
* }
*/
enum WebSocketCloseReason : short
{
none = 0,
normalClosure = 1000,
goingAway = 1001,
protocolError = 1002,
unsupportedData = 1003,
noStatusReceived = 1005,
abnormalClosure = 1006,
invalidFramePayloadData = 1007,
policyViolation = 1008,
messageTooBig = 1009,
internalError = 1011,
serviceRestart = 1012,
tryAgainLater = 1013,
badGateway = 1014,
tlsHandshake = 1015
}

string closeReasonString(WebSocketCloseReason reason) @nogc @safe
{
import std.math : floor;

//round down to the nearest thousand to get category
switch(cast(short)(cast(float)reason / 1000f).floor)
{
case 0:
return "Reserved and Unused";
case 1:
switch(reason)
{
case 1000:
return "Normal Closure";
case 1001:
return "Going Away";
case 1002:
return "Protocol Error";
case 1003:
return "Unsupported Data";
case 1004:
return "RESERVED";
case 1005:
return "No Status Recvd";
case 1006:
return "Abnormal Closure";
case 1007:
return "Invalid Frame Payload Data";
case 1008:
return "Policy Violation";
case 1009:
return "Message Too Big";
case 1010:
return "Missing Extension";
case 1011:
return "Internal Error";
case 1012:
return "Service Restart";
case 1013:
return "Try Again Later";
case 1014:
return "Bad Gateway";
case 1015:
return "TLS Handshake";
default:
return "RESERVED";
}
case 2:
return "Reserved for extensions";
case 3:
return "Available for frameworks and libraries";
case 4:
return "Available for applications";
default:
return "UNDEFINED - Nasal Demons";
}
}

unittest
{
assert((cast(WebSocketCloseReason) 0).closeReasonString == "Reserved and Unused");
assert((cast(WebSocketCloseReason) 1).closeReasonString == "Reserved and Unused");
assert(WebSocketCloseReason.normalClosure.closeReasonString == "Normal Closure");
assert(WebSocketCloseReason.abnormalClosure.closeReasonString == "Abnormal Closure");
assert((cast(WebSocketCloseReason)1020).closeReasonString == "RESERVED");
assert((cast(WebSocketCloseReason)2000).closeReasonString == "Reserved for extensions");
assert((cast(WebSocketCloseReason)3000).closeReasonString == "Available for frameworks and libraries");
assert((cast(WebSocketCloseReason)4000).closeReasonString == "Available for applications");
assert((cast(WebSocketCloseReason)5000).closeReasonString == "UNDEFINED - Nasal Demons");
assert((cast(WebSocketCloseReason) -1).closeReasonString == "UNDEFINED - Nasal Demons");

//check the other spec cases
for(short i = 1000; i < 1017; i++)
{
if(i == 1004 || i > 1015)
{
assert(
(cast(WebSocketCloseReason)i).closeReasonString == "RESERVED",
"(incorrect) code %d = %s".format(i, closeReasonString(cast(WebSocketCloseReason)i))
);
}
else
assert(
(cast(WebSocketCloseReason)i).closeReasonString != "RESERVED",
"(incorrect) code %d = %s".format(i, closeReasonString(cast(WebSocketCloseReason)i))
);
}
}


/**
* Represents a single _WebSocket connection.
Expand Down Expand Up @@ -565,10 +696,16 @@ final class WebSocket {
code = Numeric code indicating a termination reason.
reason = Message describing why the connection was terminated.
*/
void close(short code = 0, scope const(char)[] reason = "")
void close(short code = WebSocketCloseReason.normalClosure, scope const(char)[] reason = "")
{
if(reason !is null && reason.length == 0)
reason = (cast(WebSocketCloseReason)code).closeReasonString;

//control frame payloads are limited to 125 bytes
assert(reason.length <= 123);
version(assert)
assert(reason.length <= 123);
else
reason = reason[0 .. min($, 123)];

if (connected) {
send((scope msg) {
Expand Down Expand Up @@ -656,7 +793,7 @@ final class WebSocket {
logDebug("Got closing frame (%s)", m_sentCloseFrame);

// If no close code was passed, we default to 1005
this.m_closeCode = 1005;
this.m_closeCode = WebSocketCloseReason.noStatusReceived;

// If provided in the frame, attempt to parse the close code/reason
if (msg.peek().length >= short.sizeof) {
Expand Down Expand Up @@ -689,7 +826,7 @@ final class WebSocket {

// If no close code was passed, e.g. this was an unclean termination
// of our websocket connection, set the close code to 1006.
if (this.m_closeCode == 0) this.m_closeCode = 1006;
if (this.m_closeCode == 0) this.m_closeCode = WebSocketCloseReason.abnormalClosure;
m_writeMutex.performLocked!({ m_conn.close(); });
}

Expand Down