Deal with it

Bertrand Russel, the inventor of type theory

Overview

This blog post describes how to do type-safe websocket communication in OCaml.

Type-safety

What is type-safety? To quote stackoverflow, it’s

a language where the only operations that one can execute on data are the ones that are condoned by the data's type. That is, if your data is of type X and X doesn't support operation y, then the language will not allow you to to execute y(X).

In this post, I also assume it to be strong and static, that is, checked during compile time without giving the programmer the possibility to mess things up by casting something to anything (like to void* in C).

Why is type-safety good? Because it roots out several categories of errors.

Is type-safe serialization possible? No. I’m assuming only one type definition is used for communication between server and client, and that server and client are using the same source-code for serialize/deserialize.

OCaml - 1-minute crash course

OCaml is a language which puts high priority on type-safety. You can get a general short introduction here. For the purpose of this blog article, let my just mention that variables are defined like this:

let x = 10 in
...

and functions like this:

let plus x y = x + y

and are applied like this:

let sum = plus 10 20 in
...

Note that no explicit typing is necessary (but possible) since OCaml is fully type-inferred.

Recrusive functions are defined with let rec ... instead of just let.

Enums1 can be defined like this:

type my_enum =
  | One
  | Two
  | Three of string
  | Four of int

and switch is called match and is used like this:

match enum_value with
  | One     -> "you got one!"
  | Two     -> "two"
  | Three s -> "the string " ^ s (* concatenation is done with operator ^ *)
  | Four i  -> "the number " ^ (int_of_string i)

Websockets

You probably already know what websockets are, but just to recap (from Mozilla), it’s:

a technology that makes it possible to open a two-way interactive communication session between the user's browser and a server

Above all, it makes is possible for the server to send data to the browser without the browser needing to reload or poll the server. A typical use-case is a chat application.

Websockets communicate with frames, as you will see below. A frame is simply a

header + application data. The frame header contains information about the frame and the application data. The application data is any and all stuff you send in the frame "body".

All major browsers support websockets by now.

Serialization

We want to be able to send any data between the server and the client. Websockets can send both blobs and strings, but we’ll use serialization to strings.

There are a number of extensions to OCaml that lets you serialize types automatically. In this article I use ppx_deriving_json from the js_of_ocaml project.

Consider the following datatype for messages:

type message =
  | Ping
  | Chat of string

We can automatically generate the serialization functions by adding [@@deriving json]:

type message =
  | Ping
  | Chat of string
[@@deriving json]

and define functions that serialize and deserialize the type:

let to_json = [%to_json: message]
let of_json = [%of_json: message]
There's some weird syntax going on here, with [@@ ...] and [% ...]. These are syntax extensions, where libraries hook into the OCaml abstract syntax-tree to do all sorts of magic tricks, like, in our case, generating code to convert types to strings and back.

OK, let’s run an example!

let _ =
  let my_message = Chat "Hey, what's up?" in
  let json = to_json my_message in
  print_endline json

This will output

$ <compile the thing and run it>
[0,"Hey, what's up?"]

As you can see, all type information is lost. That’s why we collect all possible communication inside the message type in our program, so that no misunderstanding can happen between the server and the client.

Server

Alright, we need both a server and a client to get our natively compiled type-safe chat web app to work. For the websocket server, there are a couple of libraries available for OCaml. The one I’m using here is pretty old, because I’m too lazy to setup TLS on my local machine, but the same principles apply to newer libs.

Our library defines the following datatype (enum, if you will) for websocket frames:

type frame =
  | PingFrame of string
  | PongFrame of string
  | TextFrame of string
  | CloseFrame of int * string
  | BinaryFrame
  | UndefinedFrame of string

So these are the cases we must take care of in our server. Note that BinaryFrame does not carry any data, simply because this frame is not implemented by the library.

The main function to take care of an incoming frame will then look like this:

let rec handle_client channel =
  let%lwt frame = channel#read_frame in 
  match frame with
    | PingFrame msg ->
      let%lwt _ = channel#write_pong_frame in
      handle_client channel
    | TextFrame text ->
      let response = "You wrote this: " ^ text in
      let%lwt _ = channel#write_text_frame response in
      handle_client channel
    | PongFrame msg ->
      (* Do nothing *)
      return ()
    | CloseFrame (status_code, body) ->
      channel#write_close_frame
    | BinaryFrame ->
        raise (Failure "BinaryFrame not implemented")
    | UndefinedFrame msg ->
        raise (Failure ("Undefined frame: " ^ msg))

Scary? Let’s take it step by step:

  • First line defines a new (recursive) function called handle_client, which takes exactly one argument, an open websocket channel.
  • let%lwt is a monadic bind for the asynchronous library. If you don’t know what that means, just ignore it for now.
  • frame is read from the channel and used in the match expression2.

Asynchronous code in OCaml

A websocket server must be multitasking, accepting connections from multiple sources.

Async, lwt

I use lwt.

Client

todo

serialize object

same definition on both client and server

how can we be sure deserialization is correct?

test vs type safe

js class on both server and client, JSON.stringify -> no class, only object?

typescript, Any type? Cast to class

ppx_deriving_json a customized parser for each type

protobuff?

https://medium.com/@aems/one-mans-struggle-with-typescript-class-serialization-478d4bbb5826

typed json: https://www.npmjs.com/package/typedjson

type websocket_message = One | Two of int | Three of string
[@@deriving json]

5. Notes

1. They are really called algebraic datatypes, but if you don’t know what that is, just think of it as enums that can carry data.

2. Object methods are accessed with #. The dot operator is already used for records in OCaml.