rupring/
response.rs

1/*!
2# About Reponse
3- Response is a struct that represents the HTTP response to be returned to the client.
4
5You can create a response like this:
6```rust
7#[rupring::Get(path = /)]
8pub fn hello(_request: rupring::Request) -> rupring::Response {
9    rupring::Response::new().text("Hello, World!".to_string())
10}
11```
12
13You can also return a json value like this:
14```rust
15#[derive(serde::Serialize)]
16struct User {
17    name: String,
18}
19
20#[rupring::Get(path = /user)]
21pub fn get_user(_request: rupring::Request) -> rupring::Response {
22    rupring::Response::new().json(User {
23        name: "John".to_string(),
24    })
25}
26```
27
28You can set the status code like this:
29```rust
30#[rupring::Get(path = /asdf)]
31pub fn not_found(_request: rupring::Request) -> rupring::Response {
32    rupring::Response::new().text("not found".to_string()).status(404)
33}
34```
35
36You can set the header like this:
37```rust
38#[rupring::Get(path = /)]
39pub fn hello(_request: rupring::Request) -> rupring::Response {
40    rupring::Response::new()
41        .text("Hello, World!".to_string())
42        .header("content-type", "text/plain".to_string())
43}
44```
45
46If you want, you can receive it as a parameter instead of creating the response directly.
47```rust
48#[rupring::Get(path = /)]
49pub fn hello(_request: rupring::Request, response: rupring::Response) -> rupring::Response {
50    response
51        .text("Hello, World!".to_string())
52        .header("content-type", "text/plain".to_string())
53}
54```
55This is especially useful when you need to inherit and use Response through middleware.
56
57If you want to redirect, you can use Response’s redirect method.
58```rust
59#[rupring::Get(path = /)]
60pub fn hello(_request: rupring::Request) -> rupring::Response {
61    rupring::Response::new().redirect("/hello")
62}
63```
64This method automatically sets status to 302 unless you set it to 300-308.
65*/
66
67use std::{
68    collections::HashMap, convert::Infallible, fmt::Debug, future::Future, panic::UnwindSafe,
69    pin::Pin, sync::Arc,
70};
71
72use crate::{
73    core::stream::StreamHandler,
74    header,
75    http::{
76        cookie::Cookie,
77        meme::{self, EVENT_STREAM},
78    },
79    HeaderName, Request,
80};
81use hyper::body::Bytes;
82
83pub(crate) type BoxedResponseBody = http_body_util::combinators::BoxBody<Bytes, Infallible>;
84
85#[derive(Debug, Clone)]
86pub enum ResponseData {
87    Immediate(Vec<u8>),
88    Stream(StreamResponse),
89}
90
91impl ResponseData {
92    pub fn into_bytes(self) -> Vec<u8> {
93        match self {
94            ResponseData::Immediate(bytes) => bytes,
95            _ => vec![],
96        }
97    }
98}
99
100impl Default for ResponseData {
101    fn default() -> Self {
102        ResponseData::Immediate(Vec::new())
103    }
104}
105
106type OnCloseFn = dyn Fn() -> Pin<Box<dyn Future<Output = ()> + Send>> + Send + Sync;
107
108type StreamFn = dyn Fn(StreamHandler) -> Pin<Box<dyn Future<Output = ()> + Send>> + Send + Sync;
109
110#[derive(Default, Clone)]
111pub struct StreamResponse {
112    pub on_close: Option<Arc<OnCloseFn>>,
113    pub stream: Option<Arc<StreamFn>>,
114}
115
116impl Debug for StreamResponse {
117    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118        f.debug_struct("StreamResponse")
119            .field("on_close", &self.on_close.is_some())
120            .finish()
121    }
122}
123
124impl ResponseData {
125    pub fn is_immediate(&self) -> bool {
126        matches!(self, ResponseData::Immediate(_))
127    }
128}
129
130#[derive(Debug, Clone, Default)]
131pub struct Response {
132    pub status: u16,
133    pub data: ResponseData,
134    pub headers: HashMap<HeaderName, Vec<String>>,
135    pub(crate) next: Option<Box<(Request, Response)>>,
136}
137
138impl UnwindSafe for Response {}
139
140impl Response {
141    /// Create a new response with status code 200, empty body and empty headers.
142    /// ```
143    /// let response = rupring::Response::new();
144    /// // ...
145    /// ```
146    pub fn new() -> Self {
147        Self {
148            status: 200,
149            data: Default::default(),
150            headers: Default::default(),
151            next: None,
152        }
153    }
154
155    /// Set it to receive the value of a serializable object and return a json value.
156    /// ```
157    /// #[derive(serde::Serialize)]
158    /// struct User {
159    ///    name: String,
160    /// }
161    ///
162    /// let response = rupring::Response::new().json(User {
163    ///    name: "John".to_string(),
164    /// });
165    /// assert_eq!(response.data.into_bytes(), r#"{"name":"John"}"#.to_string().into_bytes());
166    /// // ...
167    /// ```
168    pub fn json(mut self, body: impl serde::Serialize) -> Self {
169        self.headers.insert(
170            crate::HeaderName::from_static(header::CONTENT_TYPE),
171            vec![meme::JSON.into()],
172        );
173
174        let response_body = match serde_json::to_string(&body) {
175            Ok(body) => body,
176            Err(err) => {
177                self.status = 500;
178                format!("Error serializing response body: {:?}", err)
179            }
180        }
181        .into();
182
183        self.data = ResponseData::Immediate(response_body);
184
185        self
186    }
187
188    /// Set to return a text value.
189    /// ```
190    /// let response = rupring::Response::new().text("Hello World".to_string());
191    /// assert_eq!(response.data.into_bytes(), "Hello World".to_string().into_bytes());
192    pub fn text(mut self, body: impl ToString) -> Self {
193        self.headers.insert(
194            crate::HeaderName::from_static(header::CONTENT_TYPE),
195            vec![meme::TEXT.to_string()],
196        );
197
198        self.data = ResponseData::Immediate(body.to_string().into());
199
200        self
201    }
202
203    /// set to return a html value.
204    /// ```
205    /// let response = rupring::Response::new().html("<h1>Hello World</h1>".to_string());
206    /// assert_eq!(response.data.into_bytes(), "<h1>Hello World</h1>".to_string().into_bytes());
207    /// ```
208    pub fn html(mut self, body: impl ToString) -> Self {
209        self.headers.insert(
210            crate::HeaderName::from_static(header::CONTENT_TYPE),
211            vec![meme::HTML.to_string()],
212        );
213
214        self.data = ResponseData::Immediate(body.to_string().into());
215
216        self
217    }
218
219    /// Set `Content-Diposition` header to cause the browser to download the file.
220    /// ```
221    /// use rupring::HeaderName;
222    ///
223    /// let response = rupring::Response::new().download("hello.txt", "Hello World");
224    /// assert_eq!(response.headers.get(&HeaderName::from_static("content-disposition")).unwrap(), &vec!["attachment; filename=\"hello.txt\"".to_string()]);
225    /// assert_eq!(response.data.into_bytes(), "Hello World".to_string().into_bytes());
226    /// ```
227    pub fn download(mut self, filename: impl ToString, file: impl Into<Vec<u8>>) -> Self {
228        self.headers.insert(
229            crate::HeaderName::from_static(header::CONTENT_DISPOSITION),
230            vec![format!("attachment; filename=\"{}\"", filename.to_string())],
231        );
232
233        self.data = ResponseData::Immediate(file.into());
234
235        self
236    }
237
238    /// Set the cache control header for browser caching.
239    /// ```
240    /// use rupring::HeaderName;
241    ///
242    /// let response = rupring::Response::new().cache_control(rupring::http::cache::CacheControl {
243    ///   max_age: Some(3600),
244    ///  s_max_age: Some(3800),
245    ///  ..Default::default()
246    /// });
247    /// assert_eq!(response.headers.get(&HeaderName::from_static("cache-control")).unwrap(), &vec!["max-age=3600, s-maxage=3800".to_string()]);
248    /// ```
249    pub fn cache_control(mut self, cache_control: crate::http::cache::CacheControl) -> Self {
250        let mut cache_control_str = String::new();
251
252        if let Some(max_age) = cache_control.max_age {
253            cache_control_str.push_str(&format!("max-age={}", max_age));
254        }
255
256        if let Some(s_maxage) = cache_control.s_max_age {
257            if !cache_control_str.is_empty() {
258                cache_control_str.push_str(", ");
259            }
260
261            cache_control_str.push_str(&format!("s-maxage={}", s_maxage));
262        }
263
264        if cache_control.private {
265            if !cache_control_str.is_empty() {
266                cache_control_str.push_str(", ");
267            }
268
269            cache_control_str.push_str("private");
270        }
271
272        if cache_control.no_cache {
273            if !cache_control_str.is_empty() {
274                cache_control_str.push_str(", ");
275            }
276
277            cache_control_str.push_str("no-cache");
278        }
279
280        if cache_control.no_store {
281            if !cache_control_str.is_empty() {
282                cache_control_str.push_str(", ");
283            }
284
285            cache_control_str.push_str("no-store");
286        }
287
288        if cache_control.no_transform {
289            if !cache_control_str.is_empty() {
290                cache_control_str.push_str(", ");
291            }
292
293            cache_control_str.push_str("no-transform");
294        }
295
296        if cache_control.must_revalidate {
297            if !cache_control_str.is_empty() {
298                cache_control_str.push_str(", ");
299            }
300
301            cache_control_str.push_str("must-revalidate");
302        }
303
304        if cache_control.proxy_revalidate {
305            if !cache_control_str.is_empty() {
306                cache_control_str.push_str(", ");
307            }
308
309            cache_control_str.push_str("proxy-revalidate");
310        }
311
312        if cache_control.immutable {
313            if !cache_control_str.is_empty() {
314                cache_control_str.push_str(", ");
315            }
316
317            cache_control_str.push_str("immutable");
318        }
319
320        if let Some(stale_while_revalidate) = cache_control.stale_while_revalidate {
321            if !cache_control_str.is_empty() {
322                cache_control_str.push_str(", ");
323            }
324
325            cache_control_str.push_str(&format!(
326                "stale-while-revalidate={}",
327                stale_while_revalidate
328            ));
329        }
330
331        if let Some(stale_if_error) = cache_control.stale_if_error {
332            if !cache_control_str.is_empty() {
333                cache_control_str.push_str(", ");
334            }
335
336            cache_control_str.push_str(&format!("stale-if-error={}", stale_if_error));
337        }
338
339        self.headers.insert(
340            HeaderName::from_static(header::CACHE_CONTROL),
341            vec![cache_control_str],
342        );
343
344        self
345    }
346
347    /// Set status code.
348    /// ```
349    /// let response = rupring::Response::new().status(404);
350    /// assert_eq!(response.status, 404);
351    pub fn status(mut self, status: u16) -> Self {
352        self.status = status;
353        self
354    }
355
356    /// Set a header.
357    /// ```
358    /// use rupring::HeaderName;
359    /// let response = rupring::Response::new().header("content-type", "application/json".to_string());
360    /// assert_eq!(response.headers.get(&HeaderName::from_static("content-type")).unwrap(), &vec!["application/json".to_string()]);
361    pub fn header(mut self, name: &str, value: impl ToString) -> Self {
362        if let Ok(header_name) = HeaderName::from_bytes(name.as_bytes()) {
363            if let Some(values) = self.headers.get_mut(&header_name) {
364                // if content-type already exists, overwrite it.
365                if header_name.as_str() == header::CONTENT_TYPE {
366                    values.clear();
367                }
368
369                values.push(value.to_string());
370            } else {
371                self.headers.insert(header_name, vec![value.to_string()]);
372            }
373        }
374
375        self
376    }
377
378    /// overwrite headers.
379    /// ```
380    /// use rupring::HeaderName;
381    /// use std::collections::HashMap;
382    /// let mut headers = HashMap::new();
383    /// headers.insert(HeaderName::from_static("content-type"), vec!["application/json".to_string()]);
384    /// let response = rupring::Response::new().headers(headers);
385    /// assert_eq!(response.headers.get(&HeaderName::from_static("content-type")).unwrap(), &vec!["application/json".to_string()]);
386    pub fn headers(mut self, headers: HashMap<HeaderName, Vec<String>>) -> Self {
387        self.headers = headers;
388        self
389    }
390
391    /// redirect to url.
392    /// ```
393    /// use rupring::HeaderName;
394    /// use std::collections::HashMap;
395    /// let response = rupring::Response::new().redirect("https://naver.com");
396    /// assert_eq!(response.headers.get(&HeaderName::from_static("location")).unwrap(), &vec!["https://naver.com".to_string()]);
397    pub fn redirect(mut self, url: impl ToString) -> Self {
398        if self.status < 300 || self.status > 308 {
399            self.status = 302;
400        }
401
402        self.header(header::LOCATION, url)
403    }
404
405    /// add a cookie to the response.
406    /// ```
407    /// use rupring::HeaderName;
408    /// use rupring::http::cookie::Cookie;
409    /// let response = rupring::Response::new().add_cookie(Cookie::new("foo", "bar"));
410    /// assert_eq!(response.headers.get(&HeaderName::from_static("set-cookie")).unwrap(), &vec!["foo=bar".to_string()]);
411    /// ```
412    pub fn add_cookie(mut self, cookie: Cookie) -> Self {
413        let mut cookie_str = format!("{}={}", cookie.name, cookie.value);
414
415        if let Some(expires) = cookie.expires {
416            cookie_str.push_str(&format!("; Expires={}", expires));
417        }
418
419        if let Some(max_age) = cookie.max_age {
420            cookie_str.push_str(&format!("; Max-Age={}", max_age));
421        }
422
423        if let Some(domain) = cookie.domain {
424            cookie_str.push_str(&format!("; Domain={}", domain));
425        }
426
427        if let Some(path) = cookie.path {
428            cookie_str.push_str(&format!("; Path={}", path));
429        }
430
431        if let Some(secure) = cookie.secure {
432            cookie_str.push_str(&format!("; Secure={}", secure));
433        }
434
435        if let Some(http_only) = cookie.http_only {
436            cookie_str.push_str(&format!("; HttpOnly={}", http_only));
437        }
438
439        if let Some(same_site) = cookie.same_site {
440            cookie_str.push_str(&format!("; SameSite={}", same_site));
441        }
442
443        self.headers
444            .entry(HeaderName::from_static(header::SET_COOKIE))
445            .or_default()
446            .push(cookie_str);
447
448        self
449    }
450
451    /// Set a callback function for processing stream responses.
452    /// ```rust,ignore
453    /// rupring::Response::new()
454    ///     .header("content-type", "text/event-stream")
455    ///     .header("cache-control", "no-cache")
456    ///     .header("connection", "keep-alive")
457    ///     .header("access-control-allow-origin", "*")
458    ///     .stream(async move |stream_handler| {
459    ///         let mut count = 0;
460    ///         loop {
461    ///             if stream_handler.is_closed() {
462    ///                 println!("Client disconnected, stopping SSE");
463    ///                 break;
464    ///         }
465    ///
466    ///         let message = format!("data: Message number {}\n\n", count);
467    ///         println!("Sending: {}", message.trim());
468    ///         if let Err(e) = stream_handler.send(message.as_bytes()).await {
469    ///             eprintln!("Error sending message: {}", e);
470    ///         }
471    ///         count += 1;
472    ///         tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
473    ///     })
474    /// ```
475    pub fn stream<F, Fut>(mut self, stream_fn: F) -> Self
476    where
477        F: Fn(StreamHandler) -> Fut + Send + Sync + 'static,
478        Fut: Future<Output = ()> + Send + 'static,
479    {
480        self.data = ResponseData::Stream(StreamResponse {
481            stream: Some(Arc::new(move |handler: StreamHandler| {
482                Box::pin(stream_fn(handler))
483            })),
484            on_close: None,
485        });
486
487        self
488    }
489
490    /// Set a SSE callback function. a shortcut of `Response::stream`.
491    /// ```rust,ignore
492    /// rupring::Response::new()
493    ///    .sse_stream(async move |stream_handler| {
494    ///       let mut count = 0;
495    ///       loop {
496    ///          if stream_handler.is_closed() {
497    ///              println!("Client disconnected, stopping SSE");
498    ///              break;
499    ///          }
500    ///       }
501    ///       
502    ///       let event = rupring::http::sse::Event::new()
503    ///           .event("custom-event")
504    ///           .data(format!("This is custom event number {}", count));
505    ///
506    ///       if let Err(e) = stream_handler.send_event(event).await {
507    ///           eprintln!("Error sending message: {}", e);
508    ///       }
509    ///       count += 1;
510    ///       tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
511    ///   })
512    /// ```
513    pub fn sse_stream<F, Fut>(mut self, stream_fn: F) -> Self
514    where
515        F: Fn(StreamHandler) -> Fut + Send + Sync + 'static,
516        Fut: Future<Output = ()> + Send + 'static,
517    {
518        self.headers.insert(
519            crate::HeaderName::from_static(header::CONTENT_TYPE),
520            vec![EVENT_STREAM.into()],
521        );
522
523        // Cache-Control: no-cache is required for SSE
524        if !self
525            .headers
526            .contains_key(&crate::HeaderName::from_static(header::CACHE_CONTROL))
527        {
528            self.headers.insert(
529                crate::HeaderName::from_static(header::CACHE_CONTROL),
530                vec!["no-cache".to_string()],
531            );
532        }
533
534        // Connection: keep-alive is required for SSE
535        if !self
536            .headers
537            .contains_key(&crate::HeaderName::from_static(header::CONNECTION))
538        {
539            self.headers.insert(
540                crate::HeaderName::from_static(header::CONNECTION),
541                vec!["keep-alive".to_string()],
542            );
543        }
544
545        // Keep-Alive: timeout=... is recommended for SSE
546        if !self
547            .headers
548            .contains_key(&crate::HeaderName::from_static(header::KEEP_ALIVE))
549        {
550            self.headers.insert(
551                crate::HeaderName::from_static(header::KEEP_ALIVE),
552                vec!["timeout=15".to_string()],
553            );
554        }
555
556        // Access-Control-Allow-Origin: * is required for SSE
557        if !self.headers.contains_key(&crate::HeaderName::from_static(
558            header::ACCESS_CONTROL_ALLOW_ORIGIN,
559        )) {
560            self.headers.insert(
561                crate::HeaderName::from_static(header::ACCESS_CONTROL_ALLOW_ORIGIN),
562                vec!["*".to_string()],
563            );
564        }
565
566        self.stream(stream_fn)
567    }
568}
569
570pub trait IntoResponse {
571    fn into_response(self) -> Response;
572}