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}