O1 N2O ECO: The Standard ML implementation

This page contains the description of WebSocket and static HTTP server implementation and protocol stack for application development of top of it that conforms to N2O ECO specification.

As you may know there was no WebSocket implementation for Standard ML until now. Here is the first WebSocket server with switch to static HTML if needed for serving host pages of WebSocket client. The demo echo application is presented as N2O protocol for Standard ML languages at final.

$ wscat -c ws://127.0.0.1:8989/ws/
> connected (press CTRL+C to quit)
> helo
> helo

We also updated N2O ECO site with additional o1 (Standard ML) implementation to existent o3 (Haskell) and o7 (Erlang) implementations.

N2O has two default transports: WebSocket and MQTT and Standard ML version provides WebSocket/HTTP server along with its own vision of typing N2O tract as it differs from Haskell version.

TCP Server

In Standard ML one book you will need for implementing the WebSocket server is Unix System Programming with Standard ML .

In Standard ML you have two major distribution that support standard concurrency library CML, Concurrent ML extension implemented as part of base library with its own scheduler implemented in Standard ML. This library is supported by SML/NJ and MLton compilers, so N2O for Standard ML supports both of them out of the box.

run (program_name, arglist) =
  let val s = INetSock.TCP.socket()
   in Socket.Ctl.setREUSEADDR (s, true);
      Socket.bind(s, INetSock.any 8989);      
      Socket.listen(s, 5);
      acceptLoop s end

Acceptor loop uses CML spawn primitive for lightweight context creation for socket connection:

 fun acceptLoop server_sock =
   let val (s, _) = Socket.accept server_sock
    in CML.spawn (fn() = connMain(s)); acceptLoop server_sock end

WebSocket RFC 6455

In pure and refined web stack WebSocket and static HTTP server could be unified up to switch function that performs HTTP 101 upgrade:

fun upgrade sock req =
   (checkHandshake req; 
    { body = Word8Vector.fromList nil, 
      status = 101, 
      headers = [(“Upgrade”, “websocket”),
                 (“Connection”, “Upgrade”), 
                 (“Sec-WebSocket-Accept”, getKey req)] })

101 upgrade command logic could be obtained from RFC 6455.

fun needUpgrade req = case header “Upgrade” req of 
    SOME (_,v) => (lower v) = “websocket” | _ => false

Also we have needUpgrade flag function that checks headers:

fun needUpgrade req = case header Upgrade req of
     SOME (_,v) => (lower v) = “websocket” | _ => false

The WebSocket structure of ML language contains the decode and encoder to/from raw packets Frame and datatype used in higher level code:

datatype Msg = Text of V.vector | Bin of V.vector | Close of Word32.word
             | Cont of V.vector | Ping | Pong
type Frame = { fin : bool, rsv1 : bool, rsv2 : bool, rsv3 : bool, 
               typ : FrameType, payload : V.vector }

SHA-1 RFC 3174

The getKey function is used in SHA-1 protocol which is separate RFC 3174, and also need to be implemented. Fortunately one implementation by Sophia Donataccio was existed, so we could create a smaller one.

fun getKey req = case header “Sec-WebSocket-Key” req of
    NONE => raise BadRequest “No Sec-WebSocket-Key header”
  | SOME (_,key) => let val magic = “258EAFA5-E914–47DA-95CA-C5AB0DC85B11” 
 in Base64.encode (SHA1.encode (Byte.stringToBytes (key^magic))) end

HTTP/1.1 RFC 2068

Check handshake has only three fail cases:

fun checkHandshake req =
  (if #cmd req <> “GET” then 
      raise BadRequest “Method must be GET” else ();
   if #vers req <> “HTTP/1.1” then 
      raise BadRequest “HTTP version must be 1.1” else ();
   case header “Sec-WebSocket-Version” req of 
        SOME (_,”13") => () 
      | _ => raise BadRequest “WebSocket version must be 13”)

Web Server

The internal types for HTTP and WebSocket server are specify Req and Resp types that are reused in HTTP and N2O layers for all implementations:

structure HTTP = struct
  type Headers = (string*string) list
  type Req = { cmd : string, path : string, headers : Headers, vers : string }
  type Resp = { status : int, headers : Headers, body : Word8Vector.vector }
end

The handler signature hides the call chain from HTTP request and WebScoket frame to binary result for returning to socket. You should create your own application level implementation to provide this abstraction that will be called in context of TCP server:

signature HANDLER = sig
  val hnd : HTTP.Req WebSocket.Msg -> WebSocket.Res
end

N2O Application Server

The N2O types specifies the I/O types along with Context and run function:

signature PROTO = sig
  type Prot 
  type Ev 
  type Res 
  type Req
  val proto : Prot ->Ev
end

The N2O functor wraps the Context and runner with protocol type which provides application level functionality.

functor MkN2O(M : PROTO) = struct
  type Cx = {req: M.Req, module: M.Ev -&gt; M.Res}
  fun run (cx : Cx) (handlers : (Cx -> Cx) list) (msg : M.Prot) =
      (#module cx) (M.proto msg) end

Echo Application

According to provided specification we have only one chance to write echo server example application that will unveils the protocol implementation and context implementation:

structure EchoProto : PROTO = struct
  type Prot = WS.Msg Ev = Word8Vector.vector option
  type Res = WS.Res Req = HTTP.Req
  fun proto (WS.TextMsg s) = SOME s | proto _ = NONE end 

structure Echo = MkN2O(EchoProto)

The echo handler contains all server context packed in single structure. Here echo is a page subprotocol that is plugged as local handler for protocol runner. The router function provides module extraction that returns only echo subprotocol in context for each request. The run function is called with a given router for each message and each request:

structure EchoHandler : HANDLER = struct
  fun echo NONE = WS.Ok
    | echo(SOME s) = WS.Reply(WS.Text s)
  fun router (cx : Echo.Cx) = {req=(#req cx),module=echo}
  fun hnd (req,msg) = Echo.run {req=req,module=echo} [router] msg
end 

structure Server = MkServer(EchoHandler)

The code is provided at gihub.com/o1/n2o .