1use futures_io::AsyncWrite;
2use futures_lite::{AsyncReadExt, AsyncWriteExt};
3use std::convert::TryFrom;
4use std::io::ErrorKind;
5use std::io::Write;
6
7#[cfg(any(feature = "include_dir", feature = "json"))]
8use crate::Error;
9#[cfg(feature = "include_dir")]
10use crate::Request;
11use crate::event::EventReceiver;
12use crate::http_error::HttpError;
13use crate::util::{copy_async, copy_chunked_async};
14use crate::{AsciiString, ContentType, Cookie, EventSender, HeaderList, ResponseBody};
15use safina::sync::sync_channel;
16use std::fmt::Debug;
17use std::sync::Mutex;
18
19#[allow(clippy::module_name_repetitions)]
20#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
21pub enum ResponseKind {
22 DropConnection,
23 GetBodyAndReprocess(u64),
26 Normal,
27}
28
29#[derive(Eq, PartialEq)]
30pub struct Response {
31 pub kind: ResponseKind,
32 pub code: u16,
33 pub content_type: ContentType,
34 pub headers: HeaderList,
35 pub body: ResponseBody,
36}
37impl Response {
38 #[must_use]
39 pub fn new(code: u16) -> Self {
40 Self {
41 kind: ResponseKind::Normal,
42 code,
43 content_type: ContentType::None,
44 headers: HeaderList::new(),
45 body: ResponseBody::empty(),
46 }
47 }
48
49 #[must_use]
51 pub fn drop_connection() -> Self {
52 Self {
53 kind: ResponseKind::DropConnection,
54 code: 0,
55 content_type: ContentType::None,
56 headers: HeaderList::new(),
57 body: ResponseBody::empty(),
58 }
59 }
60
61 #[must_use]
66 pub fn get_body_and_reprocess(max_len: u64) -> Self {
67 Self {
68 kind: ResponseKind::GetBodyAndReprocess(max_len),
69 code: 0,
70 content_type: ContentType::None,
71 headers: HeaderList::new(),
72 body: ResponseBody::empty(),
73 }
74 }
75
76 #[cfg(feature = "include_dir")]
86 pub fn include_dir(req: &Request, dir: &'static include_dir::Dir) -> Result<Response, Error> {
90 let path = &req.url.path;
91 let path = path.strip_prefix('/').unwrap_or(path);
92 let file = if path.is_empty() {
93 dir.get_file("index.html")
94 } else if let Some(file) = dir.get_file(path) {
95 Some(file)
96 } else if path.ends_with('/') {
97 let dir_path = path.trim_end_matches('/');
98 dir.get_file(format!("{dir_path}/index.html"))
99 } else if let Some(_dir) = dir.get_dir(path) {
100 return Ok(Response::redirect_301(format!("/{path}/")));
101 } else {
102 None
103 }
104 .ok_or_else(|| Error::client_error(Response::not_found_404()))?;
105 let extension = std::path::Path::new(path)
106 .extension()
107 .map_or("", |os_str| os_str.to_str().unwrap_or(""));
108 let content_type = match extension {
109 "css" => ContentType::Css,
110 "csv" => ContentType::Csv,
111 "gif" => ContentType::Gif,
112 "htm" | "html" => ContentType::Html,
113 "js" => ContentType::JavaScript,
114 "jpg" | "jpeg" => ContentType::Jpeg,
115 "json" => ContentType::Json,
116 "md" => ContentType::Markdown,
117 "pdf" => ContentType::Pdf,
118 "txt" => ContentType::PlainText,
119 "png" => ContentType::Png,
120 "svg" => ContentType::Svg,
121 _ => ContentType::None,
122 };
123 Ok(Response::new(200)
124 .with_type(content_type)
125 .with_body(ResponseBody::StaticBytes(file.contents())))
126 }
127
128 #[must_use]
129 pub fn html(code: u16, body: impl Into<ResponseBody>) -> Self {
130 Self::new(code).with_type(ContentType::Html).with_body(body)
131 }
132
133 #[cfg(feature = "json")]
136 pub fn json(code: u16, v: impl serde::Serialize) -> Result<Response, Error> {
137 let body_vec = serde_json::to_vec(&v)
138 .map_err(|e| Error::server_error(format!("error serializing response to json: {e}")))?;
139 Ok(Self::new(code)
140 .with_type(ContentType::Json)
141 .with_body(body_vec))
142 }
143
144 #[must_use]
145 pub fn event_stream() -> (EventSender, Response) {
146 let (sender, receiver) = sync_channel(50);
147 (
148 EventSender(Some(sender)),
149 Self::new(200)
150 .with_type(ContentType::EventStream)
151 .with_body(ResponseBody::EventStream(Mutex::new(EventReceiver(
152 receiver,
153 )))),
154 )
155 }
156
157 #[must_use]
158 pub fn text(code: u16, body: impl Into<ResponseBody>) -> Self {
159 Self::new(code)
160 .with_type(ContentType::PlainText)
161 .with_body(body)
162 }
163
164 #[must_use]
165 pub fn ok_200() -> Self {
166 Response::new(200)
167 }
168
169 #[must_use]
170 pub fn no_content_204() -> Self {
171 Response::new(204)
172 }
173
174 #[must_use]
181 pub fn redirect_301(location: impl AsRef<str>) -> Self {
182 Response::new(301).with_header("location", location.as_ref().try_into().unwrap())
183 }
184
185 #[must_use]
194 pub fn redirect_303(location: impl AsRef<str>) -> Self {
195 Response::new(303).with_header("location", location.as_ref().try_into().unwrap())
196 }
197
198 #[must_use]
199 pub fn unauthorized_401() -> Self {
200 Response::new(401)
201 }
202
203 #[must_use]
204 pub fn forbidden_403() -> Self {
205 Response::new(401)
206 }
207
208 #[must_use]
209 pub fn not_found_404() -> Self {
210 Response::text(404, "not found")
211 }
212
213 #[must_use]
216 pub fn method_not_allowed_405(allowed_methods: &[&'static str]) -> Self {
217 Self::new(405).with_header("allow", allowed_methods.join(",").try_into().unwrap())
218 }
219
220 #[must_use]
221 pub fn length_required_411() -> Self {
222 Response::text(411, "not accepting streaming uploads")
223 }
224
225 #[must_use]
226 pub fn payload_too_large_413() -> Self {
227 Response::text(413, "Uploaded data is too big.")
228 }
229
230 #[must_use]
231 pub fn unprocessable_entity_422(body: impl Into<String>) -> Self {
232 let body: String = body.into();
233 Response::text(422, body)
234 }
235
236 #[must_use]
237 pub fn too_many_requests_429() -> Self {
238 Response::text(429, "Too many requests.")
239 }
240
241 #[must_use]
242 pub fn internal_server_error_500() -> Self {
243 Response::new(500)
244 }
245
246 #[must_use]
247 pub fn not_implemented_501() -> Self {
248 Response::new(501)
249 }
250
251 #[must_use]
252 pub fn service_unavailable_503() -> Self {
253 Response::new(503)
254 }
255
256 #[must_use]
257 pub fn with_body(mut self, b: impl Into<ResponseBody>) -> Self {
258 self.body = b.into();
259 self
260 }
261
262 #[allow(clippy::missing_panics_doc)]
266 #[must_use]
267 pub fn with_max_age_seconds(mut self, seconds: u32) -> Self {
268 self.headers.add(
269 "cache-control",
270 format!("max-age={seconds}").try_into().unwrap(),
271 );
272 self
273 }
274
275 #[allow(clippy::missing_panics_doc)]
279 #[must_use]
280 pub fn with_no_store(mut self) -> Self {
281 self.headers
282 .add("cache-control", "no-store".try_into().unwrap());
283 self
284 }
285
286 #[must_use]
290 pub fn with_set_cookie(mut self, cookie: Cookie) -> Self {
291 self.headers.add("set-cookie", cookie.into());
292 self
293 }
294
295 #[must_use]
315 pub fn with_header(mut self, name: impl AsRef<str>, value: AsciiString) -> Self {
316 self.headers.add(name, value);
317 self
318 }
319
320 #[must_use]
321 pub fn with_status(mut self, c: u16) -> Self {
322 self.code = c;
323 self
324 }
325
326 #[must_use]
327 pub fn with_type(mut self, t: ContentType) -> Self {
328 self.content_type = t;
329 self
330 }
331
332 #[must_use]
333 pub fn is_1xx(&self) -> bool {
334 self.code / 100 == 1
335 }
336
337 #[must_use]
338 pub fn is_2xx(&self) -> bool {
339 self.code / 100 == 2
340 }
341
342 #[must_use]
343 pub fn is_3xx(&self) -> bool {
344 self.code / 100 == 3
345 }
346
347 #[must_use]
348 pub fn is_4xx(&self) -> bool {
349 self.code / 100 == 4
350 }
351
352 #[must_use]
353 pub fn is_5xx(&self) -> bool {
354 self.code / 100 == 5
355 }
356
357 #[must_use]
358 pub fn is_normal(&self) -> bool {
359 self.kind == ResponseKind::Normal
360 }
361
362 #[must_use]
363 pub fn is_get_body_and_reprocess(&self) -> bool {
364 matches!(self.kind, ResponseKind::GetBodyAndReprocess(..))
365 }
366}
367impl From<std::io::Error> for Response {
368 fn from(e: std::io::Error) -> Self {
369 match e.kind() {
370 ErrorKind::InvalidData => Response::text(400, "Bad request"),
371 _ => Response::text(500, "Internal server error"),
372 }
373 }
374}
375impl Debug for Response {
376 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
377 match self.kind {
378 ResponseKind::DropConnection => write!(f, "Response(kind=Drop)"),
379 ResponseKind::GetBodyAndReprocess(max_len) => {
380 write!(f, "Response(kind=GetBodyAndReprocess({max_len}))")
381 }
382 ResponseKind::Normal => {
383 write!(
384 f,
385 "Response({} {}, {:?}, {:?}, {:?})",
386 self.code,
387 reason_phrase(self.code),
388 self.content_type,
389 self.headers,
390 self.body
391 )
392 }
393 }
394 }
395}
396
397#[must_use]
398pub fn reason_phrase(code: u16) -> &'static str {
399 match code {
401 100 => "Continue",
402 101 => "Switching Protocols",
403 102 => "Processing",
404 103 => "Early Hints",
405 200 => "OK",
406 201 => "Created",
407 202 => "Accepted",
408 203 => "Non-Authoritative Information",
409 204 => "No Content",
410 205 => "Reset Content",
411 206 => "Partial Content",
412 207 => "Multi-Status",
413 208 => "Already Reported",
414 226 => "IM Used",
415 300 => "Multiple Choice",
416 301 => "Moved Permanently",
417 302 => "Found",
418 303 => "See Other",
419 304 => "Not Modified",
420 307 => "Temporary Redirect",
421 308 => "Permanent Redirect",
422 400 => "Bad Request",
423 401 => "Unauthorized",
424 402 => "Payment Required ",
425 403 => "Forbidden",
426 404 => "Not Found",
427 405 => "Method Not Allowed",
428 406 => "Not Acceptable",
429 407 => "Proxy Authentication Required",
430 408 => "Request Timeout",
431 409 => "Conflict",
432 410 => "Gone",
433 411 => "Length Required",
434 412 => "Precondition Failed",
435 413 => "Payload Too Large",
436 414 => "URI Too Long",
437 415 => "Unsupported Media Type",
438 416 => "Range Not Satisfiable",
439 417 => "Expectation Failed",
440 418 => "I'm a teapot",
441 421 => "Misdirected Request",
442 422 => "Unprocessable Entity",
443 423 => "Locked",
444 424 => "Failed Dependency",
445 425 => "Too Early ",
446 426 => "Upgrade Required",
447 428 => "Precondition Required",
448 429 => "Too Many Requests",
449 431 => "Request Header Fields Too Large",
450 451 => "Unavailable For Legal Reasons",
451 500 => "Internal Server Error",
452 501 => "Not Implemented",
453 502 => "Bad Gateway",
454 503 => "Service Unavailable",
455 504 => "Gateway Timeout",
456 505 => "HTTP Version Not Supported",
457 506 => "Variant Also Negotiates",
458 507 => "Insufficient Storage",
459 508 => "Loop Detected",
460 510 => "Not Extended",
461 511 => "Network Authentication Required",
462 _ => "Response",
463 }
464}
465
466#[allow(clippy::module_name_repetitions)]
473pub async fn write_http_response(
474 mut writer: impl AsyncWrite + Unpin,
475 response: &Response,
476 close: bool,
477) -> Result<(), HttpError> {
478 if !response.is_normal() {
480 return Err(HttpError::UnwritableResponse);
481 }
482 let mut head_bytes: Vec<u8> = format!(
487 "HTTP/1.1 {} {}\r\n",
488 response.code,
489 reason_phrase(response.code)
490 )
491 .into_bytes();
492 if response.content_type != ContentType::None {
493 if response.headers.get_only("content-type").is_some() {
494 return Err(HttpError::DuplicateContentTypeHeader);
495 }
496 write!(
497 head_bytes,
498 "content-type: {}\r\n",
499 response.content_type.as_str()
500 )
501 .unwrap();
502 }
503 if close {
504 write!(head_bytes, "connection: close\r\n",).unwrap();
505 }
506 if let Some(body_len) = response.body.len() {
507 if response.headers.get_only("content-length").is_some() {
508 return Err(HttpError::DuplicateContentLengthHeader);
509 }
510 write!(head_bytes, "content-length: {body_len}\r\n").unwrap();
511 } else {
512 if response.headers.get_only("transfer-encoding").is_some() {
513 return Err(HttpError::DuplicateTransferEncodingHeader);
514 }
515 write!(head_bytes, "transfer-encoding: chunked\r\n").unwrap();
516 }
517 for header in &response.headers {
518 write!(head_bytes, "{}: ", header.name).unwrap();
520 head_bytes.extend(header.value.chars().map(|c| u8::try_from(c).unwrap_or(255)));
521 head_bytes.extend(b"\r\n");
522 }
523 head_bytes.extend(b"\r\n");
524 writer
526 .write_all(head_bytes.as_slice())
527 .await
528 .map_err(|_| HttpError::Disconnected)?;
529 drop(head_bytes);
530 match response.body.len() {
531 Some(0) => {}
532 Some(body_len) => {
533 let mut reader = AsyncReadExt::take(
534 response
535 .body
536 .async_reader()
537 .await
538 .map_err(HttpError::error_reading_file)?,
539 body_len,
540 );
541 let num_copied = copy_async(&mut reader, &mut writer, body_len)
542 .await
543 .map_errs(HttpError::error_reading_response_body, |_| {
544 HttpError::Disconnected
545 })?;
546 if num_copied != body_len {
547 return Err(HttpError::ErrorReadingResponseBody(
548 ErrorKind::UnexpectedEof,
549 "body is smaller than expected".to_string(),
550 ));
551 }
552 }
553 None => {
554 let mut reader = response
555 .body
556 .async_reader()
557 .await
558 .map_err(HttpError::error_reading_response_body)?;
559 copy_chunked_async(&mut reader, &mut writer)
560 .await
561 .map_errs(HttpError::error_reading_response_body, |_| {
562 HttpError::Disconnected
563 })?;
564 }
565 }
566 writer.flush().await.map_err(|_| HttpError::Disconnected)
567}