Skip to main content

http_net/
message.rs

1//! Outgoing messages: the `Response` a server sends and the `Request` a
2//! client sends.
3//!
4//! Both builders validate as they go and remember the first problem instead
5//! of failing each call, so a message can be built in one chained
6//! expression; the problem surfaces as an error when the connection writes
7//! the message. Framing headers (`content-length`, `transfer-encoding`,
8//! `connection`, `upgrade`) are owned by the connection, which derives them
9//! from the body and the exchange; setting them by hand is refused, which
10//! makes conflicting framing on the way out impossible.
11
12use alloc::{borrow::Cow, format, string::String, vec::Vec};
13use core::fmt;
14
15use crate::{
16    error::HttpError,
17    head::{Method, check_target},
18    syntax::{check_field_value, is_token, trim_ows},
19};
20
21/// How an outgoing message's body is supplied.
22#[derive(Clone, Debug, PartialEq, Eq)]
23pub(crate) enum OutBody {
24    /// The whole body, sent with `content-length`.
25    Full(Cow<'static, [u8]>),
26    /// Streamed afterwards with exactly this many bytes.
27    Length(u64),
28    /// Streamed afterwards with chunked encoding.
29    Chunked,
30}
31
32/// Validates a field and appends `name: value\r\n`.
33fn push_field(
34    fields: &mut Vec<u8>,
35    name: &str,
36    value: &[u8],
37    reserved: &[&str],
38) -> Result<(), HttpError> {
39    if !is_token(name.as_bytes()) {
40        return Err(HttpError::InvalidHeaderName);
41    }
42    if reserved.iter().any(|r| r.eq_ignore_ascii_case(name)) {
43        return Err(HttpError::ReservedHeader);
44    }
45    if trim_ows(value).len() != value.len() {
46        // Surrounding whitespace would be stripped by the recipient; refusing
47        // it keeps what is sent equal to what is received.
48        return Err(HttpError::InvalidHeaderValue);
49    }
50    check_field_value(value)?;
51    fields.extend_from_slice(name.as_bytes());
52    fields.extend_from_slice(b": ");
53    fields.extend_from_slice(value);
54    fields.extend_from_slice(b"\r\n");
55    Ok(())
56}
57
58const RESPONSE_RESERVED: &[&str] = &[
59    "content-length",
60    "transfer-encoding",
61    "connection",
62    "upgrade",
63];
64const REQUEST_RESERVED: &[&str] = &[
65    "content-length",
66    "transfer-encoding",
67    "connection",
68    "upgrade",
69];
70
71/// A response for a [`ServerConn`](crate::ServerConn) to send.
72///
73/// The status line, `content-length` / `transfer-encoding`, and
74/// `connection` are written by the connection, which knows the request's
75/// method and version: a `HEAD` request gets the headers without the body,
76/// an HTTP/1.0 client never gets chunked encoding, and `connection: close`
77/// is added whenever the connection will close.
78///
79/// # Examples
80///
81/// ```
82/// use http_net::Response;
83///
84/// let ok = Response::text(200, "hello");
85/// assert_eq!(ok.status(), 200);
86///
87/// let asset = Response::new(200)
88///     .header("content-type", "text/css")
89///     .header("cache-control", "max-age=31536000, immutable")
90///     .body(&b"body { margin: 0 }"[..]);
91/// assert_eq!(asset.header_value("content-type"), Some(&b"text/css"[..]));
92/// ```
93#[derive(Clone, PartialEq, Eq)]
94pub struct Response {
95    status: u16,
96    fields: Vec<u8>,
97    pub(crate) body: OutBody,
98    pub(crate) close: bool,
99    pub(crate) upgrade: bool,
100    pub(crate) has_date: bool,
101    error: Option<HttpError>,
102}
103
104impl Response {
105    /// A response with this status, no extra headers, and an empty body.
106    ///
107    /// The status is validated when the response is written (`100..=599`,
108    /// and only `101` among the 1xx codes).
109    ///
110    /// # Examples
111    ///
112    /// ```
113    /// let r = http_net::Response::new(204);
114    /// assert_eq!(r.status(), 204);
115    /// ```
116    #[must_use]
117    pub fn new(status: u16) -> Self {
118        Self {
119            status,
120            fields: Vec::new(),
121            body: OutBody::Full(Cow::Borrowed(&[])),
122            close: false,
123            upgrade: false,
124            has_date: false,
125            error: None,
126        }
127    }
128
129    /// A `text/plain; charset=utf-8` response with `text` as its body.
130    ///
131    /// # Examples
132    ///
133    /// ```
134    /// let r = http_net::Response::text(404, "no such page");
135    /// assert_eq!(r.body_bytes(), Some(&b"no such page"[..]));
136    /// ```
137    #[must_use]
138    pub fn text(status: u16, text: &str) -> Self {
139        Self::new(status)
140            .header("content-type", "text/plain; charset=utf-8")
141            .body(text.as_bytes().to_vec())
142    }
143
144    /// A `101 Switching Protocols` response handing the connection to
145    /// `protocol` (for example `websocket`). The connection writes
146    /// `connection: upgrade` and `upgrade: <protocol>`; add any
147    /// protocol-specific headers (such as `sec-websocket-accept`) with
148    /// [`header`](Self::header).
149    ///
150    /// # Examples
151    ///
152    /// ```
153    /// let r = http_net::Response::switching_protocols("websocket")
154    ///     .header("sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=");
155    /// assert_eq!(r.status(), 101);
156    /// assert_eq!(r.header_value("upgrade"), Some(&b"websocket"[..]));
157    /// ```
158    #[must_use]
159    pub fn switching_protocols(protocol: &str) -> Self {
160        let mut r = Self::new(101);
161        r.upgrade = true;
162        if !is_token(protocol.as_bytes()) {
163            r.error = Some(HttpError::InvalidHeaderValue);
164            return r;
165        }
166        r.fields
167            .extend_from_slice(b"connection: upgrade\r\nupgrade: ");
168        r.fields.extend_from_slice(protocol.as_bytes());
169        r.fields.extend_from_slice(b"\r\n");
170        r
171    }
172
173    /// Adds a header field. The name must be a token and the value may not
174    /// contain control characters other than tab, nor leading or trailing
175    /// whitespace. Framing headers are refused with
176    /// [`HttpError::ReservedHeader`]; the first problem is reported when the
177    /// response is written.
178    ///
179    /// # Examples
180    ///
181    /// ```
182    /// use http_net::{HttpError, Response, ServerConn};
183    ///
184    /// let bad = Response::new(200).header("content-length", "5");
185    /// let mut conn = ServerConn::new();
186    /// let _ = conn.feed(b"GET / HTTP/1.1\r\nHost: a\r\n\r\n");
187    /// let _ = conn.poll();
188    /// assert_eq!(conn.respond(&bad, &mut Vec::new()), Err(HttpError::ReservedHeader));
189    /// ```
190    #[must_use]
191    pub fn header(mut self, name: &str, value: impl AsRef<[u8]>) -> Self {
192        if self.error.is_none() {
193            let value = value.as_ref();
194            if let Err(e) = push_field(&mut self.fields, name, value, RESPONSE_RESERVED) {
195                self.error = Some(e);
196            } else if name.eq_ignore_ascii_case("date") {
197                self.has_date = true;
198            }
199        }
200        self
201    }
202
203    /// Sets the whole body; it is sent with `content-length`. Static data
204    /// (an embedded asset) is borrowed rather than copied.
205    ///
206    /// # Examples
207    ///
208    /// ```
209    /// static PAGE: &[u8] = b"<!doctype html><title>x</title>";
210    /// let r = http_net::Response::new(200).body(PAGE);
211    /// assert_eq!(r.body_bytes(), Some(PAGE));
212    /// ```
213    #[must_use]
214    pub fn body(mut self, body: impl Into<Cow<'static, [u8]>>) -> Self {
215        self.body = OutBody::Full(body.into());
216        self
217    }
218
219    /// Streams the body with chunked encoding: after
220    /// [`ServerConn::respond`](crate::ServerConn::respond), send data with
221    /// [`ServerConn::send_body`](crate::ServerConn::send_body) and end it
222    /// with [`ServerConn::end_body`](crate::ServerConn::end_body). For an
223    /// HTTP/1.0 client the connection falls back to closing the connection
224    /// to end the body.
225    ///
226    /// # Examples
227    ///
228    /// ```
229    /// let r = http_net::Response::new(200).chunked();
230    /// assert_eq!(r.body_bytes(), None);
231    /// ```
232    #[must_use]
233    pub fn chunked(mut self) -> Self {
234        self.body = OutBody::Chunked;
235        self
236    }
237
238    /// Streams a body of exactly `len` bytes, sent with `content-length`;
239    /// sending more or fewer is [`HttpError::BodyLengthMismatch`].
240    ///
241    /// # Examples
242    ///
243    /// ```
244    /// let r = http_net::Response::new(200).content_length(1 << 20);
245    /// assert_eq!(r.body_bytes(), None);
246    /// ```
247    #[must_use]
248    pub fn content_length(mut self, len: u64) -> Self {
249        self.body = OutBody::Length(len);
250        self
251    }
252
253    /// Closes the connection after this response.
254    ///
255    /// # Examples
256    ///
257    /// ```
258    /// let r = http_net::Response::new(503).close();
259    /// assert!(r.closes());
260    /// ```
261    #[must_use]
262    pub fn close(mut self) -> Self {
263        self.close = true;
264        self
265    }
266
267    /// The status code.
268    ///
269    /// # Examples
270    ///
271    /// ```
272    /// assert_eq!(http_net::Response::new(418).status(), 418);
273    /// ```
274    #[must_use]
275    pub fn status(&self) -> u16 {
276        self.status
277    }
278
279    /// Whether [`close`](Self::close) was requested.
280    ///
281    /// # Examples
282    ///
283    /// ```
284    /// assert!(!http_net::Response::new(200).closes());
285    /// ```
286    #[must_use]
287    pub fn closes(&self) -> bool {
288        self.close
289    }
290
291    /// The full body, or `None` for a streamed one.
292    ///
293    /// # Examples
294    ///
295    /// ```
296    /// assert_eq!(http_net::Response::new(200).body_bytes(), Some(&[][..]));
297    /// ```
298    #[must_use]
299    pub fn body_bytes(&self) -> Option<&[u8]> {
300        match &self.body {
301            OutBody::Full(b) => Some(b),
302            _ => None,
303        }
304    }
305
306    /// The value of the first header named `name` added to this response
307    /// (ASCII case-insensitive), including the `upgrade` of
308    /// [`switching_protocols`](Self::switching_protocols).
309    ///
310    /// # Examples
311    ///
312    /// ```
313    /// let r = http_net::Response::new(200).header("x-id", "7");
314    /// assert_eq!(r.header_value("X-ID"), Some(&b"7"[..]));
315    /// ```
316    #[must_use]
317    pub fn header_value(&self, name: &str) -> Option<&[u8]> {
318        field_value(&self.fields, name)
319    }
320
321    pub(crate) fn error(&self) -> Option<HttpError> {
322        self.error
323    }
324
325    pub(crate) fn fields(&self) -> &[u8] {
326        &self.fields
327    }
328}
329
330impl fmt::Debug for Response {
331    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
332        f.debug_struct("Response")
333            .field("status", &self.status)
334            .field("fields", &String::from_utf8_lossy(&self.fields))
335            .field("body", &body_debug(&self.body))
336            .field("close", &self.close)
337            .field("error", &self.error)
338            .finish()
339    }
340}
341
342/// A request for a [`ClientConn`](crate::ClientConn) to send.
343///
344/// HTTP/1.1 requires a `Host` field; the connection refuses to send a
345/// request without one. The request line always says HTTP/1.1.
346///
347/// # Examples
348///
349/// ```
350/// use http_net::Request;
351///
352/// let get = Request::get("/index.html").header("host", "example.com");
353/// let post = Request::post("/api")
354///     .header("host", "example.com")
355///     .header("content-type", "application/json")
356///     .body(br#"{"ok":true}"#.to_vec());
357/// assert_eq!(get.method(), "GET");
358/// assert_eq!(post.target(), "/api");
359/// ```
360#[derive(Clone, PartialEq, Eq)]
361pub struct Request {
362    method: Cow<'static, str>,
363    pub(crate) method_kind: Method,
364    target: String,
365    fields: Vec<u8>,
366    pub(crate) body: OutBody,
367    pub(crate) close: bool,
368    pub(crate) upgrade: bool,
369    pub(crate) has_host: bool,
370    error: Option<HttpError>,
371}
372
373impl Request {
374    /// A request with `method` and `target`, validated when it is sent:
375    /// the method must be a token and the target must be in a form the
376    /// method allows (`/path?query`, an absolute URI, `host:port` for
377    /// `CONNECT`, or `*` for `OPTIONS`).
378    ///
379    /// # Examples
380    ///
381    /// ```
382    /// let r = http_net::Request::new("PURGE", "/cache/a");
383    /// assert_eq!(r.method(), "PURGE");
384    /// ```
385    #[must_use]
386    pub fn new(method: &str, target: &str) -> Self {
387        let method_kind = Method::from_bytes(method.as_bytes());
388        let mut error = None;
389        if !is_token(method.as_bytes()) {
390            error = Some(HttpError::InvalidMethod);
391        } else if let Err(e) = check_target(method_kind, target.as_bytes()) {
392            error = Some(e);
393        }
394        Self {
395            method: match method_kind.as_str() {
396                Some(s) => Cow::Borrowed(s),
397                None => Cow::Owned(method.into()),
398            },
399            method_kind,
400            target: target.into(),
401            fields: Vec::new(),
402            body: OutBody::Full(Cow::Borrowed(&[])),
403            close: false,
404            upgrade: false,
405            has_host: false,
406            error,
407        }
408    }
409
410    /// A `GET` request.
411    ///
412    /// # Examples
413    ///
414    /// ```
415    /// assert_eq!(http_net::Request::get("/").method(), "GET");
416    /// ```
417    #[must_use]
418    pub fn get(target: &str) -> Self {
419        Self::new("GET", target)
420    }
421
422    /// A `POST` request.
423    ///
424    /// # Examples
425    ///
426    /// ```
427    /// assert_eq!(http_net::Request::post("/").method(), "POST");
428    /// ```
429    #[must_use]
430    pub fn post(target: &str) -> Self {
431        Self::new("POST", target)
432    }
433
434    /// Adds a header field, validated as for [`Response::header`]. A second
435    /// `host` is refused with [`HttpError::InvalidHost`].
436    ///
437    /// # Examples
438    ///
439    /// ```
440    /// let r = http_net::Request::get("/").header("host", "a").header("accept", "*/*");
441    /// assert_eq!(r.header_value("accept"), Some(&b"*/*"[..]));
442    /// ```
443    #[must_use]
444    pub fn header(mut self, name: &str, value: impl AsRef<[u8]>) -> Self {
445        if self.error.is_none() {
446            let value = value.as_ref();
447            let is_host = name.eq_ignore_ascii_case("host");
448            if is_host && (self.has_host || !crate::syntax::is_host_value(value)) {
449                self.error = Some(HttpError::InvalidHost);
450            } else if let Err(e) = push_field(&mut self.fields, name, value, REQUEST_RESERVED) {
451                self.error = Some(e);
452            } else {
453                self.has_host |= is_host;
454            }
455        }
456        self
457    }
458
459    /// Sets the whole body, sent with `content-length`.
460    ///
461    /// # Examples
462    ///
463    /// ```
464    /// let r = http_net::Request::post("/").body(&b"x=1"[..]);
465    /// assert_eq!(r.body_bytes(), Some(&b"x=1"[..]));
466    /// ```
467    #[must_use]
468    pub fn body(mut self, body: impl Into<Cow<'static, [u8]>>) -> Self {
469        self.body = OutBody::Full(body.into());
470        self
471    }
472
473    /// Streams the body with chunked encoding through
474    /// [`ClientConn::send_body`](crate::ClientConn::send_body) and
475    /// [`ClientConn::end_body`](crate::ClientConn::end_body).
476    ///
477    /// # Examples
478    ///
479    /// ```
480    /// assert_eq!(http_net::Request::post("/").chunked().body_bytes(), None);
481    /// ```
482    #[must_use]
483    pub fn chunked(mut self) -> Self {
484        self.body = OutBody::Chunked;
485        self
486    }
487
488    /// Streams a body of exactly `len` bytes.
489    ///
490    /// # Examples
491    ///
492    /// ```
493    /// assert_eq!(http_net::Request::post("/").content_length(4).body_bytes(), None);
494    /// ```
495    #[must_use]
496    pub fn content_length(mut self, len: u64) -> Self {
497        self.body = OutBody::Length(len);
498        self
499    }
500
501    /// Asks the server to close the connection after its response.
502    ///
503    /// # Examples
504    ///
505    /// ```
506    /// let _ = http_net::Request::get("/").header("host", "a").close();
507    /// ```
508    #[must_use]
509    pub fn close(mut self) -> Self {
510        self.close = true;
511        self
512    }
513
514    /// Asks to switch the connection to `protocol` (writes
515    /// `connection: upgrade` and `upgrade: <protocol>`). A `101` answer
516    /// hands the connection over; see
517    /// [`ClientEvent::Upgraded`](crate::ClientEvent::Upgraded).
518    ///
519    /// # Examples
520    ///
521    /// ```
522    /// let r = http_net::Request::get("/rpc").header("host", "a").upgrade("websocket");
523    /// assert_eq!(r.header_value("upgrade"), Some(&b"websocket"[..]));
524    /// ```
525    #[must_use]
526    pub fn upgrade(mut self, protocol: &str) -> Self {
527        if self.error.is_none() {
528            if is_token(protocol.as_bytes()) {
529                self.upgrade = true;
530                self.fields
531                    .extend_from_slice(b"connection: upgrade\r\nupgrade: ");
532                self.fields.extend_from_slice(protocol.as_bytes());
533                self.fields.extend_from_slice(b"\r\n");
534            } else {
535                self.error = Some(HttpError::InvalidHeaderValue);
536            }
537        }
538        self
539    }
540
541    /// The method text.
542    ///
543    /// # Examples
544    ///
545    /// ```
546    /// assert_eq!(http_net::Request::new("OPTIONS", "*").method(), "OPTIONS");
547    /// ```
548    #[must_use]
549    pub fn method(&self) -> &str {
550        &self.method
551    }
552
553    /// The request target.
554    ///
555    /// # Examples
556    ///
557    /// ```
558    /// assert_eq!(http_net::Request::get("/a?b").target(), "/a?b");
559    /// ```
560    #[must_use]
561    pub fn target(&self) -> &str {
562        &self.target
563    }
564
565    /// The full body, or `None` for a streamed one.
566    ///
567    /// # Examples
568    ///
569    /// ```
570    /// assert_eq!(http_net::Request::get("/").body_bytes(), Some(&[][..]));
571    /// ```
572    #[must_use]
573    pub fn body_bytes(&self) -> Option<&[u8]> {
574        match &self.body {
575            OutBody::Full(b) => Some(b),
576            _ => None,
577        }
578    }
579
580    /// The value of the first header named `name` added to this request.
581    ///
582    /// # Examples
583    ///
584    /// ```
585    /// let r = http_net::Request::get("/").header("host", "a");
586    /// assert_eq!(r.header_value("HOST"), Some(&b"a"[..]));
587    /// ```
588    #[must_use]
589    pub fn header_value(&self, name: &str) -> Option<&[u8]> {
590        field_value(&self.fields, name)
591    }
592
593    pub(crate) fn error(&self) -> Option<HttpError> {
594        self.error
595    }
596
597    pub(crate) fn fields(&self) -> &[u8] {
598        &self.fields
599    }
600}
601
602impl fmt::Debug for Request {
603    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
604        f.debug_struct("Request")
605            .field("method", &self.method)
606            .field("target", &self.target)
607            .field("fields", &String::from_utf8_lossy(&self.fields))
608            .field("body", &body_debug(&self.body))
609            .field("error", &self.error)
610            .finish()
611    }
612}
613
614fn body_debug(body: &OutBody) -> String {
615    match body {
616        OutBody::Full(b) => format!("{} bytes", b.len()),
617        OutBody::Length(n) => format!("streamed, {n} bytes"),
618        OutBody::Chunked => "streamed, chunked".into(),
619    }
620}
621
622/// Looks a header up in serialized `name: value\r\n` lines.
623fn field_value<'a>(fields: &'a [u8], name: &str) -> Option<&'a [u8]> {
624    fields
625        .split(|&b| b == b'\n')
626        .filter_map(|line| {
627            let line = line.strip_suffix(b"\r")?;
628            let colon = line.iter().position(|&b| b == b':')?;
629            Some((&line[..colon], line.get(colon + 2..).unwrap_or(&[])))
630        })
631        .find(|(n, _)| n.eq_ignore_ascii_case(name.as_bytes()))
632        .map(|(_, v)| v)
633}
634
635/// The canonical reason phrase for a status code (RFC 9110 section 15).
636///
637/// # Examples
638///
639/// ```
640/// assert_eq!(http_net::reason_phrase(404), "Not Found");
641/// assert_eq!(http_net::reason_phrase(299), "");
642/// ```
643#[must_use]
644pub const fn reason_phrase(status: u16) -> &'static str {
645    match status {
646        100 => "Continue",
647        101 => "Switching Protocols",
648        200 => "OK",
649        201 => "Created",
650        202 => "Accepted",
651        203 => "Non-Authoritative Information",
652        204 => "No Content",
653        205 => "Reset Content",
654        206 => "Partial Content",
655        300 => "Multiple Choices",
656        301 => "Moved Permanently",
657        302 => "Found",
658        303 => "See Other",
659        304 => "Not Modified",
660        307 => "Temporary Redirect",
661        308 => "Permanent Redirect",
662        400 => "Bad Request",
663        401 => "Unauthorized",
664        403 => "Forbidden",
665        404 => "Not Found",
666        405 => "Method Not Allowed",
667        406 => "Not Acceptable",
668        408 => "Request Timeout",
669        409 => "Conflict",
670        410 => "Gone",
671        411 => "Length Required",
672        412 => "Precondition Failed",
673        413 => "Content Too Large",
674        414 => "URI Too Long",
675        415 => "Unsupported Media Type",
676        416 => "Range Not Satisfiable",
677        417 => "Expectation Failed",
678        421 => "Misdirected Request",
679        422 => "Unprocessable Content",
680        426 => "Upgrade Required",
681        428 => "Precondition Required",
682        429 => "Too Many Requests",
683        431 => "Request Header Fields Too Large",
684        500 => "Internal Server Error",
685        501 => "Not Implemented",
686        502 => "Bad Gateway",
687        503 => "Service Unavailable",
688        504 => "Gateway Timeout",
689        505 => "HTTP Version Not Supported",
690        _ => "",
691    }
692}
693
694#[cfg(test)]
695mod tests {
696    use super::*;
697
698    #[test]
699    fn test_response_refuses_reserved_and_invalid_headers() {
700        assert_eq!(
701            Response::new(200)
702                .header("Transfer-Encoding", "chunked")
703                .error(),
704            Some(HttpError::ReservedHeader)
705        );
706        assert_eq!(
707            Response::new(200).header("bad name", "x").error(),
708            Some(HttpError::InvalidHeaderName)
709        );
710        assert_eq!(
711            Response::new(200).header("x", "a\r\nInjected: 1").error(),
712            Some(HttpError::BareCr)
713        );
714        assert_eq!(
715            Response::new(200).header("x", " padded").error(),
716            Some(HttpError::InvalidHeaderValue)
717        );
718        // The first error sticks.
719        let r = Response::new(200)
720            .header("a b", "1")
721            .header("connection", "close");
722        assert_eq!(r.error(), Some(HttpError::InvalidHeaderName));
723    }
724
725    #[test]
726    fn test_request_validates_method_target_and_host() {
727        assert_eq!(
728            Request::new("G T", "/").error(),
729            Some(HttpError::InvalidMethod)
730        );
731        assert_eq!(
732            Request::get("no-slash").error(),
733            Some(HttpError::InvalidTarget)
734        );
735        assert_eq!(
736            Request::get("/")
737                .header("host", "a")
738                .header("Host", "b")
739                .error(),
740            Some(HttpError::InvalidHost)
741        );
742        assert_eq!(
743            Request::get("/").header("host", "a b").error(),
744            Some(HttpError::InvalidHost)
745        );
746        assert!(Request::get("/").header("host", "a").has_host);
747        assert_eq!(
748            Request::get("/").upgrade("a b").error(),
749            Some(HttpError::InvalidHeaderValue)
750        );
751    }
752
753    #[test]
754    fn test_field_lookup_and_debug() {
755        let r = Response::switching_protocols("websocket").header("x", "");
756        assert_eq!(r.header_value("connection"), Some(&b"upgrade"[..]));
757        assert_eq!(r.header_value("x"), Some(&b""[..]));
758        assert!(alloc::format!("{r:?}").contains("Response"));
759        assert_eq!(
760            Response::switching_protocols("a b").error(),
761            Some(HttpError::InvalidHeaderValue)
762        );
763    }
764}