@@ -58,7 +58,12 @@ pub async fn Client::connect(
5858 let key = base64_encode (nonce .unsafe_reinterpret_as_bytes ())
5959 let request = "GET \{ path } HTTP/1.1\r\n "
6060 conn .write (request )
61- conn .write ("Host: \{ host } \r\n " )
61+ let host_header = if port == 80 || port == 443 {
62+ host
63+ } else {
64+ "\{ host } :\{ port } "
65+ }
66+ conn .write ("Host: \{ host_header } \r\n " )
6267 conn .write ("Upgrade: websocket\r\n " )
6368 conn .write ("Connection: Upgrade\r\n " )
6469 conn .write ("Sec-WebSocket-Key: \{ key } \r\n " )
@@ -149,17 +154,17 @@ pub async fn Client::send_close(
149154 code ? : CloseCode = Normal ,
150155 reason ? : String ,
151156) -> Unit {
152- if self .closed is Some (_ ) {
153- return
157+ if self .closed is Some (e ) {
158+ raise e
154159 }
155- let mut payload = FixedArray ::make (0 , b '\x00 ' )
156160 let code_int = code .to_int ()
157- let reason_bytes = if reason is Some (r ) {
158- @encoding/utf8 .encode (r )
159- } else {
160- b ""
161+ let reason_bytes = @encoding/utf8 .encode (reason .unwrap_or ("" ))
162+ if reason_bytes .length () > 123 {
163+ // Close reason too long
164+ // TODO: should we close the connection anyway?
165+ fail ("Close reason too long" )
161166 }
162- payload = FixedArray ::make (2 + reason_bytes .length (), b '\x00 ' )
167+ let payload = FixedArray ::make (2 + reason_bytes .length (), b '\x00 ' )
163168 payload .unsafe_write_uint16_be (0 , code_int .to_uint16 ())
164169 payload .blit_from_bytesview (2 , reason_bytes )
165170 write_frame (
@@ -169,12 +174,16 @@ pub async fn Client::send_close(
169174 payload .unsafe_reinterpret_as_bytes (),
170175 self .rand.int ().to_be_bytes (),
171176 )
177+ // Wait until the server acknowledges the close
178+ ignore (read_frame (self .conn)) catch {
179+ _ => ()
180+ }
172181 self .closed = Some (ConnectionClosed (code , reason ))
173182}
174183
175184///|
176185/// Send a text message
177- pub async fn Client ::send_text (self : Client , text : String ) -> Unit {
186+ pub async fn Client ::send_text (self : Client , text : StringView ) -> Unit {
178187 if self .closed is Some (code ) {
179188 raise code
180189 }
@@ -190,7 +199,7 @@ pub async fn Client::send_text(self : Client, text : String) -> Unit {
190199
191200///|
192201/// Send a binary message
193- pub async fn Client ::send_binary (self : Client , data : Bytes ) -> Unit {
202+ pub async fn Client ::send_binary (self : Client , data : BytesView ) -> Unit {
194203 if self .closed is Some (code ) {
195204 raise code
196205 }
@@ -247,67 +256,127 @@ pub async fn Client::receive(self : Client) -> Message {
247256 }
248257 let frames : Array [Frame ] = []
249258 let mut first_opcode : OpCode? = None
250- for {
259+ while self .closed is None {
251260 let frame = read_frame (self .conn)
252261
253262 // Handle control frames immediately
254263 match frame .opcode {
255- OpCode :: Close => {
264+ Close => {
256265 // Parse close code and reason
266+ // Ref: https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.1
257267 let mut close_code = Normal
258268 let mut reason : String? = None
259- if frame .payload is [u16be (code ), .. data ] {
269+ if frame .payload is [u16be (code ), .. rest ] {
260270 close_code = CloseCode ::from_int (code .reinterpret_as_int ())
261- reason = Some (
262- @encoding/utf8 .decode (data ) catch {
263- _ => {
264- close_code = ProtocolError
265- ""
266- }
267- },
268- )
271+ reason = Some (@encoding/utf8 .decode (rest )) catch {
272+ _ => {
273+ // Invalid reason, fail fast
274+ close_code = ProtocolError
275+ None
276+ }
277+ }
278+ } else {
279+ guard frame .payload is [] else {
280+ // Invalid close payload
281+ close_code = ProtocolError
282+ }
269283 }
270- self .closed = Some (ConnectionClosed (close_code , reason ))
271- raise ConnectionClosed (close_code , reason )
284+ // If we didn't send close first, respond with close
285+ if self .closed is None {
286+ // Echo the close frame back and close
287+ self .send_close (code = close_code , reason ?) catch {
288+ _ => ()
289+ }
290+ }
291+ continue
272292 }
273- OpCode :: Ping => {
293+ Ping =>
274294 // Auto-respond to ping with pong
275295 self .pong (data = frame .payload)
276- continue
277- }
278- OpCode ::Pong =>
296+ Pong =>
279297 // Ignore pong frames
280- continue
281- _ => ()
282- }
283-
284- // Track the first opcode for message type
285- if first_opcode is None {
286- first_opcode = Some (frame .opcode)
287- }
288- frames .push (frame )
289-
290- // If this is the final frame, assemble the message
291- if frame .fin {
292- break
293- }
294- }
295-
296- // Assemble message from frames
297- let total_size = frames .fold (init = 0 , fn (acc , f ) { acc + f .payload.length () })
298- let data = FixedArray ::make (total_size , b '\x00 ' )
299- let mut offset = 0
300- for frame in frames {
301- let payload_arr = frame .payload.to_fixedarray ()
302- for i = 0 ; i < payload_arr .length (); i = i + 1 {
303- data [offset + i ] = payload_arr [i ]
298+ ()
299+ Text =>
300+ if first_opcode is Some (_ ) {
301+ // Ref https://datatracker.ietf.org/doc/html/rfc6455#section-5.4
302+ // We don't have extensions, so fragments MUST NOT be interleaved
303+ self .send_close (code = ProtocolError ) catch {
304+ _ => ()
305+ }
306+ } else if frame .fin {
307+ // Single-frame text message
308+ return Message ::Text (@encoding/utf8 .decode (frame .payload)) catch {
309+ _ => {
310+ // Ref https://datatracker.ietf.org/doc/html/rfc6455#section-8.1
311+ // We MUST Fail the WebSocket Connection if the payload is not
312+ // valid UTF-8
313+ self .send_close (code = InvalidFramePayload ) catch {
314+ _ => ()
315+ }
316+ continue
317+ }
318+ }
319+ } else {
320+ first_opcode = Some (Text )
321+ // Start of fragmented text message
322+ frames .push (frame )
323+ }
324+ Binary =>
325+ if first_opcode is Some (_ ) {
326+ // Ref https://datatracker.ietf.org/doc/html/rfc6455#section-5.4
327+ // We don't have extensions, so fragments MUST NOT be interleaved
328+ self .send_close (code = ProtocolError ) catch {
329+ _ => ()
330+ }
331+ } else if frame .fin {
332+ // Single-frame binary message
333+ return Message ::Binary (frame .payload)
334+ } else {
335+ first_opcode = Some (Binary )
336+ frames .push (frame )
337+ }
338+ Continuation => {
339+ if first_opcode is None {
340+ // Continuation frame without a starting frame
341+ self .send_close (code = ProtocolError ) catch {
342+ _ => ()
343+ }
344+ continue
345+ }
346+ frames .push (frame )
347+ if frame .fin {
348+ // Final fragment received, assemble message
349+ let total_size = frames .fold (init = 0 , fn (acc , f ) {
350+ acc + f .payload.length ()
351+ })
352+ let data = FixedArray ::make (total_size , b '\x00 ' )
353+ let mut offset = 0
354+ for f in frames {
355+ data .blit_from_bytes (offset , f .payload, 0 , f .payload.length ())
356+ offset + = f .payload.length ()
357+ }
358+ let message_data = data .unsafe_reinterpret_as_bytes ()
359+ match first_opcode {
360+ Some (Text ) => {
361+ let text = @encoding/utf8 .decode (message_data ) catch {
362+ _ => {
363+ self .send_close (code = InvalidFramePayload ) catch {
364+ _ => ()
365+ }
366+ continue
367+ }
368+ }
369+ return Message ::Text (text )
370+ }
371+ Some (Binary ) => return Message ::Binary (message_data )
372+ _ => panic ()
373+ }
374+ // Reset for next message
375+ frames .clear ()
376+ first_opcode = None
377+ }
378+ }
304379 }
305- offset + = payload_arr .length ()
306- }
307- let message_data = data .unsafe_reinterpret_as_bytes ()
308- match first_opcode {
309- Some (OpCode ::Text ) => Text (@encoding/utf8 .decode_lossy (message_data ))
310- Some (OpCode ::Binary ) => Binary (message_data )
311- _ => Binary (message_data )
312380 }
381+ raise self .closed.unwrap ()
313382}
0 commit comments