1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
use std::collections::BTreeMap;
use std::fmt::Display;
use std::io::{self, Write};
/// A response from a FastCGI server.
#[derive(Debug)]
pub struct Response {
headers: BTreeMap<String, String>,
body: Vec<u8>,
error: Option<Box<dyn std::error::Error>>,
}
impl Default for Response {
/// Creates an empty 200 OK response
fn default() -> Self {
let default_headers = BTreeMap::from_iter([
("Status".into(), "200".into()),
("Content-Type".into(), "text/plain".into()),
]);
Self {
headers: default_headers,
body: vec![],
error: None,
}
}
}
impl Response {
/// Create a new FastCGI response.
///
/// Defaults to an empty 200 OK
pub fn new() -> Self {
Self::default()
}
/// Sets the `Content-Type` header
pub fn set_content_type<S: Display>(mut self, content_type: S) -> Self {
self.headers
.insert("Content-Type".into(), content_type.to_string());
self
}
/// Sets the `Location` header
pub fn set_location<S: Display>(mut self, location: S) -> Self {
self.headers.insert("Location".into(), location.to_string());
self
}
/// Sets the status of the response
pub fn set_status(mut self, code: u16) -> Self {
self.headers.insert("Status".into(), code.to_string());
self
}
/// Sets the content of the body
pub fn set_body<S: Display>(mut self, body: S) -> Self {
self.body = body.to_string().into_bytes();
self
}
/// Sets the content of the body using raw bytes
pub fn set_body_raw(mut self, body: &[u8]) -> Self {
self.body = body.to_vec();
self
}
/// Sets an error associated with the response
///
/// Note: The reported error is sent in a separate FastCGI "stderr" stream which usually just
/// ends up in the logs of the FastCGI client.
/// Meaning, this does not get reported to the browser at all.
pub fn set_error(mut self, err: Box<dyn std::error::Error>) -> Self {
self.error = Some(err);
self
}
/// Returns the error associated with the response, if any
pub fn get_error(&self) -> Option<&dyn std::error::Error> {
self.error.as_deref()
}
pub(crate) fn write_stdout_bytes<W: Write>(&self, writer: &mut W) -> Result<(), io::Error> {
for (key, value) in self.headers.iter() {
writeln!(writer, "{key}: {value}")?;
}
writeln!(writer)?;
writer.write_all(&self.body)
}
pub(crate) fn write_stderr_bytes<W: Write>(&self, writer: &mut W) -> Result<(), io::Error> {
let Some(err) = &self.error else {
return Ok(());
};
writeln!(writer, "{}", err)?;
for child_err in std::iter::successors(Some(err.as_ref()), |err| err.source()) {
writeln!(writer, "\t{}", child_err)?;
}
Ok(())
}
}
// Shortcuts for creating responses
impl Response {
pub fn text<S: std::fmt::Display>(str: S) -> Response {
Response::default()
.set_body(str.to_string())
.set_content_type("text/plain")
}
/// Creates an HTML response
pub fn html<S: Display>(str: S) -> Response {
Response::default()
.set_body(str.to_string())
.set_content_type("text/html")
}
/// Creates a JSON response
pub fn json<S: Display>(str: S) -> Response {
Response::default()
.set_body(str.to_string())
.set_content_type("application/json")
}
/// Creates a temporary redirection response.
///
/// The browser receiving the request will to re-make the request with `path` as the new target
/// with method and body unchanged.
///
/// Search engines receiving this response will not attribute links to the original URL to the
/// new resource, meaning no SEO value is transferred to the new URL.
pub fn redirect(path: &str) -> Response {
Response::new().set_location(path).set_status(307)
}
/// Creates a permanent redirection response.
///
/// The browser receiving the request will to re-make the request with `path` as the new target
/// with method and body unchanged.
///
/// Search engines receiving this response will attribute links to the original URL to the
/// redirected resource, passing the SEO ranking to the new URL.
pub fn permanent_redirect(path: &str) -> Response {
Response::new().set_location(path).set_status(308)
}
}