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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
//! A HTTP request
use crate::bytes::{Data, Source};
use crate::error::Error;
use std::fs::File;
use std::io::{self, BufReader, Seek, SeekFrom, Write};
/// A HTTP response
#[derive(Debug)]
pub struct Response<const HEADER_SIZE_MAX: usize = 4096> {
/// The HTTP version
pub version: Data,
/// The response status code
pub status: Data,
/// The response status reason
pub reason: Data,
/// The response header fields
pub fields: Vec<(Data, Data)>,
/// The response body
pub body: Source,
}
// Core functionality
impl<const HEADER_SIZE_MAX: usize> Response<HEADER_SIZE_MAX> {
/// Creates a new HTTP response
pub fn new(version: Data, status: Data, reason: Data) -> Self {
Self { version, status, reason, fields: Vec::new(), body: Source::default() }
}
/// Writes the response to the given stream
pub fn to_stream<T>(&mut self, stream: &mut T) -> Result<(), Error>
where
T: Write,
{
// Create a temporary buffer
let mut buf = Vec::with_capacity(HEADER_SIZE_MAX);
// Write start line
buf.write_all(&self.version)?;
buf.write_all(b" ")?;
buf.write_all(&self.status)?;
buf.write_all(b" ")?;
buf.write_all(&self.reason)?;
buf.write_all(b"\r\n")?;
// Write header fields and finalize header
for (key, value) in &self.fields {
buf.write_all(key)?;
buf.write_all(b": ")?;
buf.write_all(value)?;
buf.write_all(b"\r\n")?;
}
buf.write_all(b"\r\n")?;
// Write the header, and copy the body
stream.write_all(&buf)?;
io::copy(&mut self.body, stream)?;
Ok(())
}
/// Checks if the header has `Connection: Close` set
pub fn has_connection_close(&self) -> bool {
// Search for `Connection` header
for (key, value) in &self.fields {
if key.eq_ignore_ascii_case(b"Connection") {
return value.eq_ignore_ascii_case(b"close");
}
}
false
}
}
// Useful helpers
impl<const HEADER_SIZE_MAX: usize> Response<HEADER_SIZE_MAX> {
/// Creates a new HTTP response with the given status code and reason and sets an empty body
pub fn new_status_reason<T>(status: u16, reason: T) -> Self
where
T: Into<Data>,
{
// Create basic request
let version = Data::from(b"HTTP/1.1");
let status = Data::from(status.to_string());
let reason = reason.into();
let mut this = Self::new(version, status, reason);
// Set content-length to 0
this.set_content_length(0);
this
}
/// Creates a new `200 OK` HTTP response with an empty body
pub fn new_200_ok() -> Self {
Self::new_status_reason(200, "OK")
}
/// Creates a new `307 Temporary Redirect` HTTP response with an empty body and the `Location`-header field set to
/// the given location
pub fn new_307_temporaryredirect<T>(location: T) -> Self
where
T: Into<Data>,
{
let mut this = Self::new_status_reason(307, "Temporary Redirect");
this.set_field("Location", location);
this
}
/// Creates a new `400 Bad Request` HTTP response with an empty body
pub fn new_400_badrequest() -> Self {
Self::new_status_reason(400, "Bad Request")
}
/// Creates a new `401 Unauthorized` HTTP response with an empty body and the `WWW-Authenticate`-header field set
/// to the given requirement
pub fn new_401_unauthorized<T>(requirement: T) -> Self
where
T: Into<Data>,
{
let mut this = Self::new_status_reason(401, "Unauthorized");
this.set_field("WWW-Authenticate", requirement);
this
}
/// Creates a new `403 Forbidden` HTTP response with an empty body
pub fn new_403_forbidden() -> Self {
Self::new_status_reason(403, "Forbidden")
}
/// Creates a new `404 Not Found` HTTP response with an empty body
pub fn new_404_notfound() -> Self {
Self::new_status_reason(404, "Not Found")
}
/// Creates a new `405 Method Not Allowed` HTTP response with an empty body
pub fn new_405_methodnotallowed() -> Self {
Self::new_status_reason(405, "Method Not Allowed")
}
/// Creates a new `413 Payload Too Large` HTTP response with an empty body
pub fn new_413_payloadtoolarge() -> Self {
Self::new_status_reason(413, "Payload Too Large")
}
/// Creates a new `416 Range Not Satisfiable` HTTP response with an empty body
pub fn new_416_rangenotsatisfiable() -> Self {
Self::new_status_reason(416, "Range Not Satisfiable")
}
/// Creates a new `500 Internal Server Error` HTTP response with an empty body
pub fn new_500_internalservererror() -> Self {
Self::new_status_reason(500, "Internal Server Error")
}
/// Sets the field with the given name (performs an ASCII-case-insensitve comparison for replacement)
pub fn set_field<K, V>(&mut self, key: K, value: V)
where
K: Into<Data>,
V: Into<Data>,
{
// Convert the field into vecs
let key = key.into();
let value = value.into();
// Remove any field with the same name and set the field
self.fields.retain(|(existing, _)| !key.eq_ignore_ascii_case(existing));
self.fields.push((key, value));
}
/// Sets the body content type
pub fn set_content_type<T>(&mut self, type_: T)
where
T: Into<Data>,
{
self.set_field("Content-Type", type_)
}
/// Sets the body content length
pub fn set_content_length(&mut self, len: u64) {
self.set_field("Content-Length", len.to_string())
}
/// Sets the connection header to `Close`
pub fn set_connection_close(&mut self) {
self.set_field("Connection", "Close")
}
/// Returns the content length if it is set
pub fn content_length(&self) -> Result<Option<u64>, Error> {
// Search for `Content-Length` header
for (key, value) in &self.fields {
if key.eq_ignore_ascii_case(b"Content-Length") {
// Decode the value
let value = str::from_utf8(value)?;
let content_length: u64 = value.parse()?;
return Ok(Some(content_length));
}
}
Ok(None)
}
/// Sets the given data as body content and updates the `Content-Length` header accordingly
pub fn set_body_data<T>(&mut self, data: T)
where
T: Into<Data>,
{
let data = data.into();
self.set_content_length(data.len() as u64);
self.body = Source::from(data);
}
/// Sets the given file as body content and updates the `Content-Length` header accordingly
///
/// # Note
/// Please note that this function also respects the file's current seek offset; so if you are at offset `7` out of
/// `15`, the content length is set to `8`.
pub fn set_body_file<T>(&mut self, file: T) -> Result<(), Error>
where
T: Into<File>,
{
// Get the current position and the total length
let mut file = file.into();
#[allow(clippy::seek_from_current)]
let pos = file.seek(SeekFrom::Current(0))?;
let len = file.seek(SeekFrom::End(0))?;
// Recover the original position and set the length
if pos != len {
file.seek(SeekFrom::Start(pos))?;
}
self.set_content_length(len - pos);
// Set the body
self.body = BufReader::new(file).into();
Ok(())
}
/// Turns the current `GET`-response into a `HEAD`-response by discarding the body without modifying content length
/// etc.
pub fn make_head(&mut self) {
self.body = Source::default();
}
}