OWIN WebSocket Extension v0.3.0

 

Authors: The OWIN working group

Last Modified: 7 September 2012

Contents

1.       Introduction

2.       Definitions

3.       Detection

3.1.    Startup

3.2.    Per Request

4.       Upgrade

5.       Consumption

6.       Delegate Signature

7.       Usage

1.    Introduction

This document outlines the recommended patterns for using WebSockets through the OWIN interface. It depends on OWIN v0.15 (but may work with other versions), the OWIN opaque streams extension v0.2.0, and uses the extension key mechanics as outlined in CommonKeys.html.

2.    Definitions

WebSocket - Defined in RFC 6455, a web socket is a bidirectional, framed communication channel that uses HTTP for its initial handshake.

Opaque Stream - Some servers allow a request to take direct ownership of the underlying connection after the initial HTTP handshake. This gives the application access to a bidirectional opaque stream and the application is then responsible for employing its own protocol (e.g.  WebSockets) to communicate with the client.

3.    Detection

In order for an application to use WebSockets it MUST first detect support for the extension. 

3.1 Startup

At startup the server SHOULD specify in the Properties dictionary if WebSockets are supported (see the table below).  If the application does not see support for WebSockets but does see support for Opaque Streams, it MAY insert a middleware to add WebSocket support.

3.2 Per Request

The WebSocket capable server or middleware inspects each request as it arrives to determine if it is WebSocket compatible (e.g. verb, headers, etc.).  If so, it marks the request with a specific environment key (see the table below).

Key Name

Description

“websocket.Version”

The string value “1.0” indicating version 1.0 of the OWIN WebSockets extension.

“websocket.Support”

The string value “WebSocketFunc” used in the startup Properties dictionary to indicate general WebSocket support.  Used in the request Environment dictionary to indicate a WebSocket compatible request.

 

4. Upgrade

When an application sees a request that is marked as WebSocket capable and decides to upgrade to a WebSocket, it indicates this to the server or middleware by specifying a 101 response status code and provides a WebSocketFunc delegate in the environment dictionary (see the table below).  The application MAY set the Sec-WebSocket-Protocol response header, according to RFC 6455 Section 4.2.2 Step 5.5.  The application then completes the AppFunc Task and allows the pipeline to unwind.  Middleware that wish to intercept the applications WebSocketFunc SHOULD insert themselves at this point.

When the WebSocket server or middleware sees the 101 and WebSocketFunc it SHOULD perform the requested upgrade.  The WebSocket server or middleware MUST provide the necessary WebSocket response headers when performing the upgrade.  Once the upgrade is complete the request has left HTTP processing and transitioned to WebSocket processing.  The original Environment dictionary and its content are presumed invalid and a new one MUST be provided with the keys listed as required in the table below (see Consumption).

If the upgrade fails then the server or middleware SHOULD terminate the request.  There is no guarantee that the WebSocketFunc provided by the application can or will be invoked in these scenarios.

Key Name

Description

“websocket.Func”

Used in the Environment dictionary to request that the server or middleware upgrade to a WebSocket and invoke this delegate.  See Delegate Signature.

 

5. Consumption

When the server or middleware invoke the WebSocketFunc it provides a new WebSocket Environment dictionary (see Delegate Signature).  The WebRequest Environment dictionary has the same requirements as the OWIN request Envronment dictionary, namely that it MUST be non-null, mutable, use ordinal key comparison, and MUST contain request information and delegates for Send, Receive, and Close where listed as required in the table below. 

Required?

Key Name

Description

Yes

“websocket.SendAsyncFunc”

A Func used to send data. See Delegate Signature.

Yes

“websocket.ReceiveAsyncFunc”

A Func used to receive data. See Delegate Signature.

Yes

“websocket.CloseAsyncFunc”

A Func used to close the output channel. See Delegate Signature.

Yes

“websocket.Version”

The string value “1.0” indicating version 1.0 of this OWIN WebSockets extension.

Yes

“websocket.CallCancelled”

A CancellationToken provided by the server to signal that the WebSocket has been canceled/aborted.

 

In addition to these keys the host, server, middleware, application, etc. may add arbitrary data associated with the WebSocket context to the environment dictionary.  Guidelines for additional keys and a list of commonly defined keys can be found in CommonKeys.html.

 6. Delegate Signature

The WebSocket Func signatures are follows:

using System;

using System.Threading;

using System.Threading.Tasks;

using System.Collections.Generic;

 

namespace Owin.WebSockets

{

    using WebSocketFunc =

        Func

        <

            IDictionary<string, object>, // WebSocket Environment

            Task // Complete

        >;

 

    using WebSocketSendAsync =

        Func

        <

            ArraySegment<byte> /* data */,

            int /* messageType */,

            bool /* endOfMessage */,

            CancellationToken /* cancel */,

            Task

        >;

 

    using WebSocketReceiveAsync =

        Func

        <

            ArraySegment<byte> /* data */,

            CancellationToken /* cancel */,

            Task

            <

                Tuple

                <

                    int /* messageType */,

                    bool /* endOfMessage */,

                    int? /* count */,

                    int? /* closeStatus */,

                    string /* closeStatusDescription */

                >

            >

        >;

 

    using WebSocketReceiveTuple =

        Tuple

        <

            int /* messageType */,

            bool /* endOfMessage */,

            int? /* count */,

            int? /* closeStatus */,

            string /* closeStatusDescription */

        >;

 

    using WebSocketCloseAsync =

        Func

        <

            int /* closeStatus */,

            string /* closeDescription */,

            CancellationToken /* cancel */,

            Task

        >;

}

 

7.    Usage

7.1 WebSocketFunc

This is the WebSocket application entry point.  The server provides an IDictionary containing request information and the Send, Receive, and Close Funcs.  On completion (success or fail), the application MUST complete or fail the returned Task or throw a synchronous exception.

7.2 WebSocketSendAsync

The application uses the WebSocketSendAsync Func to send data to the client.  The application does not have direct control over frame boundaries, but it does control message boundaries.  The values of messageType MUST match the opcodes defined in the RFC; E.g. 0x1 = Text, 0x2 = Binary, and 0x8 = Close.  If SendAsync is used to send a close frame, the ArraySegment must either refer to a null or empty segment, or must contain the close status code and message as defined in RFC 6455 section 5.5.1. Ping and Pong control frames (0x9, 0xA) MAY be sent, but the underlying server or middleware MUST silently discard them if it does not permit them.

7.3 WebSocketReceiveAsync

                ReceiveAsync will return the current message type, if the full message was consumed, and the count of data copied to the given buffer.  The values of messageType MUST match the opcodes defined in the RFC; 0x1 = Text, 0x2 = Binary, and 0x8 = Close.  The server or middleware MUST perform any necessary un-masking of the data before returning it. If a closed frame is received then count will be null and closeStatus and closeStatusDescription MAY be populated if such values were sent by the client.  Close data MUST NOT be copied to the user’s buffer.  The server or middleware MUST NOT deliver Ping and Pong control frames (0x9, 0xA) to the application, they MUST be handled internally.

7.4 WebSocketCloseAsync

                Applications SHOULD follow the RFC guidelines regarding CloseAsync (section 5.5.1).  The application SHOULD invoke CloseAsync when it is done sending outgoing data.  The application MUST NOT attempt to send additional data after sending a close frame.  The application MUST NOT attempt any further receives after a close frame is received.  The application MAY abortively terminate the connection at any time by completing the WebSocketFunc return Task, optionally with an exception.  The application SHOULD NOT invoke any of the delegates after it completes the returned task.