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}