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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
//! What a websocket session handler is, and what happens when one fails.
//!
//! A session handler is the callback [`WebSocketUpgrade::on_upgrade`](super::WebSocketUpgrade)
//! and [`.ws()`](crate::routing::CreateRouteNode::ws) run once the handshake succeeded. It owns
//! the socket for the lifetime of the connection, which is why its failures cannot travel the
//! normal response path: by the time it runs, the `101` response has already been sent.
//!
//! So a session reports failure by returning one. Returning `()` still means "the session ended";
//! returning [`Result`] lets the handler use `?` and hand the framework whatever ended it. The
//! framework logs a returned error with its whole `source()` chain and tries to close the
//! connection with [`INTERNAL_ERROR`], so a peer learns the session died rather than watching the
//! socket go silent.
use Future;
use Error;
use ;
/// Close code `1011`: the server hit a condition that stopped it fulfilling the request.
///
/// This is the code the framework sends on behalf of a session handler that returned an error,
/// mirroring what a `500` does for an ordinary handler.
pub const INTERNAL_ERROR: u16 = 1011;
/// The close frame sent when a session handler returns an error.
///
/// The reason is deliberately generic for the same rationale as the `5xx` response body: the
/// detail belongs in the server's logs, not on the wire.
/// What a websocket session handler is allowed to return.
///
/// Implemented for `()` — the session ended, and said nothing about how — and for
/// `Result<(), E>` where `E` converts into [`Error`], which covers
/// [`WebSocketError`](super::WebSocketError), every [`HttpError`](skyzen_core::error::HttpError)
/// and [`Error`] itself. The trait is sealed: it exists to name those two shapes, not to be
/// extended.
/// `Send`, except on `wasm32`.
///
/// A session handler on a multi-threaded native runtime has to be `Send`: the executor may poll it
/// on any thread. Nothing about a WebAssembly isolate makes that true or useful — there is no
/// second thread — and requiring it there would reject every real edge session, because the socket
/// itself is built from `Rc`s and JS handles that no `Send` future can hold across an `await`.
///
/// The relaxation stops at the builder. What the router stores still has to satisfy
/// `http_kit::Endpoint`'s unconditional `Send`, which is why the session is carried across that
/// boundary in a cell whose `unsafe impl Send` is justified by the isolate being single-threaded.
/// `Send`, except on `wasm32` — where it asks for nothing, because a Worker isolate has no second
/// thread to send anything to. See the native definition for the full rationale.
/// `Sync`, except on `wasm32`. The counterpart of [`MaybeSend`], for the session callback itself:
/// the native upgrade path shares it across threads, a Worker isolate never can.
/// `Sync`, except on `wasm32` — where it asks for nothing. See [`MaybeSend`].
/// Carries a session across the router's `Send + Sync` bounds.
///
/// Native sessions already are `Send + Sync`, so this is a plain newtype there.
;
/// Carries a session across the router's `Send + Sync` bounds.
///
/// A wasm session is routinely `!Send`: it holds the socket's `Rc<RefCell<..>>` event closures and
/// raw JS handles across every `await`. The bound it has to cross is not the framework's to relax
/// — `http_kit::Endpoint` requires `Send` unconditionally, and the router's type erasure boxes
/// every endpoint future as `dyn Send + Future` — so the session is carried through it in this
/// cell instead of being rejected at compile time on the one target the framework exists for.
;
// SAFETY: a `wasm32` Worker (and every other WinterCG isolate Skyzen targets) is single-threaded:
// there is no second thread for the value to be sent to or shared with, so the `Send`/`Sync` these
// bounds ask for cannot be observed. The cell is private and never leaves this crate, so the only
// values it ever carries are session callbacks the router immediately runs on the same isolate.
unsafe
// SAFETY: see the `Send` implementation above.
unsafe
/// Build the request handler `.ws()` registers: extract the upgrade, then run `session` on the
/// socket it produces.
///
/// The returned closure is what the router type-erases, so it — not the session — is what must
/// satisfy [`Handler`](crate::handler::Handler)'s `Send + Sync + Clone + 'static`.
+ Clone
+ Send
+ Sync
+ 'static
where
F: Fn + Clone + MaybeSend + MaybeSync + 'static,
Fut: Future + MaybeSend + 'static,
Output: IntoWebSocketOutcome + 'static,
/// Compile-time proof that [`.ws()`](crate::routing::CreateRouteNode::ws) accepts the sessions
/// this target actually produces: ones holding an [`Rc`](std::rc::Rc) — and, in real code, the
/// socket's own JS handles — across every `await`.
///
/// It lives in the library rather than in `#[cfg(test)]` because the wasm CI leg is a plain
/// `cargo check` with no `--all-targets`: a test module would never be compiled, and the bound
/// this guards against regressing is exactly the one that only fails on this target. Nothing calls
/// it; building it is the whole point.