Skip to main content

rustlavel_http/
response.rs

1use crate::cookie::Cookie;
2use crate::headers::Headers;
3use crate::status::Status;
4use rustlavel_core::{Error, Json};
5
6/// An outgoing response.
7#[derive(Clone)]
8pub struct Response {
9    pub status: Status,
10    pub headers: Headers,
11    pub body: Vec<u8>,
12    /// Set when the handler answered `101` and wants the socket.
13    pub(crate) upgrade: Option<crate::upgrade::Upgrader>,
14}
15
16impl std::fmt::Debug for Response {
17    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18        f.debug_struct("Response")
19            .field("status", &self.status)
20            .field("headers", &self.headers)
21            .field("body_len", &self.body.len())
22            .field("upgrades", &self.upgrade.is_some())
23            .finish()
24    }
25}
26
27impl Response {
28    pub fn new(status: Status) -> Self {
29        Response { status, headers: Headers::new(), body: Vec::new(), upgrade: None }
30    }
31
32    /// Answer `101` and hand the connection to another protocol.
33    ///
34    /// The caller sets whatever headers the new protocol's handshake requires;
35    /// this only arranges for the socket to be handed over afterwards.
36    pub fn upgrading(mut self, upgrade: impl crate::upgrade::Upgrade) -> Self {
37        self.status = Status(101);
38        self.upgrade = Some(std::sync::Arc::new(upgrade));
39        self
40    }
41
42    /// Keep the status and headers, and hand the connection over once they are
43    /// written — for a body that is produced over time rather than known now.
44    ///
45    /// This is what an event stream is: a `200` whose body is events written
46    /// as they happen, for as long as the client stays. [`upgrading`] is the
47    /// same mechanism with a `101`, for a protocol that stops being HTTP.
48    ///
49    /// [`upgrading`]: Response::upgrading
50    pub fn streaming(mut self, body: impl crate::upgrade::Upgrade) -> Self {
51        self.upgrade = Some(std::sync::Arc::new(body));
52        self
53    }
54
55    /// Whether this response takes the connection over.
56    pub fn upgrades(&self) -> bool {
57        self.upgrade.is_some()
58    }
59
60    /// Take the upgrade out of a dispatched response.
61    ///
62    /// Public so a test can drive an upgrade through the router rather than
63    /// only over a real socket.
64    pub fn take_upgrade(&mut self) -> Option<crate::upgrade::Upgrader> {
65        self.upgrade.take()
66    }
67
68    pub fn ok() -> Self {
69        Response::new(Status::OK)
70    }
71
72    pub fn no_content() -> Self {
73        Response::new(Status::NO_CONTENT)
74    }
75
76    pub fn not_found() -> Self {
77        Response::new(Status::NOT_FOUND).with_html("<h1>404 Not Found</h1>")
78    }
79
80    /// A `302` redirect. Use [`Response::see_other`] after a form submission so
81    /// the browser switches to GET.
82    pub fn redirect(location: impl Into<String>) -> Self {
83        Response::new(Status::FOUND).with_header("location", location)
84    }
85
86    pub fn see_other(location: impl Into<String>) -> Self {
87        Response::new(Status::SEE_OTHER).with_header("location", location)
88    }
89
90    pub fn json(value: impl Into<Json>) -> Self {
91        Response::ok().with_json(value)
92    }
93
94    pub fn html(body: impl Into<String>) -> Self {
95        Response::ok().with_html(body)
96    }
97
98    pub fn text(body: impl Into<String>) -> Self {
99        Response::ok().with_text(body)
100    }
101
102    pub fn with_status(mut self, status: impl Into<Status>) -> Self {
103        self.status = status.into();
104        self
105    }
106
107    pub fn with_header(mut self, name: &str, value: impl Into<String>) -> Self {
108        self.headers.set(name, value);
109        self
110    }
111
112    pub fn with_body(mut self, body: impl Into<Vec<u8>>) -> Self {
113        self.body = body.into();
114        self
115    }
116
117    pub fn with_json(self, value: impl Into<Json>) -> Self {
118        self.with_header("content-type", "application/json; charset=utf-8")
119            .with_body(value.into().to_string())
120    }
121
122    pub fn with_html(self, body: impl Into<String>) -> Self {
123        self.with_header("content-type", "text/html; charset=utf-8").with_body(body.into())
124    }
125
126    pub fn with_text(self, body: impl Into<String>) -> Self {
127        self.with_header("content-type", "text/plain; charset=utf-8").with_body(body.into())
128    }
129
130    pub fn with_cookie(mut self, cookie: Cookie) -> Self {
131        self.headers.append("set-cookie", cookie.to_header());
132        self
133    }
134
135    /// Expire a cookie on the client.
136    pub fn without_cookie(self, name: &str) -> Self {
137        self.with_cookie(Cookie::forget(name))
138    }
139
140    pub fn body_string(&self) -> String {
141        String::from_utf8_lossy(&self.body).into_owned()
142    }
143
144    /// Serialize into an HTTP/1.1 wire response.
145    ///
146    /// `include_body` is false for HEAD requests, which must report the length
147    /// they would have sent while sending nothing.
148    pub fn to_bytes(&self, include_body: bool) -> Vec<u8> {
149        let bodyless = self.status.is_bodyless();
150        let body: &[u8] = if bodyless { &[] } else { &self.body };
151
152        let mut head = format!("HTTP/1.1 {} {}\r\n", self.status.code(), self.status.reason());
153        for (name, value) in self.headers.iter() {
154            if name == "content-length" {
155                continue;
156            }
157            head.push_str(&format!("{name}: {value}\r\n"));
158        }
159        // No length for a response whose body is written by whatever takes
160        // the socket over: the server does not know it yet, and `content-length:
161        // 0` would tell the client the body is complete before the first byte
162        // of an event stream arrives.
163        if !bodyless && self.upgrade.is_none() {
164            head.push_str(&format!("content-length: {}\r\n", body.len()));
165        }
166        head.push_str("\r\n");
167
168        let mut out = head.into_bytes();
169        if include_body && !bodyless {
170            out.extend_from_slice(body);
171        }
172        out
173    }
174}
175
176impl Default for Response {
177    fn default() -> Self {
178        Response::ok()
179    }
180}
181
182/// Anything a handler is allowed to return.
183///
184/// This is what lets a handler return a `&str`, a `Json`, a `Result`, or a full
185/// `Response` without wrapping it by hand.
186pub trait IntoResponse {
187    fn into_response(self) -> Response;
188}
189
190impl IntoResponse for Response {
191    fn into_response(self) -> Response {
192        self
193    }
194}
195
196impl IntoResponse for Status {
197    fn into_response(self) -> Response {
198        Response::new(self)
199    }
200}
201
202impl IntoResponse for String {
203    fn into_response(self) -> Response {
204        Response::html(self)
205    }
206}
207
208impl IntoResponse for &str {
209    fn into_response(self) -> Response {
210        Response::html(self.to_string())
211    }
212}
213
214impl IntoResponse for Json {
215    fn into_response(self) -> Response {
216        Response::json(self)
217    }
218}
219
220impl IntoResponse for () {
221    fn into_response(self) -> Response {
222        Response::no_content()
223    }
224}
225
226impl<T: IntoResponse> IntoResponse for (u16, T) {
227    fn into_response(self) -> Response {
228        let (status, inner) = self;
229        inner.into_response().with_status(status)
230    }
231}
232
233/// `None` becomes a 404, which makes `req.param_as::<i64>("id")?` style
234/// lookups read naturally in a handler.
235impl<T: IntoResponse> IntoResponse for Option<T> {
236    fn into_response(self) -> Response {
237        match self {
238            Some(value) => value.into_response(),
239            None => Response::not_found(),
240        }
241    }
242}
243
244/// A framework error renders as the development error page, or a generic 500
245/// in production.
246impl IntoResponse for Error {
247    fn into_response(self) -> Response {
248        crate::error_page::response_for(&self)
249    }
250}
251
252/// `?` in a handler works for any error that knows how to become a response.
253///
254/// This is what lets validation return a 422 with its own JSON body while a
255/// framework error still reaches the error page: each error type decides how it
256/// is rendered, rather than everything collapsing into a 500.
257impl<T: IntoResponse, E: IntoResponse> IntoResponse for Result<T, E> {
258    fn into_response(self) -> Response {
259        match self {
260            Ok(value) => value.into_response(),
261            Err(error) => error.into_response(),
262        }
263    }
264}
265
266/// An I/O failure in a handler is a server error; carried separately because
267/// `std::io::Error` cannot implement a foreign trait here.
268impl IntoResponse for std::io::Error {
269    fn into_response(self) -> Response {
270        crate::error_page::response_for(&Error::Io(self))
271    }
272}
273
274#[cfg(test)]
275mod tests {
276    use super::*;
277
278    #[test]
279    fn serializes_a_wire_response() {
280        let response = Response::json(Json::object([("ok", true.into())]));
281        let wire = String::from_utf8(response.to_bytes(true)).unwrap();
282
283        assert!(wire.starts_with("HTTP/1.1 200 OK\r\n"));
284        assert!(wire.contains("content-type: application/json; charset=utf-8\r\n"));
285        assert!(wire.contains("content-length: 11\r\n"));
286        assert!(wire.ends_with("\r\n\r\n{\"ok\":true}"));
287    }
288
289    #[test]
290    fn head_requests_keep_the_length_but_drop_the_body() {
291        let response = Response::text("hello");
292        let wire = String::from_utf8(response.to_bytes(false)).unwrap();
293
294        assert!(wire.contains("content-length: 5\r\n"));
295        assert!(wire.ends_with("\r\n\r\n"));
296    }
297
298    #[test]
299    fn bodyless_statuses_send_no_content_length() {
300        let wire = String::from_utf8(Response::no_content().to_bytes(true)).unwrap();
301        assert!(!wire.contains("content-length"));
302    }
303
304    #[test]
305    fn repeated_cookies_each_get_their_own_header() {
306        let response = Response::ok()
307            .with_cookie(Cookie::new("a", "1"))
308            .with_cookie(Cookie::new("b", "2"));
309        let wire = String::from_utf8(response.to_bytes(true)).unwrap();
310
311        assert_eq!(wire.matches("set-cookie:").count(), 2);
312    }
313
314    #[test]
315    fn common_return_types_convert() {
316        assert_eq!("hi".into_response().status, Status::OK);
317        assert_eq!(().into_response().status, Status::NO_CONTENT);
318        assert_eq!(Option::<String>::None.into_response().status, Status::NOT_FOUND);
319        assert_eq!((201, "made").into_response().status, Status::CREATED);
320
321        let failed: Result<&str, Error> = Err(Error::msg("boom"));
322        assert!(failed.into_response().status.is_error());
323    }
324}