Skip to content

Commit a61a0ae

Browse files
refactor: handle close code properly
1 parent 9206d3d commit a61a0ae

File tree

6 files changed

+86
-74
lines changed

6 files changed

+86
-74
lines changed

examples/websocket_echo_server/main.mbt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,10 @@ pub async fn start_echo_server() -> Unit {
6262
}
6363
}
6464
} catch {
65-
@websocket.ConnectionClosed(e) =>
66-
println("Client \{client_addr} disconnected with \{e}")
65+
@websocket.ConnectionClosed(e, reason) =>
66+
println(
67+
"Client \{client_addr} disconnected with \{e}, reason: \{reason}",
68+
)
6769
e => println("Error with client \{client_addr}: \{e}")
6870
}
6971
},

src/websocket/client.mbt

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
struct Client {
1818
conn : @socket.Tcp
1919
rand : @random.Rand
20-
mut closed : CloseCode?
20+
mut closed : WebSocketError?
2121
}
2222

2323
///|
@@ -139,39 +139,44 @@ pub async fn Client::connect(
139139
pub fn Client::close(self : Client) -> Unit {
140140
if self.closed is None {
141141
self.conn.close()
142-
self.closed = Some(Normal)
142+
self.closed = Some(ConnectionClosed(Normal, None))
143143
}
144144
}
145145
146146
///|
147147
pub async fn Client::send_close(
148148
self : Client,
149149
code? : CloseCode = Normal,
150-
reason? : BytesView = "",
150+
reason? : String,
151151
) -> Unit {
152152
if self.closed is Some(_) {
153153
return
154154
}
155155
let mut payload = FixedArray::make(0, b'\x00')
156156
let code_int = code.to_int()
157-
payload = FixedArray::make(2 + reason.length(), b'\x00')
157+
let reason_bytes = if reason is Some(r) {
158+
@encoding/utf8.encode(r)
159+
} else {
160+
b""
161+
}
162+
payload = FixedArray::make(2 + reason_bytes.length(), b'\x00')
158163
payload.unsafe_write_uint16_be(0, code_int.to_uint16())
159-
payload.blit_from_bytesview(2, reason)
164+
payload.blit_from_bytesview(2, reason_bytes)
160165
write_frame(
161166
self.conn,
162167
true,
163168
OpCode::Close,
164169
payload.unsafe_reinterpret_as_bytes(),
165170
self.rand.int().to_be_bytes(),
166171
)
167-
self.closed = Some(code)
172+
self.closed = Some(ConnectionClosed(code, reason))
168173
}
169174
170175
///|
171176
/// Send a text message
172177
pub async fn Client::send_text(self : Client, text : String) -> Unit {
173178
if self.closed is Some(code) {
174-
raise ConnectionClosed(code)
179+
raise code
175180
}
176181
let payload = @encoding/utf8.encode(text)
177182
write_frame(
@@ -187,7 +192,7 @@ pub async fn Client::send_text(self : Client, text : String) -> Unit {
187192
/// Send a binary message
188193
pub async fn Client::send_binary(self : Client, data : Bytes) -> Unit {
189194
if self.closed is Some(code) {
190-
raise ConnectionClosed(code)
195+
raise code
191196
}
192197
write_frame(
193198
self.conn,
@@ -205,7 +210,7 @@ pub async fn Client::send_binary(self : Client, data : Bytes) -> Unit {
205210
/// indicating if a pong was received within a timeout
206211
async fn Client::_ping(self : Client, data? : Bytes = Bytes::new(0)) -> Unit {
207212
if self.closed is Some(code) {
208-
raise ConnectionClosed(code)
213+
raise code
209214
}
210215
write_frame(
211216
self.conn,
@@ -222,7 +227,7 @@ async fn Client::_ping(self : Client, data? : Bytes = Bytes::new(0)) -> Unit {
222227
/// This is done automatically, so it is not exposed in the public API
223228
async fn Client::pong(self : Client, data? : Bytes = Bytes::new(0)) -> Unit {
224229
if self.closed is Some(code) {
225-
raise ConnectionClosed(code)
230+
raise code
226231
}
227232
write_frame(
228233
self.conn,
@@ -238,7 +243,7 @@ async fn Client::pong(self : Client, data? : Bytes = Bytes::new(0)) -> Unit {
238243
/// Returns the complete message after assembling all frames
239244
pub async fn Client::receive(self : Client) -> Message {
240245
if self.closed is Some(code) {
241-
raise ConnectionClosed(code)
246+
raise code
242247
}
243248
let frames : Array[Frame] = []
244249
let mut first_opcode : OpCode? = None
@@ -250,18 +255,20 @@ pub async fn Client::receive(self : Client) -> Message {
250255
OpCode::Close => {
251256
// Parse close code and reason
252257
let mut close_code = Normal
253-
if frame.payload.length() >= 2 {
254-
let payload_arr = frame.payload.to_fixedarray()
255-
let code_int = (payload_arr[0].to_int() << 8) |
256-
payload_arr[1].to_int()
257-
close_code = CloseCode::from_int(code_int) catch {
258-
FrameError =>
259-
// Invalid close code, use ProtocolError
260-
ProtocolError
261-
}
258+
let mut reason : String? = None
259+
if frame.payload is [u16be(code), .. data] {
260+
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+
)
262269
}
263-
self.closed = Some(close_code)
264-
raise ConnectionClosed(close_code)
270+
self.closed = Some(ConnectionClosed(close_code, reason))
271+
raise ConnectionClosed(close_code, reason)
265272
}
266273
OpCode::Ping => {
267274
// Auto-respond to ping with pong

src/websocket/pkg.generated.mbti

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,8 @@ async fn run_server(@socket.Addr, String, async (ServerConnection, @socket.Addr)
1010

1111
// Errors
1212
pub suberror WebSocketError {
13-
ConnectionClosed(CloseCode)
13+
ConnectionClosed(CloseCode, String?)
1414
InvalidHandshake(String)
15-
FrameError(String)
1615
}
1716
impl Show for WebSocketError
1817

@@ -22,6 +21,7 @@ fn Client::close(Self) -> Unit
2221
async fn Client::connect(String, String, port? : Int, headers? : Map[String, String]) -> Self
2322
async fn Client::receive(Self) -> Message
2423
async fn Client::send_binary(Self, Bytes) -> Unit
24+
async fn Client::send_close(Self, code? : CloseCode, reason? : String) -> Unit
2525
async fn Client::send_text(Self, String) -> Unit
2626

2727
pub(all) enum CloseCode {
@@ -33,7 +33,9 @@ pub(all) enum CloseCode {
3333
InvalidFramePayload
3434
PolicyViolation
3535
MessageTooBig
36+
MissingExtension
3637
InternalError
38+
Other(Int)
3739
}
3840
impl Eq for CloseCode
3941
impl Show for CloseCode
@@ -48,7 +50,7 @@ type ServerConnection
4850
fn ServerConnection::close(Self) -> Unit
4951
async fn ServerConnection::receive(Self) -> Message
5052
async fn ServerConnection::send_binary(Self, BytesView) -> Unit
51-
async fn ServerConnection::send_close(Self, code? : CloseCode, reason? : BytesView) -> Unit
53+
async fn ServerConnection::send_close(Self, code? : CloseCode, reason? : String) -> Unit
5254
async fn ServerConnection::send_text(Self, StringView) -> Unit
5355

5456
// Type aliases

0 commit comments

Comments
 (0)