1use crate::headers::{Headers, IntoHeaderValue};
3use crate::syscalls;
4use std::io;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10#[repr(u8)]
11pub enum Method {
12 Get = 0,
13 Post = 1,
14 Put = 2,
15 Delete = 3,
16 Patch = 4,
17 Head = 5,
18 Options = 6,
19 Trace = 7,
20 Connect = 8,
21 Unknown = 9,
22}
23
24impl Method {
25 #[inline(always)]
27 pub fn from_bytes(b: &[u8]) -> Self {
28 if b.is_empty() {
29 return Method::Unknown;
30 }
31 match b[0] {
32 b'G' => {
33 if b.len() == 3 && b[1] == b'E' && b[2] == b'T' {
34 Method::Get
35 } else {
36 Method::Unknown
37 }
38 }
39 b'P' => {
40 if b.len() < 3 {
41 return Method::Unknown;
42 }
43 match b[1] {
44 b'O' => {
45 if b.len() == 4 && b[2] == b'S' && b[3] == b'T' {
46 Method::Post
47 } else {
48 Method::Unknown
49 }
50 }
51 b'U' => {
52 if b.len() == 3 && b[2] == b'T' {
53 Method::Put
54 } else {
55 Method::Unknown
56 }
57 }
58 b'A' => {
59 if b.len() == 5 && b[2] == b'T' && b[3] == b'C' && b[4] == b'H' {
60 Method::Patch
61 } else {
62 Method::Unknown
63 }
64 }
65 _ => Method::Unknown,
66 }
67 }
68 b'D' => {
69 if b == b"DELETE" {
70 Method::Delete
71 } else {
72 Method::Unknown
73 }
74 }
75 b'H' => {
76 if b == b"HEAD" {
77 Method::Head
78 } else {
79 Method::Unknown
80 }
81 }
82 b'O' => {
83 if b == b"OPTIONS" {
84 Method::Options
85 } else {
86 Method::Unknown
87 }
88 }
89 b'T' => {
90 if b == b"TRACE" {
91 Method::Trace
92 } else {
93 Method::Unknown
94 }
95 }
96 b'C' => {
97 if b == b"CONNECT" {
98 Method::Connect
99 } else {
100 Method::Unknown
101 }
102 }
103 _ => Method::Unknown,
104 }
105 }
106}
107
108pub const MAX_HEADERS: usize = 32;
109pub const MAX_PARAMS: usize = 4;
110
111pub struct Request<'a> {
114 pub method: Method,
115 pub path: &'a str,
116 pub query: Option<&'a str>,
117 pub headers: [(&'a str, &'a str); MAX_HEADERS],
118 pub header_count: u8,
119 pub body: &'a [u8],
120}
121
122pub struct OwnedFd(i32);
124
125impl OwnedFd {
126 pub fn new(fd: i32) -> Self {
128 Self(fd)
129 }
130
131 pub(crate) fn take(&mut self) -> i32 {
134 let fd = self.0;
135 self.0 = -1;
136 fd
137 }
138
139 #[allow(dead_code)]
141 pub fn raw(&self) -> i32 {
142 self.0
143 }
144}
145
146impl Drop for OwnedFd {
147 fn drop(&mut self) {
148 if self.0 >= 0 {
149 unsafe {
150 libc::close(self.0);
151 }
152 }
153 }
154}
155
156pub enum Body {
161 Empty,
163 Static(&'static [u8]),
165 Bytes(Vec<u8>),
167 Stream(Box<dyn Iterator<Item = Vec<u8>> + Send>),
169 File { fd: OwnedFd, offset: u64, len: u64 },
172 Raw(&'static [u8]),
179}
180
181impl Body {
182 #[inline(always)]
183 pub fn len(&self) -> usize {
184 match self {
185 Body::Empty => 0,
186 Body::Static(b) => b.len(),
187 Body::Bytes(b) => b.len(),
188 Body::Stream(_) => 0, Body::File { len, .. } => *len as usize,
190 Body::Raw(b) => b.len(), }
192 }
193
194 #[inline(always)]
195 pub fn is_empty(&self) -> bool {
196 self.len() == 0
197 }
198
199 #[inline(always)]
200 pub fn as_bytes(&self) -> &[u8] {
201 match self {
202 Body::Empty => &[],
203 Body::Static(b) => b,
204 Body::Bytes(b) => b.as_slice(),
205 Body::Stream(_) => &[], Body::File { .. } => &[], Body::Raw(b) => b, }
209 }
210
211 #[inline(always)]
213 pub fn is_file(&self) -> bool {
214 matches!(self, Body::File { .. })
215 }
216
217 #[inline(always)]
219 pub fn is_raw(&self) -> bool {
220 matches!(self, Body::Raw(_))
221 }
222}
223
224pub struct Response {
245 pub status: u16,
246 pub body: Body,
247 pub content_type: &'static str,
248 pub headers: Headers,
251}
252
253impl Response {
254 pub fn new(status: u16) -> Self {
256 Self {
257 status,
258 body: Body::Empty,
259 content_type: "text/plain",
260 headers: Headers::new(),
261 }
262 }
263
264 pub fn with_header(mut self, name: &'static str, value: impl IntoHeaderValue) -> Self {
270 self.headers.add(name, value);
271 self
272 }
273
274 pub fn text(body: impl Into<Vec<u8>>) -> Self {
276 Self {
277 status: 200,
278 body: Body::Bytes(body.into()),
279 content_type: "text/plain",
280 headers: Headers::new(),
281 }
282 }
283
284 pub fn text_static(body: &'static [u8]) -> Self {
287 Self {
288 status: 200,
289 body: Body::Static(body),
290 content_type: "text/plain",
291 headers: Headers::new(),
292 }
293 }
294
295 pub fn json_bytes(body: impl Into<Vec<u8>>) -> Self {
298 Self {
299 status: 200,
300 body: Body::Bytes(body.into()),
301 content_type: "application/json",
302 headers: Headers::new(),
303 }
304 }
305
306 pub fn json<T: kowito_json::serialize::Serialize>(val: &T) -> Self {
309 let mut buf = Vec::with_capacity(128);
310 val.serialize(&mut buf);
311 Self::json_bytes(buf)
312 }
313
314 #[inline(always)]
325 pub fn json_static(body: &'static [u8]) -> Self {
326 Self {
327 status: 200,
328 body: Body::Static(body),
329 content_type: "application/json",
330 headers: Headers::new(),
331 }
332 }
333
334 #[inline(always)]
361 pub fn raw(bytes: &'static [u8]) -> Self {
362 Self {
363 status: 200,
364 body: Body::Raw(bytes),
365 content_type: "",
366 headers: Headers::new(),
367 }
368 }
369
370 pub fn not_found() -> Self {
372 Self {
373 status: 404,
374 body: Body::Static(b"Not Found"),
375 content_type: "text/plain",
376 headers: Headers::new(),
377 }
378 }
379
380 pub fn server_error() -> Self {
382 Self {
383 status: 500,
384 body: Body::Static(b"Internal Server Error"),
385 content_type: "text/plain",
386 headers: Headers::new(),
387 }
388 }
389
390 pub fn bad_request() -> Self {
392 Self {
393 status: 400,
394 body: Body::Static(b"Bad Request"),
395 content_type: "text/plain",
396 headers: Headers::new(),
397 }
398 }
399
400 pub fn unauthorized() -> Self {
402 Self {
403 status: 401,
404 body: Body::Static(b"Unauthorized"),
405 content_type: "text/plain",
406 headers: Headers::new(),
407 }
408 }
409
410 pub fn forbidden() -> Self {
412 Self {
413 status: 403,
414 body: Body::Static(b"Forbidden"),
415 content_type: "text/plain",
416 headers: Headers::new(),
417 }
418 }
419
420 pub fn stream(iter: impl Iterator<Item = Vec<u8>> + Send + 'static) -> Self {
422 Self {
423 status: 200,
424 body: Body::Stream(Box::new(iter)),
425 content_type: "application/octet-stream",
426 headers: Headers::new(),
427 }
428 }
429
430 pub fn file(path: &str) -> Self {
433 match Self::try_file(path) {
434 Ok(resp) => resp,
435 Err(_) => Self::not_found(),
436 }
437 }
438
439 fn try_file(path: &str) -> io::Result<Self> {
441 let fd = syscalls::open_file_readonly(path)?;
442 let size = match syscalls::file_size(fd) {
443 Ok(s) => s,
444 Err(e) => {
445 unsafe {
446 libc::close(fd);
447 }
448 return Err(e);
449 }
450 };
451 let content_type = mime_from_path(path);
452
453 Ok(Self {
454 status: 200,
455 body: Body::File {
456 fd: OwnedFd::new(fd),
457 offset: 0,
458 len: size,
459 },
460 content_type,
461 headers: Headers::new(),
462 })
463 }
464
465 pub fn sendfile(fd: i32, offset: u64, len: u64, content_type: &'static str) -> Self {
469 Self {
470 status: 200,
471 body: Body::File {
472 fd: OwnedFd::new(fd),
473 offset,
474 len,
475 },
476 content_type,
477 headers: Headers::new(),
478 }
479 }
480
481 #[cfg(feature = "compression")]
487 pub fn gzip(mut self) -> Self {
488 use flate2::Compression;
489 use flate2::write::GzEncoder;
490 use std::io::Write;
491
492 let raw = match &self.body {
493 Body::Static(b) => *b,
494 Body::Bytes(b) => b.as_slice(),
495 _ => return self,
496 };
497
498 if raw.is_empty() {
499 return self;
500 }
501
502 let mut encoder = GzEncoder::new(Vec::with_capacity(raw.len() / 2), Compression::fast());
503 if encoder.write_all(raw).is_ok() {
504 if let Ok(compressed) = encoder.finish() {
505 if compressed.len() < raw.len() {
506 self.body = Body::Bytes(compressed);
507 self.headers.add("Content-Encoding", "gzip");
508 self.headers.add("Vary", "Accept-Encoding");
509 }
510 }
511 }
512 self
513 }
514}
515
516fn mime_from_path(path: &str) -> &'static str {
519 let ext = match path.rsplit('.').next() {
520 Some(e) => e,
521 None => return "application/octet-stream",
522 };
523 match ext {
524 "html" | "htm" => "text/html; charset=utf-8",
526 "css" => "text/css; charset=utf-8",
527 "js" | "mjs" => "application/javascript; charset=utf-8",
528 "json" => "application/json; charset=utf-8",
529 "xml" => "application/xml; charset=utf-8",
530 "txt" => "text/plain; charset=utf-8",
531 "csv" => "text/csv; charset=utf-8",
532 "svg" => "image/svg+xml",
533 "png" => "image/png",
535 "jpg" | "jpeg" => "image/jpeg",
536 "gif" => "image/gif",
537 "webp" => "image/webp",
538 "ico" => "image/x-icon",
539 "avif" => "image/avif",
540 "woff" => "font/woff",
542 "woff2" => "font/woff2",
543 "ttf" => "font/ttf",
544 "otf" => "font/otf",
545 "mp4" => "video/mp4",
547 "webm" => "video/webm",
548 "mp3" => "audio/mpeg",
549 "ogg" => "audio/ogg",
550 "wasm" => "application/wasm",
552 "pdf" => "application/pdf",
553 "zip" => "application/zip",
554 "gz" | "gzip" => "application/gzip",
555 "tar" => "application/x-tar",
556 _ => "application/octet-stream",
557 }
558}
559
560pub trait IntoResponse {
565 fn into_response(self) -> Response;
566}
567
568impl IntoResponse for Response {
569 fn into_response(self) -> Response {
570 self
571 }
572}
573
574impl IntoResponse for String {
575 fn into_response(self) -> Response {
576 Response::text(self.into_bytes())
577 }
578}
579
580impl IntoResponse for &'static str {
581 fn into_response(self) -> Response {
582 Response::text(self.as_bytes().to_vec())
583 }
584}
585
586impl<T: IntoResponse, E: IntoResponse> IntoResponse for Result<T, E> {
587 fn into_response(self) -> Response {
588 match self {
589 Ok(v) => v.into_response(),
590 Err(e) => e.into_response(),
591 }
592 }
593}
594
595pub struct Context<'a> {
617 pub req: Request<'a>,
618 pub params: [(&'a str, &'a str); MAX_PARAMS],
619 pub param_count: u8,
620}
621
622impl<'a> Context<'a> {
623 pub fn param(&self, key: &str) -> Option<&'a str> {
625 for i in 0..self.param_count as usize {
626 if self.params[i].0 == key {
627 return Some(self.params[i].1);
628 }
629 }
630 None
631 }
632
633 pub fn header(&self, key: &str) -> Option<&'a str> {
635 for i in 0..self.req.header_count as usize {
636 if self.req.headers[i].0.eq_ignore_ascii_case(key) {
637 return Some(self.req.headers[i].1);
638 }
639 }
640 None
641 }
642
643 #[allow(clippy::collapsible_if)]
646 pub fn multipart(&self) -> Option<crate::multipart::Multipart<'a>> {
647 let ct = self.header("content-type")?;
648 if ct.starts_with("multipart/form-data") {
649 if let Some(idx) = ct.find("boundary=") {
650 let boundary = &ct[idx + 9..];
651 return Some(crate::multipart::Multipart::new(self.req.body, boundary));
652 }
653 }
654 None
655 }
656
657 pub fn extract<T: crate::extract::FromRequest<'a>>(&'a self) -> Result<T, T::Error> {
660 T::from_request(self)
661 }
662
663 pub fn json<T: crate::json::Serialize>(&self, val: &T) -> Response {
666 Response::json(val)
667 }
668}
669
670#[cfg(test)]
671mod tests {
672 use super::*;
673
674 #[test]
677 fn test_method_get() {
678 assert_eq!(Method::from_bytes(b"GET"), Method::Get);
679 }
680 #[test]
681 fn test_method_post() {
682 assert_eq!(Method::from_bytes(b"POST"), Method::Post);
683 }
684 #[test]
685 fn test_method_put() {
686 assert_eq!(Method::from_bytes(b"PUT"), Method::Put);
687 }
688 #[test]
689 fn test_method_delete() {
690 assert_eq!(Method::from_bytes(b"DELETE"), Method::Delete);
691 }
692 #[test]
693 fn test_method_patch() {
694 assert_eq!(Method::from_bytes(b"PATCH"), Method::Patch);
695 }
696 #[test]
697 fn test_method_head() {
698 assert_eq!(Method::from_bytes(b"HEAD"), Method::Head);
699 }
700 #[test]
701 fn test_method_options() {
702 assert_eq!(Method::from_bytes(b"OPTIONS"), Method::Options);
703 }
704 #[test]
705 fn test_method_trace() {
706 assert_eq!(Method::from_bytes(b"TRACE"), Method::Trace);
707 }
708 #[test]
709 fn test_method_connect() {
710 assert_eq!(Method::from_bytes(b"CONNECT"), Method::Connect);
711 }
712
713 #[test]
714 fn test_method_empty_is_unknown() {
715 assert_eq!(Method::from_bytes(b""), Method::Unknown);
716 }
717
718 #[test]
719 fn test_method_lowercase_is_unknown() {
720 assert_eq!(Method::from_bytes(b"get"), Method::Unknown);
721 assert_eq!(Method::from_bytes(b"post"), Method::Unknown);
722 }
723
724 #[test]
725 fn test_method_truncated_is_unknown() {
726 assert_eq!(Method::from_bytes(b"GE"), Method::Unknown);
727 assert_eq!(Method::from_bytes(b"POS"), Method::Unknown);
728 assert_eq!(Method::from_bytes(b"DEL"), Method::Unknown);
729 }
730
731 #[test]
732 fn test_method_junk_is_unknown() {
733 assert_eq!(Method::from_bytes(b"GETX"), Method::Unknown);
734 assert_eq!(Method::from_bytes(b"XPOST"), Method::Unknown);
735 }
736
737 #[test]
738 fn test_method_eq_and_copy() {
739 let m = Method::Get;
740 let m2 = m; assert_eq!(m, m2);
742 assert_ne!(Method::Get, Method::Post);
743 }
744
745 #[test]
748 fn test_response_new_status() {
749 let r = Response::new(204);
750 assert_eq!(r.status, 204);
751 assert!(r.body.is_empty());
752 }
753
754 #[test]
755 fn test_response_text_status_and_ct() {
756 let r = Response::text(b"hello".to_vec());
757 assert_eq!(r.status, 200);
758 assert_eq!(r.content_type, "text/plain");
759 assert_eq!(r.body.as_bytes(), b"hello");
760 }
761
762 #[test]
763 fn test_response_text_static() {
764 let r = Response::text_static(b"static");
765 assert_eq!(r.status, 200);
766 assert_eq!(r.content_type, "text/plain");
767 assert_eq!(r.body.as_bytes(), b"static");
768 }
769
770 #[test]
771 fn test_response_json_bytes() {
772 let r = Response::json_bytes(b"{}".to_vec());
773 assert_eq!(r.status, 200);
774 assert_eq!(r.content_type, "application/json");
775 assert_eq!(r.body.as_bytes(), b"{}");
776 }
777
778 #[test]
779 fn test_response_not_found() {
780 let r = Response::not_found();
781 assert_eq!(r.status, 404);
782 }
783
784 #[test]
785 fn test_response_server_error() {
786 let r = Response::server_error();
787 assert_eq!(r.status, 500);
788 }
789
790 #[test]
791 fn test_response_bad_request() {
792 let r = Response::bad_request();
793 assert_eq!(r.status, 400);
794 }
795
796 #[test]
797 fn test_response_unauthorized() {
798 let r = Response::unauthorized();
799 assert_eq!(r.status, 401);
800 }
801
802 #[test]
803 fn test_response_forbidden() {
804 let r = Response::forbidden();
805 assert_eq!(r.status, 403);
806 }
807
808 #[test]
809 fn test_response_with_header_adds_header() {
810 let r = Response::new(200).with_header("x-custom", "value");
811 assert_eq!(r.status, 200);
812 let found = r
814 .headers
815 .iter()
816 .any(|h| h.name == "x-custom" && h.value.as_str() == "value");
817 assert!(found, "header x-custom: value not found");
818 }
819
820 #[test]
823 fn test_body_empty() {
824 let b = Body::Empty;
825 assert_eq!(b.len(), 0);
826 assert!(b.is_empty());
827 assert_eq!(b.as_bytes(), b"");
828 assert!(!b.is_file());
829 }
830
831 #[test]
832 fn test_body_static() {
833 let b = Body::Static(b"hello");
834 assert_eq!(b.len(), 5);
835 assert!(!b.is_empty());
836 assert_eq!(b.as_bytes(), b"hello");
837 }
838
839 #[test]
840 fn test_body_bytes() {
841 let v = b"world".to_vec();
842 let b = Body::Bytes(v.clone());
843 assert_eq!(b.len(), 5);
844 assert_eq!(b.as_bytes(), b"world");
845 }
846
847 #[test]
848 fn test_body_stream_len_is_zero() {
849 let b = Body::Stream(Box::new(std::iter::empty()));
850 assert_eq!(b.len(), 0);
851 assert!(b.is_empty());
852 assert!(!b.is_file());
853 }
854}