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
//! What the edge says when it will not forward.
//!
//! Pure: each of these is a complete HTTP/1.1 response as bytes, built here
//! and never obtained from anywhere else. That is the property worth having
//! a module for — a refusal produced locally cannot be confused with
//! something the backend said.
//!
//! For [`unauthorized`] and [`bad_request`] the stronger statement holds:
//! at the point they are written the backend has not been contacted at all.
//! The three written *after* contact are the deliberate exceptions, and
//! each has to be. [`bad_gateway`] and [`backend_unreachable`] report what
//! happened at the backend, so neither can be produced before reaching for
//! one. [`incomplete_request`] reports a request body that stopped short,
//! and a body can only stop short after its head has already gone upstream
//! — which is precisely why it is not [`bad_request`], whose promise it
//! would break. [`tunnel_unavailable`] is the odd one out in a different
//! way: it is written by the *connect* side, which never had a backend to
//! reach. All six are still synthesized here rather than relayed, which is
//! what keeps them distinguishable from a status the backend itself sent.
//!
//! Every one closes the connection. A stream carries exactly one exchange
//! and a refused exchange is over.
//!
//! # Why there are three 502s
//!
//! There used to be one, and its sentence — "the backend sent a response
//! this tunnel could not read" — was written at four call sites of which it
//! described one and a half. A backend that was never reached sent no
//! response; a tunnel with no peer has no backend at either end. Both said
//! so anyway, which sends the reader to the wrong machine: to the model
//! server, when the model server is fine and the far laptop is asleep.
//!
//! The status stays 502 for all three and [`Outcome`](crate::outcome::Outcome) stays
//! one variant, because the client's *recovery* is the same in each case
//! and that is what a status code is for. What differs is the sentence a
//! person reads, and the `code` a program matches on.
/// One refusal, in the shape they all share.
///
/// A status line, a JSON object naming a code a program can match and a
/// message a person can read, a `Content-Length` that matches what was
/// actually written, and `Connection: close`. Built in one place because
/// six copies of that shape is six chances for a declared length to
/// disagree with its body — which is the one mistake in this module that
/// hangs a client rather than merely annoying it.
///
/// `message` and `code` are literals at every call site, so nothing here
/// escapes them: there is no caller-supplied text in this module and the
/// tests below assert every refusal is still parseable. Interpolating the
/// backend's authority was considered and dropped — the client is on the
/// other machine and cannot act on an address it does not own, while the
/// serve side already logs it.
/// The 401 the edge writes itself.
///
/// Synthesized locally and never forwarded: the backend has not been
/// contacted at the point this is produced, so there is no upstream
/// response for it to be confused with.
pub
/// The 400 for a head this edge refuses to interpret.
pub
/// The 502 for a backend that was reached and gave nothing usable back.
///
/// Three sub-cases share it, and the wording has to cover all three because
/// this edge cannot always tell them apart: an answer framed ambiguously, a
/// head that would not parse, and a backend that said nothing at all before
/// the grace expired. The old sentence — "the backend *sent* a response
/// this tunnel could not read" — was true of the first two and a claim
/// about an event that did not happen for the third, which is the same
/// mistake `backend_unreachable` exists to stop making.
///
/// Distinct from [`bad_request`] on purpose: the client did nothing wrong,
/// and reporting a gateway failure as a client error would send whoever is
/// debugging it to the wrong side of the tunnel.
pub
/// The 502 for a backend that was never reached at all.
///
/// The most likely failure on a first run, and the one the old wording was
/// worst for: the tunnel is up, the serving side is running, and the model
/// server behind it is not — stopped, on a different port, or bound
/// somewhere this listener may not dial. Saying "the backend sent a
/// response" there is not merely imprecise, it is a claim about an event
/// that did not happen, and it points at the one component that is working.
pub
/// The 502 the *connect* side writes when it has no tunnel to use.
///
/// Nothing about this one involves a backend: the peer is away, or the
/// connection to it has died and `keep_connected` is looking for a
/// replacement. It is the only refusal in this module produced on the
/// machine that does not have the models, which is exactly why it must not
/// borrow either of the sentences above — the reader is sitting at the
/// client, and what they need to know is that the *other* machine is not
/// there.
pub
/// The 400 for a request body that never arrived whole.
///
/// A client error, and deliberately not [`bad_request`]: that one promises
/// the backend was never contacted, and this one can only be written after
/// the head has gone upstream. The distinction is not pedantry — it is the
/// difference between "your request was malformed" and "your request was
/// fine and your upload stopped", and only the second tells the operator to
/// look at the network rather than at their JSON.
///
/// 400 rather than 408: no timeout expired. Rather than 499, which is not a
/// status. The client is often gone by now — a truncated upload usually
/// means it hung up — but a body this edge could not go on *reading*, an
/// unreadable chunk-size line, leaves it there and owed an answer.
pub