WebSocket frame codec (RFC 6455)
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

ocaml-linkedin: apply dune fmt

Pure formatting changes from `dune fmt`: doc comment placement moves
from above the binding to below it for `type`s, multi-line `match`
expressions collapse onto one line where they fit, and infix operator
applications pick up spaces (`Soup.($?)` -> `Soup.( $? )`). No
semantic changes.

+90 -1
+82
README.md
··· 1 + # websocket 2 + 3 + RFC 6455 WebSocket frame codec for OCaml. 4 + 5 + `websocket` encodes and decodes [RFC 6455][rfc6455] frames: text, 6 + binary, ping, pong, close, with masking and fragmentation. It does 7 + **not** perform the HTTP upgrade handshake — drive that with an HTTP 8 + client or server (e.g. [requests][requests]) and hand the negotiated 9 + byte stream here. 10 + 11 + [rfc6455]: https://www.rfc-editor.org/rfc/rfc6455 12 + [requests]: https://tangled.org/gazagnaire.org/ocaml-requests 13 + 14 + ## Installation 15 + 16 + Install with opam: 17 + 18 + <!-- $MDX skip --> 19 + ```sh 20 + $ opam install websocket 21 + ``` 22 + 23 + If opam cannot find the package, it may not yet be released in the 24 + public `opam-repository`. Add the overlay repository, then install 25 + it: 26 + 27 + <!-- $MDX skip --> 28 + ```sh 29 + $ opam repo add samoht https://tangled.org/gazagnaire.org/opam-overlay.git 30 + $ opam update 31 + $ opam install websocket 32 + ``` 33 + 34 + ## Usage 35 + 36 + Encode a text frame (client frames are masked by default, as required 37 + by the RFC): 38 + 39 + ```ocaml 40 + # let frame = Websocket.text "hello" in 41 + let bytes = Websocket.encode frame in 42 + String.length bytes 43 + - : int = 11 44 + ``` 45 + 46 + Decode a frame from a buffer. The decoder returns the frame and any 47 + trailing bytes, or signals that more input is needed: 48 + 49 + ```ocaml 50 + # let bytes = Websocket.encode ~mask:false (Websocket.text "hello") in 51 + match Websocket.decode bytes with 52 + | Ok ({ opcode; payload; fin }, rest) -> 53 + Fmt.str "opcode=%a payload=%S fin=%b rest=%d" 54 + Websocket.pp_opcode opcode payload fin (String.length rest) 55 + | Error `Need_more -> "need more" 56 + | Error (`Invalid m) -> "invalid: " ^ m 57 + - : string = "opcode=text payload=\"hello\" fin=true rest=0" 58 + ``` 59 + 60 + `Need_more` is the normal case while streaming: keep reading from 61 + the socket and re-attempting the decode until a frame lands: 62 + 63 + ```ocaml 64 + # let full = Websocket.encode ~mask:false (Websocket.binary "\x01\x02\x03") in 65 + let partial = String.sub full 0 (String.length full - 1) in 66 + Websocket.decode partial 67 + - : (Websocket.t * string, [ `Invalid of string | `Need_more ]) result = 68 + Error `Need_more 69 + ``` 70 + 71 + Build the common control frames directly: 72 + 73 + ```ocaml 74 + # let p = Websocket.ping "keepalive" 75 + and c = Websocket.close ~code:1000 ~reason:"normal" () in 76 + (p.opcode, c.opcode) 77 + - : Websocket.opcode * Websocket.opcode = (Websocket.Ping, Websocket.Close) 78 + ``` 79 + 80 + ## Licence 81 + 82 + ISC
+4
dune
··· 1 1 (env 2 2 (dev 3 3 (flags :standard %{dune-warnings}))) 4 + 5 + (mdx 6 + (files README.md) 7 + (libraries websocket fmt))
+3 -1
dune-project
··· 1 1 (lang dune 3.21) 2 + (using mdx 0.4) 2 3 (name websocket) 3 4 4 5 (generate_opam_files true) ··· 17 18 (depends 18 19 (ocaml (>= 5.2)) 19 20 (fmt (>= 0.9)) 20 - (alcotest :with-test))) 21 + (alcotest :with-test) 22 + (mdx :with-test)))
+1
websocket.opam
··· 13 13 "ocaml" {>= "5.2"} 14 14 "fmt" {>= "0.9"} 15 15 "alcotest" {with-test} 16 + "mdx" {with-test} 16 17 "odoc" {with-doc} 17 18 ] 18 19 build: [