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
//! The WebSocket seam: a message channel, and the thing that opens one.
//!
//! # Why this is not a method on [`Transport`](super::Transport)
//!
//! The same reasoning `hclient-tls-quic`'s `QuicTlsConnect` rests on, and
//! `hclient_rt::TcpAdoptStd` before it: the intersection between "send a
//! request, read a response" and "exchange messages until somebody closes"
//! is empty, and an adapter between them would type-check *with an empty
//! body*. A `Transport::websocket` returning `Err(Unsupported)` would push
//! the same failure from compile time to run time, on a feature a caller
//! either has or has not.
//!
//! **So the seam expresses itself by being implemented.** A backend that
//! can do WebSocket implements [`WebSocketConnect`]; one that cannot does
//! not, and asking it for a WebSocket does not compile. There is
//! deliberately no capability field to read: a runtime `Unsupported`
//! would move the same failure from compile time to run time.
//!
//! # Why message oriented, rather than "hand back the socket"
//!
//! A byte-stream seam is implementable by exactly one of this project's
//! four backends, and the three it excludes include the browser — the
//! target whose inclusion is the whole claim. `WebSocket` in a browser is
//! a wholly separate global reached from no `fetch`-shaped API, it hands
//! back no bytes, and on Apple platforms `NSURLSessionWebSocketTask` is
//! message-framed too. So the h1 upgrade is an implementation detail
//! *underneath* this seam on native, not the seam.
//!
//! # What is deliberately not here
//!
//! - **`Ping` and `Pong` are not [`Message`] variants.** RFC 6455 §5.5.2
//! makes answering a ping the *endpoint's* duty, not the caller's, and
//! `hclient-tungstenite` discharges it without telling anybody
//! (`crates/hclient-tungstenite/tests/websocket.rs` watches the pong
//! leave from the server's side of the wire). A caller-visible `Ping`
//! would be
//! a variant the browser can neither send nor ever receive, which is the
//! capability lie this workspace has caught four times. If a caller
//! decision ever turns on one, adding the variant is a compile error at
//! every backend — which is the right way round, and why this enum is
//! not `#[non_exhaustive]`.
//! - **Permessage-deflate and subprotocol negotiation** are not
//! supported. A subprotocol *can* be asked for, because the request
//! carries headers; nothing here checks what came back.
use crateError;
use Stream;
use Sink;
use Future;
/// One WebSocket message, in the vocabulary every backend can speak.
///
/// Not `#[non_exhaustive]`: see the module doc. Nothing here is published,
/// so a new variant costs a rebase inside this workspace and a compile
/// error is what a backend author should get.
/// The close code and reason of a [`Message::Close`].
///
/// `u16` rather than an enum of the RFC 6455 §7.4 codes: this seam does
/// not interpret them, and an enum would have to decide what a reserved
/// or application-defined code means, and this seam has not decided.
/// An open WebSocket: messages out, messages in.
///
/// `Stream` for the receiving half and `Sink` for the sending half, on one
/// value rather than a split pair, because splitting is something
/// `futures_util::StreamExt::split` already does for any `Stream + Sink`
/// and a seam that pre-split would take that choice away from the caller.
///
/// # The error type is concrete, unlike [`Transport::Error`](crate::unversioned::Transport::Error)
///
/// [`Transport`](super::Transport) carries `type Error` and a `to_error`
/// hook so a backend whose error is genuinely `!Send` can still implement
/// it. That escape hatch has no subject here: it exists so a backend can
/// keep its own *typed source* while `Client` classifies, and there is no
/// `Client` between this trait and its caller — whatever a backend would
/// put in its own error, it can put in [`Error`]'s source, which is where
/// a caller would read it from anyway. One concrete type also keeps
/// `Stream::Item`'s error and `Sink::Error` the same type without an
/// associated-type equality the caller has to spell out.
///
/// # Ending
///
/// The `Stream` ends (`None`) when the connection is finished: after the
/// peer's [`Message::Close`] has been delivered, or when the connection
/// broke and the error has already been reported. A `Stream` that has
/// ended stays ended.
/// A backend that can open a WebSocket.
///
/// Implemented either by a transport itself (`hclient_fetch::Fetch`, where
/// the platform hands back messages) or by a connector over one
/// (`hclient_tungstenite::Tungstenite`, where it hands back bytes and the
/// framing is a crate of its own).
/// Either way a WebSocket opened this way inherits everything the
/// transport already knows: its runtime, its TLS configuration, its
/// resolver.