package miaou-driver-web
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>
Miaou web driver (xterm.js over WebSocket)
Install
dune-project
Dependency
Authors
Maintainers
Sources
v0.5.2.tar.gz
md5=60a3b9f181f24572a06a9492532bfdda
sha512=fcc35a275066be2900e6201782faf47503076fa4640f08cf78067835a6f447b74613009e55b2ac799adb7ca46f1bffa261fc5971753f2cc3c6bef327511c7ef6
doc/src/miaou-driver-web.driver/web_websocket.ml.html
Source file web_websocket.ml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165(*****************************************************************************) (* *) (* SPDX-License-Identifier: MIT *) (* Copyright (c) 2025 Nomadic Labs <contact@nomadic-labs.com> *) (* *) (*****************************************************************************) (** Minimal WebSocket (RFC 6455) implementation for Eio. Uses direct flow writes instead of Buf_write to avoid buffering issues with the WebSocket protocol upgrade. *) type t = {mutable closed : bool; write : string -> unit} let websocket_magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" let accept_key client_key = let open Digestif in let hash = SHA1.digest_string (client_key ^ websocket_magic) in Base64.encode_exn (SHA1.to_raw_string hash) (* Parse HTTP headers from buf_read until blank line *) let parse_headers br = let headers = Hashtbl.create 16 in let rec loop () = let line = Eio.Buf_read.line br in if String.length line = 0 || line = "\r" then headers else match String.index_opt line ':' with | Some i -> let key = String.lowercase_ascii (String.trim (String.sub line 0 i)) in let value = String.trim (String.sub line (i + 1) (String.length line - i - 1)) in Hashtbl.replace headers key value ; loop () | None -> loop () in loop () let upgrade headers ~write = let is_upgrade = match Hashtbl.find_opt headers "upgrade" with | Some v -> String.lowercase_ascii v = "websocket" | None -> false in let ws_key = Hashtbl.find_opt headers "sec-websocket-key" in match (is_upgrade, ws_key) with | true, Some key -> let accept = accept_key key in let response = Printf.sprintf "HTTP/1.1 101 Switching Protocols\r\n\ Upgrade: websocket\r\n\ Connection: Upgrade\r\n\ Sec-WebSocket-Accept: %s\r\n\ \r\n" accept in write response ; Some {closed = false; write} | _ -> None (* WebSocket frame opcodes *) let _opcode_continuation = 0x0 let opcode_text = 0x1 let _opcode_binary = 0x2 let opcode_close = 0x8 let opcode_ping = 0x9 let opcode_pong = 0xA (* Build a WebSocket frame as a string (server frames are NOT masked) *) let build_frame ~opcode payload = let len = String.length payload in let buf = Buffer.create (len + 10) in (* FIN bit + opcode *) Buffer.add_uint8 buf (0x80 lor opcode) ; (* Payload length (no mask bit for server) *) if len < 126 then Buffer.add_uint8 buf len else if len < 65536 then begin Buffer.add_uint8 buf 126 ; Buffer.add_uint16_be buf len end else begin Buffer.add_uint8 buf 127 ; (* Write 64-bit length *) Buffer.add_int32_be buf 0l ; Buffer.add_int32_be buf (Int32.of_int len) end ; if len > 0 then Buffer.add_string buf payload ; Buffer.contents buf let write_frame t ~opcode payload = t.write (build_frame ~opcode payload) (* Read a WebSocket frame, returns (opcode, payload) *) let read_frame br = let b0 = Eio.Buf_read.uint8 br in let _fin = b0 land 0x80 <> 0 in let opcode = b0 land 0x0F in let b1 = Eio.Buf_read.uint8 br in let masked = b1 land 0x80 <> 0 in let len = b1 land 0x7F in let len = if len = 126 then Eio.Buf_read.BE.uint16 br else if len = 127 then let _high = Eio.Buf_read.BE.uint32 br in Int32.to_int (Eio.Buf_read.BE.uint32 br) else len in let mask_key = if masked then Some (Eio.Buf_read.take 4 br) else None in let payload = Eio.Buf_read.take len br in let payload = match mask_key with | None -> payload | Some mask -> let buf = Bytes.of_string payload in for i = 0 to Bytes.length buf - 1 do let b = Bytes.get_uint8 buf i in let m = Char.code mask.[i mod 4] in Bytes.set_uint8 buf i (b lxor m) done ; Bytes.to_string buf in (opcode, payload) let send_text t msg = if not t.closed then write_frame t ~opcode:opcode_text msg let rec recv_text t br = if t.closed then None else match read_frame br with | exception End_of_file -> t.closed <- true ; None | exception Eio.Io _ -> t.closed <- true ; None | opcode, _payload when opcode = opcode_close -> t.closed <- true ; (* Send close frame back *) (try write_frame t ~opcode:opcode_close "" with _ -> ()) ; None | opcode, payload when opcode = opcode_ping -> (* Respond with pong *) (try write_frame t ~opcode:opcode_pong payload with _ -> ()) ; recv_text t br | opcode, _payload when opcode = opcode_pong -> (* Ignore pong *) recv_text t br | _opcode, payload -> Some payload let close t = if not t.closed then begin t.closed <- true ; try write_frame t ~opcode:opcode_close "" with _ -> () end let is_closed t = t.closed
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>