@@ -4,79 +4,113 @@ import sttp.model.Headers
4
4
import sttp .monad .MonadError
5
5
import sttp .monad .syntax ._
6
6
7
- /** The `send` and `receive` methods may result in a failed effect, with either one of [[WebSocketException ]]
8
- * exceptions, or a backend-specific exception.
7
+ /** The `send*` and `receive*` methods may result in a failed effect, with either one of [[WebSocketException ]]
8
+ * exceptions, or a backend-specific exception. Specifically, they will fail with [[WebSocketClosed ]] if the
9
+ * web socket is closed.
10
+ *
11
+ * See the `either` and `eitherClose` method to lift web socket closed events to the value level.
9
12
*/
10
13
trait WebSocket [F [_]] {
11
14
12
- /** After receiving a close frame, no further interactions with the web socket should happen. Subsequent invocations
13
- * of `receive`, as well as `send`, will fail with the [[WebSocketClosed ]] exception.
15
+ /** Receive the next frame from the web socket. This can be a data frame, or a control frame including
16
+ * [[WebSocketFrame.Close ]]. After receiving a close frame, no further interactions with the web socket should
17
+ * happen.
18
+ *
19
+ * However, not all implementations expose the close frame, and web sockets might also get closed without the proper
20
+ * close frame exchange. In such cases, as well as when invoking `receive`/`send` after receiving a close frame,
21
+ * this effect will fail with the [[WebSocketClosed ]] exception.
14
22
*/
15
23
def receive (): F [WebSocketFrame ]
16
24
def send (f : WebSocketFrame , isContinuation : Boolean = false ): F [Unit ]
17
25
def isOpen (): F [Boolean ]
18
26
19
27
/** Receive a single data frame, ignoring others. The frame might be a fragment.
28
+ * Will fail with [[WebSocketClosed ]] if the web socket is closed, or if a close frame is received.
20
29
* @param pongOnPing Should a [[WebSocketFrame.Pong ]] be sent when a [[WebSocketFrame.Ping ]] is received.
21
30
*/
22
- def receiveDataFrame (pongOnPing : Boolean = true ): F [Either [ WebSocketFrame .Close , WebSocketFrame . Data [_] ]] =
31
+ def receiveDataFrame (pongOnPing : Boolean = true ): F [WebSocketFrame .Data [_]] =
23
32
receive().flatMap {
24
- case close : WebSocketFrame .Close => ( Left ( close): Either [ WebSocketFrame . Close , WebSocketFrame . Data [_]]).unit
25
- case d : WebSocketFrame .Data [_] => ( Right (d) : Either [ WebSocketFrame . Close , WebSocketFrame . Data [_]]).unit
33
+ case close : WebSocketFrame .Close => monad.error( WebSocketClosed ( Some ( close)))
34
+ case d : WebSocketFrame .Data [_] => monad.unit(d)
26
35
case WebSocketFrame .Ping (payload) if pongOnPing =>
27
36
send(WebSocketFrame .Pong (payload)).flatMap(_ => receiveDataFrame(pongOnPing))
28
37
case _ => receiveDataFrame(pongOnPing)
29
38
}
30
39
31
40
/** Receive a single text data frame, ignoring others. The frame might be a fragment. To receive whole messages,
32
41
* use [[receiveText ]].
42
+ * Will fail with [[WebSocketClosed ]] if the web socket is closed, or if a close frame is received.
33
43
* @param pongOnPing Should a [[WebSocketFrame.Pong ]] be sent when a [[WebSocketFrame.Ping ]] is received.
34
44
*/
35
- def receiveTextFrame (pongOnPing : Boolean = true ): F [Either [ WebSocketFrame .Close , WebSocketFrame . Text ] ] =
45
+ def receiveTextFrame (pongOnPing : Boolean = true ): F [WebSocketFrame .Text ] =
36
46
receiveDataFrame(pongOnPing).flatMap {
37
- case Left (close) => (Left (close): Either [WebSocketFrame .Close , WebSocketFrame .Text ]).unit
38
- case Right (t : WebSocketFrame .Text ) => (Right (t): Either [WebSocketFrame .Close , WebSocketFrame .Text ]).unit
39
- case _ => receiveTextFrame(pongOnPing)
47
+ case t : WebSocketFrame .Text => t.unit
48
+ case _ => receiveTextFrame(pongOnPing)
40
49
}
41
50
42
51
/** Receive a single binary data frame, ignoring others. The frame might be a fragment. To receive whole messages,
43
52
* use [[receiveBinary ]].
53
+ * Will fail with [[WebSocketClosed ]] if the web socket is closed, or if a close frame is received.
44
54
* @param pongOnPing Should a [[WebSocketFrame.Pong ]] be sent when a [[WebSocketFrame.Ping ]] is received.
45
55
*/
46
- def receiveBinaryFrame (pongOnPing : Boolean = true ): F [Either [ WebSocketFrame .Close , WebSocketFrame . Binary ] ] =
56
+ def receiveBinaryFrame (pongOnPing : Boolean = true ): F [WebSocketFrame .Binary ] =
47
57
receiveDataFrame(pongOnPing).flatMap {
48
- case Left (close) => (Left (close): Either [WebSocketFrame .Close , WebSocketFrame .Binary ]).unit
49
- case Right (t : WebSocketFrame .Binary ) => (Right (t): Either [WebSocketFrame .Close , WebSocketFrame .Binary ]).unit
50
- case _ => receiveBinaryFrame(pongOnPing)
58
+ case t : WebSocketFrame .Binary => t.unit
59
+ case _ => receiveBinaryFrame(pongOnPing)
51
60
}
52
61
53
62
/** Receive a single text message (which might come from multiple, fragmented frames).
54
63
* Ignores non-text frames and returns combined results.
64
+ * Will fail with [[WebSocketClosed ]] if the web socket is closed, or if a close frame is received.
55
65
* @param pongOnPing Should a [[WebSocketFrame.Pong ]] be sent when a [[WebSocketFrame.Ping ]] is received.
56
66
*/
57
- def receiveText (pongOnPing : Boolean = true ): F [Either [ WebSocketFrame . Close , String ] ] =
67
+ def receiveText (pongOnPing : Boolean = true ): F [String ] =
58
68
receiveConcat(() => receiveTextFrame(pongOnPing), _ + _)
59
69
60
70
/** Receive a single binary message (which might come from multiple, fragmented frames).
61
71
* Ignores non-binary frames and returns combined results.
72
+ * Will fail with [[WebSocketClosed ]] if the web socket is closed, or if a close frame is received.
62
73
* @param pongOnPing Should a [[WebSocketFrame.Pong ]] be sent when a [[WebSocketFrame.Ping ]] is received.
63
74
*/
64
- def receiveBinary (pongOnPing : Boolean ): F [Either [ WebSocketFrame . Close , Array [Byte ] ]] =
75
+ def receiveBinary (pongOnPing : Boolean ): F [Array [Byte ]] =
65
76
receiveConcat(() => receiveBinaryFrame(pongOnPing), _ ++ _)
66
77
78
+ /** Extracts the received close frame (if available) as the left side of an either, or returns the original result
79
+ * on the right.
80
+ *
81
+ * Will fail with [[WebSocketClosed ]] if the web socket is closed, but no close frame is available.
82
+ *
83
+ * @param f The effect describing web socket interactions.
84
+ */
85
+ def eitherClose [T ](f : => F [T ]): F [Either [WebSocketFrame .Close , T ]] =
86
+ f.map(t => Right (t): Either [WebSocketFrame .Close , T ]).handleError { case WebSocketClosed (Some (close)) =>
87
+ (Left (close): Either [WebSocketFrame .Close , T ]).unit
88
+ }
89
+
90
+ /** Returns an effect computing a:
91
+ *
92
+ * - `Left` if the web socket is closed - optionally with the received close frame (if available).
93
+ * - `Right` with the original result otherwise.
94
+ *
95
+ * Will never fail with a [[WebSocketClosed ]].
96
+ *
97
+ * @param f The effect describing web socket interactions.
98
+ */
99
+ def either [T ](f : => F [T ]): F [Either [Option [WebSocketFrame .Close ], T ]] =
100
+ f.map(t => Right (t): Either [Option [WebSocketFrame .Close ], T ]).handleError { case WebSocketClosed (close) =>
101
+ (Left (close): Either [Option [WebSocketFrame .Close ], T ]).unit
102
+ }
103
+
67
104
private def receiveConcat [T , U <: WebSocketFrame .Data [T ]](
68
- receiveSingle : () => F [Either [ WebSocketFrame . Close , U ] ],
105
+ receiveSingle : () => F [U ],
69
106
combine : (T , T ) => T
70
- ): F [Either [ WebSocketFrame . Close , T ] ] = {
107
+ ): F [T ] = {
71
108
receiveSingle().flatMap {
72
- case Left (close) => (Left (close): Either [WebSocketFrame .Close , T ]).unit
73
- case Right (data) if ! data.finalFragment =>
74
- receiveConcat(receiveSingle, combine).flatMap {
75
- case Left (close) => (Left (close): Either [WebSocketFrame .Close , T ]).unit
76
- case Right (t) => (Right (combine(data.payload, t)): Either [WebSocketFrame .Close , T ]).unit
109
+ case data if ! data.finalFragment =>
110
+ receiveConcat(receiveSingle, combine).map { t =>
111
+ combine(data.payload, t)
77
112
}
78
- case Right (data) /* if data.finalFragment */ =>
79
- (Right (data.payload): Either [WebSocketFrame .Close , T ]).unit
113
+ case data /* if data.finalFragment */ => data.payload.unit
80
114
}
81
115
}
82
116
0 commit comments