1#![warn(missing_docs)]
124
125use std::collections::{BTreeMap, HashMap};
126use std::fmt::{Debug, Display, Formatter};
127use std::future::ready;
128use std::pin::Pin;
129use std::time::SystemTime;
130use async_trait::async_trait;
131use bytes::Bytes;
132use chrono::{DateTime, FixedOffset, Utc};
133use futures_util::future::FutureExt;
134use http::{HeaderMap, Request, Response};
135use http_body_util::{BodyExt, Full};
136use hyper::body::{Body, Incoming};
137use itertools::Itertools;
138use lazy_static::lazy_static;
139use maplit::hashmap;
140use tracing::{debug, error, trace};
141
142use context::{WebmachineContext, WebmachineRequest, WebmachineResponse};
143use headers::HeaderValue;
144use crate::content_negotiation::acceptable_content_type;
145use crate::paths::map_path;
146
147#[macro_use] pub mod headers;
148pub mod context;
149pub mod content_negotiation;
150pub mod paths;
151
152pub type WebmachineCallback<T> = Box<dyn Fn(&mut WebmachineContext, &WebmachineResource) -> T + Send + Sync>;
154
155pub fn callback<T, RT>(cb: T) -> WebmachineCallback<RT>
157 where T: Fn(&mut WebmachineContext, &WebmachineResource) -> RT + Send + Sync + 'static {
158 Box::new(cb)
159}
160
161pub type AsyncWebmachineCallback<T> = Pin<Box<dyn Fn(&mut WebmachineContext, &WebmachineResource) -> Pin<Box<dyn Future<Output=T> + Send>> + Send + Sync>>;
163
164pub fn async_callback<T, RT>(cb: T) -> Pin<Box<T>>
166 where T: Fn(&mut WebmachineContext, &WebmachineResource) -> Pin<Box<dyn Future<Output=RT> + Send>> {
167 Box::pin(cb)
168}
169
170pub fn owned_vec(strings: &[&str]) -> Vec<String> {
172 strings.iter().map(|s| s.to_string()).collect()
173}
174
175#[async_trait]
177pub trait Resource: Debug {
178 fn finalise_response(&self, _context: &mut WebmachineContext) {}
181
182 async fn render_response(&self, _context: &mut WebmachineContext) -> anyhow::Result<Option<Bytes>> {
184 Ok(None)
185 }
186
187 fn available(&self, _context: &mut WebmachineContext) -> bool {
191 true
192 }
193
194 fn known_methods(&self) -> Vec<&str> {
197 vec!["OPTIONS", "GET", "POST", "PUT", "DELETE", "HEAD", "TRACE", "CONNECT", "PATCH"]
198 }
199
200 fn uri_too_long(&self, _context: &mut WebmachineContext) -> bool {
203 false
204 }
205
206 fn allowed_methods(&self) -> Vec<&str> {
208 vec!["OPTIONS", "GET", "HEAD"]
209 }
210
211 fn malformed_request(&self, _context: &mut WebmachineContext) -> bool {
214 false
215 }
216
217 fn not_authorized(&self, _context: &mut WebmachineContext) -> Option<String> {
221 None
222 }
223
224 fn forbidden(&self, _context: &mut WebmachineContext) -> bool {
227 false
228 }
229
230 fn unsupported_content_headers(&self, _context: &mut WebmachineContext) -> bool {
233 false
234 }
235
236 fn acceptable_content_types(&self, _context: &mut WebmachineContext) -> Vec<&str> {
240 vec!["application/json"]
241 }
242
243 fn valid_entity_length(&self, _context: &mut WebmachineContext) -> bool {
246 true
247 }
248
249 fn finish_request(&self, context: &mut WebmachineContext) {
253 context.response.add_cors_headers(self.allowed_methods().as_slice())
254 }
255
256 fn options(&self, _context: &mut WebmachineContext) -> Option<HashMap<String, Vec<String>>> {
259 Some(WebmachineResponse::cors_headers(self.allowed_methods().as_slice()))
260 }
261
262 fn produces(&self) -> Vec<&str> {
266 vec!["application/json"]
267 }
268
269 fn languages_provided(&self) -> Vec<&str> {
273 vec![]
274 }
275
276 fn charsets_provided(&self) -> Vec<&str> {
280 vec![]
281 }
282
283 fn encodings_provided(&self) -> Vec<&str> {
286 vec!["identity"]
287 }
288
289 fn variances(&self) -> Vec<&str> {
294 vec![]
295 }
296
297 async fn resource_exists(&self, _context: &mut WebmachineContext) -> bool {
300 true
301 }
302
303 fn previously_existed(&self, _context: &mut WebmachineContext) -> bool {
305 false
306 }
307
308 fn moved_permanently(&self, _context: &mut WebmachineContext) -> Option<String> {
311 None
312 }
313
314 fn moved_temporarily(&self, _context: &mut WebmachineContext) -> Option<String> {
317 None
318 }
319
320 fn is_conflict(&self, _context: &mut WebmachineContext) -> bool {
323 false
324 }
325
326 fn allow_missing_post(&self, _context: &mut WebmachineContext) -> bool {
328 false
329 }
330
331 fn generate_etag(&self, _context: &mut WebmachineContext) -> Option<String> {
334 None
335 }
336
337 fn last_modified(&self, _context: &mut WebmachineContext) -> Option<DateTime<FixedOffset>> {
341 None
342 }
343
344 async fn delete_resource(&self, _context: &mut WebmachineContext) -> Result<bool, u16> {
350 Ok(true)
351 }
352
353 fn post_is_create(&self, _context: &mut WebmachineContext) -> bool {
358 false
359 }
360
361 async fn process_post(&self, _context: &mut WebmachineContext) -> Result<bool, u16> {
367 Ok(false)
368 }
369
370 async fn create_path(&self, context: &mut WebmachineContext) -> Result<String, u16> {
379 Ok(context.request.request_path.clone())
380 }
381
382 async fn process_put(&self, _context: &mut WebmachineContext) -> Result<bool, u16> {
386 Ok(true)
387 }
388
389 fn multiple_choices(&self, _context: &mut WebmachineContext) -> bool {
393 false
394 }
395
396 fn expires(&self, _context: &mut WebmachineContext) -> Option<DateTime<FixedOffset>> {
398 None
399 }
400}
401
402pub struct WebmachineResource {
404 pub finalise_response: Option<WebmachineCallback<()>>,
407 pub render_response: AsyncWebmachineCallback<anyhow::Result<Option<Bytes>>>,
409 pub available: WebmachineCallback<bool>,
413 pub known_methods: Vec<String>,
416 pub uri_too_long: WebmachineCallback<bool>,
419 pub allowed_methods: Vec<String>,
421 pub malformed_request: WebmachineCallback<bool>,
424 pub not_authorized: WebmachineCallback<Option<String>>,
428 pub forbidden: WebmachineCallback<bool>,
431 pub unsupported_content_headers: WebmachineCallback<bool>,
434 pub acceptable_content_types: Vec<String>,
438 pub valid_entity_length: WebmachineCallback<bool>,
441 pub finish_request: WebmachineCallback<()>,
445 pub options: WebmachineCallback<Option<HashMap<String, Vec<String>>>>,
448 pub produces: Vec<String>,
452 pub languages_provided: Vec<String>,
456 pub charsets_provided: Vec<String>,
460 pub encodings_provided: Vec<String>,
463 pub variances: Vec<String>,
468 pub resource_exists: WebmachineCallback<bool>,
471 pub previously_existed: WebmachineCallback<bool>,
473 pub moved_permanently: WebmachineCallback<Option<String>>,
476 pub moved_temporarily: WebmachineCallback<Option<String>>,
479 pub is_conflict: WebmachineCallback<bool>,
482 pub allow_missing_post: WebmachineCallback<bool>,
484 pub generate_etag: WebmachineCallback<Option<String>>,
487 pub last_modified: WebmachineCallback<Option<DateTime<FixedOffset>>>,
491 pub delete_resource: WebmachineCallback<Result<bool, u16>>,
497 pub post_is_create: WebmachineCallback<bool>,
502 pub process_post: AsyncWebmachineCallback<Result<bool, u16>>,
508 pub create_path: WebmachineCallback<Result<String, u16>>,
517 pub process_put: WebmachineCallback<Result<bool, u16>>,
521 pub multiple_choices: WebmachineCallback<bool>,
525 pub expires: WebmachineCallback<Option<DateTime<FixedOffset>>>
527}
528
529fn true_fn(_: &mut WebmachineContext, _: &WebmachineResource) -> bool {
530 true
531}
532
533fn false_fn(_: &mut WebmachineContext, _: &WebmachineResource) -> bool {
534 false
535}
536
537fn none_fn<T>(_: &mut WebmachineContext, _: &WebmachineResource) -> Option<T> {
538 None
539}
540
541impl Default for WebmachineResource {
542 fn default() -> WebmachineResource {
543 WebmachineResource {
544 finalise_response: None,
545 available: callback(true_fn),
546 known_methods: owned_vec(&["OPTIONS", "GET", "POST", "PUT", "DELETE", "HEAD", "TRACE", "CONNECT", "PATCH"]),
547 uri_too_long: callback(false_fn),
548 allowed_methods: owned_vec(&["OPTIONS", "GET", "HEAD"]),
549 malformed_request: callback(false_fn),
550 not_authorized: callback(none_fn),
551 forbidden: callback(false_fn),
552 unsupported_content_headers: callback(false_fn),
553 acceptable_content_types: owned_vec(&["application/json"]),
554 valid_entity_length: callback(true_fn),
555 finish_request: callback(|context, resource| {
556 let methods = resource.allowed_methods.iter()
557 .map(|m| m.as_str())
558 .collect_vec();
559 context.response.add_cors_headers(methods.as_slice())
560 }),
561 options: callback(|_, resource| {
562 let methods = resource.allowed_methods.iter()
563 .map(|m| m.as_str())
564 .collect_vec();
565 Some(WebmachineResponse::cors_headers(methods.as_slice()))
566 }),
567 produces: vec!["application/json".to_string()],
568 languages_provided: Vec::new(),
569 charsets_provided: Vec::new(),
570 encodings_provided: vec!["identity".to_string()],
571 variances: Vec::new(),
572 resource_exists: callback(true_fn),
573 previously_existed: callback(false_fn),
574 moved_permanently: callback(none_fn),
575 moved_temporarily: callback(none_fn),
576 is_conflict: callback(false_fn),
577 allow_missing_post: callback(false_fn),
578 generate_etag: callback(none_fn),
579 last_modified: callback(none_fn),
580 delete_resource: callback(|_, _| Ok(true)),
581 post_is_create: callback(false_fn),
582 process_post: async_callback(|_, _| ready(Ok(false)).boxed()),
583 process_put: callback(|_, _| Ok(true)),
584 multiple_choices: callback(false_fn),
585 create_path: callback(|context, _| Ok(context.request.request_path.clone())),
586 expires: callback(none_fn),
587 render_response: async_callback(|_, _| ready(Ok(None)).boxed())
588 }
589 }
590}
591
592impl Debug for WebmachineResource {
593 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
594 write!(f, "WebmachineResource{{}}")
595 }
596}
597
598#[async_trait]
599impl Resource for WebmachineResource {
600 fn finalise_response(&self, context: &mut WebmachineContext) {
601 if let Some(callback) = &self.finalise_response {
602 callback(context, self);
603 }
604 }
605
606 async fn render_response(&self, context: &mut WebmachineContext) -> anyhow::Result<Option<Bytes>> {
607 (self.render_response)(context, self).await
608 }
609
610 fn available(&self, context: &mut WebmachineContext) -> bool {
611 (self.available)(context, self)
612 }
613
614 fn known_methods(&self) -> Vec<&str> {
615 self.known_methods
616 .iter()
617 .map(|s| s.as_str())
618 .collect()
619 }
620
621 fn uri_too_long(&self, context: &mut WebmachineContext) -> bool {
622 (self.uri_too_long)(context, self)
623 }
624
625 fn allowed_methods(&self) -> Vec<&str> {
626 self.allowed_methods
627 .iter()
628 .map(|s| s.as_str())
629 .collect()
630 }
631
632 fn malformed_request(&self, context: &mut WebmachineContext) -> bool {
633 (self.malformed_request)(context, self)
634 }
635
636 fn not_authorized(&self, context: &mut WebmachineContext) -> Option<String> {
637 (self.not_authorized)(context, self)
638 }
639
640 fn forbidden(&self, context: &mut WebmachineContext) -> bool {
641 (self.forbidden)(context, self)
642 }
643
644 fn unsupported_content_headers(&self, context: &mut WebmachineContext) -> bool {
645 (self.unsupported_content_headers)(context, self)
646 }
647
648 fn acceptable_content_types(&self, _context: &mut WebmachineContext) -> Vec<&str> {
649 self.acceptable_content_types
650 .iter()
651 .map(|s| s.as_str())
652 .collect_vec()
653 }
654
655 fn valid_entity_length(&self, context: &mut WebmachineContext) -> bool {
656 (self.valid_entity_length)(context, self)
657 }
658
659 fn finish_request(&self, context: &mut WebmachineContext) {
660 (self.finish_request)(context, self)
661 }
662
663 fn options(&self, context: &mut WebmachineContext) -> Option<HashMap<String, Vec<String>>> {
664 (self.options)(context, self)
665 }
666
667 fn produces(&self) -> Vec<&str> {
668 self.produces
669 .iter()
670 .map(|s| s.as_str())
671 .collect_vec()
672 }
673
674 fn languages_provided(&self) -> Vec<&str> {
675 self.languages_provided.iter()
676 .map(|s| s.as_str())
677 .collect_vec()
678 }
679
680 fn charsets_provided(&self) -> Vec<&str> {
681 self.charsets_provided.iter()
682 .map(|s| s.as_str())
683 .collect_vec()
684 }
685
686 fn encodings_provided(&self) -> Vec<&str> {
687 self.encodings_provided.iter()
688 .map(|s| s.as_str())
689 .collect_vec()
690 }
691
692 fn variances(&self) -> Vec<&str> {
693 self.variances.iter()
694 .map(|s| s.as_str())
695 .collect_vec()
696 }
697
698 async fn resource_exists(&self, context: &mut WebmachineContext) -> bool {
699 (self.resource_exists)(context, self)
700 }
701
702 fn previously_existed(&self, context: &mut WebmachineContext) -> bool {
703 (self.previously_existed)(context, self)
704 }
705
706 fn moved_permanently(&self, context: &mut WebmachineContext) -> Option<String> {
707 (self.moved_permanently)(context, self)
708 }
709
710 fn moved_temporarily(&self, context: &mut WebmachineContext) -> Option<String> {
711 (self.moved_temporarily)(context, self)
712 }
713
714 fn is_conflict(&self, context: &mut WebmachineContext) -> bool {
715 (self.is_conflict)(context, self)
716 }
717
718 fn allow_missing_post(&self, context: &mut WebmachineContext) -> bool {
719 (self.allow_missing_post)(context, self)
720 }
721
722 fn generate_etag(&self, context: &mut WebmachineContext) -> Option<String> {
723 (self.generate_etag)(context, self)
724 }
725
726 fn last_modified(&self, context: &mut WebmachineContext) -> Option<DateTime<FixedOffset>> {
727 (self.last_modified)(context, self)
728 }
729
730 async fn delete_resource(&self, context: &mut WebmachineContext) -> Result<bool, u16> {
731 (self.delete_resource)(context, self)
732 }
733
734 fn post_is_create(&self, context: &mut WebmachineContext) -> bool {
735 (self.post_is_create)(context, self)
736 }
737
738 async fn process_post(&self, context: &mut WebmachineContext) -> Result<bool, u16> {
739 (self.process_post)(context, self).await
740 }
741
742 async fn create_path(&self, context: &mut WebmachineContext) -> Result<String, u16> {
743 (self.create_path)(context, self)
744 }
745
746 async fn process_put(&self, context: &mut WebmachineContext) -> Result<bool, u16> {
747 (self.process_put)(context, self)
748 }
749
750 fn multiple_choices(&self, context: &mut WebmachineContext) -> bool {
751 (self.multiple_choices)(context, self)
752 }
753
754 fn expires(&self, context: &mut WebmachineContext) -> Option<DateTime<FixedOffset>> {
755 (self.expires)(context, self)
756 }
757}
758
759fn sanitise_path(path: &str) -> Vec<String> {
760 path.split("/").filter(|p| !p.is_empty()).map(|p| p.to_string()).collect()
761}
762
763fn join_paths(base: &Vec<String>, path: &Vec<String>) -> String {
764 let mut paths = base.clone();
765 paths.extend_from_slice(path);
766 let filtered: Vec<String> = paths.iter().cloned().filter(|p| !p.is_empty()).collect();
767 if filtered.is_empty() {
768 "/".to_string()
769 } else {
770 let new_path = filtered.iter().join("/");
771 if new_path.starts_with("/") {
772 new_path
773 } else {
774 "/".to_owned() + &new_path
775 }
776 }
777}
778
779const MAX_STATE_MACHINE_TRANSITIONS: u8 = 100;
780
781#[derive(Debug, Clone, PartialEq, Eq, Hash)]
782enum Decision {
783 Start,
784 End(u16),
785 A3Options,
786 B3Options,
787 B4RequestEntityTooLarge,
788 B5UnknownContentType,
789 B6UnsupportedContentHeader,
790 B7Forbidden,
791 B8Authorized,
792 B9MalformedRequest,
793 B10MethodAllowed,
794 B11UriTooLong,
795 B12KnownMethod,
796 B13Available,
797 C3AcceptExists,
798 C4AcceptableMediaTypeAvailable,
799 D4AcceptLanguageExists,
800 D5AcceptableLanguageAvailable,
801 E5AcceptCharsetExists,
802 E6AcceptableCharsetAvailable,
803 F6AcceptEncodingExists,
804 F7AcceptableEncodingAvailable,
805 G7ResourceExists,
806 G8IfMatchExists,
807 G9IfMatchStarExists,
808 G11EtagInIfMatch,
809 H7IfMatchStarExists,
810 H10IfUnmodifiedSinceExists,
811 H11IfUnmodifiedSinceValid,
812 H12LastModifiedGreaterThanUMS,
813 I4HasMovedPermanently,
814 I12IfNoneMatchExists,
815 I13IfNoneMatchStarExists,
816 I7Put,
817 J18GetHead,
818 K5HasMovedPermanently,
819 K7ResourcePreviouslyExisted,
820 K13ETagInIfNoneMatch,
821 L5HasMovedTemporarily,
822 L7Post,
823 L13IfModifiedSinceExists,
824 L14IfModifiedSinceValid,
825 L15IfModifiedSinceGreaterThanNow,
826 L17IfLastModifiedGreaterThanMS,
827 M5Post,
828 M7PostToMissingResource,
829 M16Delete,
830 M20DeleteEnacted,
831 N5PostToMissingResource,
832 N11Redirect,
833 N16Post,
834 O14Conflict,
835 O16Put,
836 O18MultipleRepresentations,
837 O20ResponseHasBody,
838 P3Conflict,
839 P11NewResource
840}
841
842impl Decision {
843 fn is_terminal(&self) -> bool {
844 match self {
845 &Decision::End(_) => true,
846 &Decision::A3Options => true,
847 _ => false
848 }
849 }
850}
851
852enum Transition {
853 To(Decision),
854 Branch(Decision, Decision)
855}
856
857#[derive(Debug, Clone, PartialEq, Eq, Hash)]
858enum DecisionResult {
859 True(String),
860 False(String),
861 StatusCode(u16)
862}
863
864impl DecisionResult {
865 fn wrap(result: bool, reason: &str) -> DecisionResult {
866 if result {
867 DecisionResult::True(format!("is: {}", reason))
868 } else {
869 DecisionResult::False(format!("is not: {}", reason))
870 }
871 }
872}
873
874lazy_static! {
875 static ref TRANSITION_MAP: HashMap<Decision, Transition> = hashmap!{
876 Decision::Start => Transition::To(Decision::B13Available),
877 Decision::B3Options => Transition::Branch(Decision::A3Options, Decision::C3AcceptExists),
878 Decision::B4RequestEntityTooLarge => Transition::Branch(Decision::End(413), Decision::B3Options),
879 Decision::B5UnknownContentType => Transition::Branch(Decision::End(415), Decision::B4RequestEntityTooLarge),
880 Decision::B6UnsupportedContentHeader => Transition::Branch(Decision::End(501), Decision::B5UnknownContentType),
881 Decision::B7Forbidden => Transition::Branch(Decision::End(403), Decision::B6UnsupportedContentHeader),
882 Decision::B8Authorized => Transition::Branch(Decision::B7Forbidden, Decision::End(401)),
883 Decision::B9MalformedRequest => Transition::Branch(Decision::End(400), Decision::B8Authorized),
884 Decision::B10MethodAllowed => Transition::Branch(Decision::B9MalformedRequest, Decision::End(405)),
885 Decision::B11UriTooLong => Transition::Branch(Decision::End(414), Decision::B10MethodAllowed),
886 Decision::B12KnownMethod => Transition::Branch(Decision::B11UriTooLong, Decision::End(501)),
887 Decision::B13Available => Transition::Branch(Decision::B12KnownMethod, Decision::End(503)),
888 Decision::C3AcceptExists => Transition::Branch(Decision::C4AcceptableMediaTypeAvailable, Decision::D4AcceptLanguageExists),
889 Decision::C4AcceptableMediaTypeAvailable => Transition::Branch(Decision::D4AcceptLanguageExists, Decision::End(406)),
890 Decision::D4AcceptLanguageExists => Transition::Branch(Decision::D5AcceptableLanguageAvailable, Decision::E5AcceptCharsetExists),
891 Decision::D5AcceptableLanguageAvailable => Transition::Branch(Decision::E5AcceptCharsetExists, Decision::End(406)),
892 Decision::E5AcceptCharsetExists => Transition::Branch(Decision::E6AcceptableCharsetAvailable, Decision::F6AcceptEncodingExists),
893 Decision::E6AcceptableCharsetAvailable => Transition::Branch(Decision::F6AcceptEncodingExists, Decision::End(406)),
894 Decision::F6AcceptEncodingExists => Transition::Branch(Decision::F7AcceptableEncodingAvailable, Decision::G7ResourceExists),
895 Decision::F7AcceptableEncodingAvailable => Transition::Branch(Decision::G7ResourceExists, Decision::End(406)),
896 Decision::G7ResourceExists => Transition::Branch(Decision::G8IfMatchExists, Decision::H7IfMatchStarExists),
897 Decision::G8IfMatchExists => Transition::Branch(Decision::G9IfMatchStarExists, Decision::H10IfUnmodifiedSinceExists),
898 Decision::G9IfMatchStarExists => Transition::Branch(Decision::H10IfUnmodifiedSinceExists, Decision::G11EtagInIfMatch),
899 Decision::G11EtagInIfMatch => Transition::Branch(Decision::H10IfUnmodifiedSinceExists, Decision::End(412)),
900 Decision::H7IfMatchStarExists => Transition::Branch(Decision::End(412), Decision::I7Put),
901 Decision::H10IfUnmodifiedSinceExists => Transition::Branch(Decision::H11IfUnmodifiedSinceValid, Decision::I12IfNoneMatchExists),
902 Decision::H11IfUnmodifiedSinceValid => Transition::Branch(Decision::H12LastModifiedGreaterThanUMS, Decision::I12IfNoneMatchExists),
903 Decision::H12LastModifiedGreaterThanUMS => Transition::Branch(Decision::End(412), Decision::I12IfNoneMatchExists),
904 Decision::I4HasMovedPermanently => Transition::Branch(Decision::End(301), Decision::P3Conflict),
905 Decision::I7Put => Transition::Branch(Decision::I4HasMovedPermanently, Decision::K7ResourcePreviouslyExisted),
906 Decision::I12IfNoneMatchExists => Transition::Branch(Decision::I13IfNoneMatchStarExists, Decision::L13IfModifiedSinceExists),
907 Decision::I13IfNoneMatchStarExists => Transition::Branch(Decision::J18GetHead, Decision::K13ETagInIfNoneMatch),
908 Decision::J18GetHead => Transition::Branch(Decision::End(304), Decision::End(412)),
909 Decision::K13ETagInIfNoneMatch => Transition::Branch(Decision::J18GetHead, Decision::L13IfModifiedSinceExists),
910 Decision::K5HasMovedPermanently => Transition::Branch(Decision::End(301), Decision::L5HasMovedTemporarily),
911 Decision::K7ResourcePreviouslyExisted => Transition::Branch(Decision::K5HasMovedPermanently, Decision::L7Post),
912 Decision::L5HasMovedTemporarily => Transition::Branch(Decision::End(307), Decision::M5Post),
913 Decision::L7Post => Transition::Branch(Decision::M7PostToMissingResource, Decision::End(404)),
914 Decision::L13IfModifiedSinceExists => Transition::Branch(Decision::L14IfModifiedSinceValid, Decision::M16Delete),
915 Decision::L14IfModifiedSinceValid => Transition::Branch(Decision::L15IfModifiedSinceGreaterThanNow, Decision::M16Delete),
916 Decision::L15IfModifiedSinceGreaterThanNow => Transition::Branch(Decision::M16Delete, Decision::L17IfLastModifiedGreaterThanMS),
917 Decision::L17IfLastModifiedGreaterThanMS => Transition::Branch(Decision::M16Delete, Decision::End(304)),
918 Decision::M5Post => Transition::Branch(Decision::N5PostToMissingResource, Decision::End(410)),
919 Decision::M7PostToMissingResource => Transition::Branch(Decision::N11Redirect, Decision::End(404)),
920 Decision::M16Delete => Transition::Branch(Decision::M20DeleteEnacted, Decision::N16Post),
921 Decision::M20DeleteEnacted => Transition::Branch(Decision::O20ResponseHasBody, Decision::End(202)),
922 Decision::N5PostToMissingResource => Transition::Branch(Decision::N11Redirect, Decision::End(410)),
923 Decision::N11Redirect => Transition::Branch(Decision::End(303), Decision::P11NewResource),
924 Decision::N16Post => Transition::Branch(Decision::N11Redirect, Decision::O16Put),
925 Decision::O14Conflict => Transition::Branch(Decision::End(409), Decision::P11NewResource),
926 Decision::O16Put => Transition::Branch(Decision::O14Conflict, Decision::O18MultipleRepresentations),
927 Decision::P3Conflict => Transition::Branch(Decision::End(409), Decision::P11NewResource),
928 Decision::P11NewResource => Transition::Branch(Decision::End(201), Decision::O20ResponseHasBody),
929 Decision::O18MultipleRepresentations => Transition::Branch(Decision::End(300), Decision::End(200)),
930 Decision::O20ResponseHasBody => Transition::Branch(Decision::O18MultipleRepresentations, Decision::End(204))
931 };
932}
933
934fn resource_etag_matches_header_values(
935 resource: &(dyn Resource + Send + Sync),
936 context: &mut WebmachineContext,
937 header: &str
938) -> bool {
939 let header_values = context.request.find_header(header);
940 match resource.generate_etag(context) {
941 Some(etag) => {
942 header_values.iter().find(|val| {
943 if let Some(weak_etag) = val.weak_etag() {
944 weak_etag == etag
945 } else {
946 val.value == etag
947 }
948 }).is_some()
949 },
950 None => false
951 }
952}
953
954fn validate_header_date(
955 request: &WebmachineRequest,
956 header: &str,
957 context_meta: &mut Option<DateTime<FixedOffset>>
958) -> bool {
959 let header_values = request.find_header(header);
960 if let Some(date_value) = header_values.first() {
961 match DateTime::parse_from_rfc2822(&date_value.value) {
962 Ok(datetime) => {
963 *context_meta = Some(datetime.clone());
964 true
965 },
966 Err(err) => {
967 debug!("Failed to parse '{}' header value '{:?}' - {}", header, date_value, err);
968 false
969 }
970 }
971 } else {
972 false
973 }
974}
975
976async fn execute_decision(
977 decision: &Decision,
978 context: &mut WebmachineContext,
979 resource: &(dyn Resource + Send + Sync)
980) -> DecisionResult {
981 match decision {
982 Decision::B10MethodAllowed => {
983 match resource.allowed_methods()
984 .iter().find(|m| m.to_uppercase() == context.request.method.to_uppercase()) {
985 Some(_) => DecisionResult::True("method is in the list of allowed methods".to_string()),
986 None => {
987 context.response.add_header("Allow", resource.allowed_methods()
988 .iter()
989 .map(|s| HeaderValue::basic(*s))
990 .collect());
991 DecisionResult::False("method is not in the list of allowed methods".to_string())
992 }
993 }
994 },
995 Decision::B11UriTooLong => {
996 DecisionResult::wrap(resource.uri_too_long(context), "URI too long")
997 },
998 Decision::B12KnownMethod => DecisionResult::wrap(resource.known_methods()
999 .iter().find(|m| m.to_uppercase() == context.request.method.to_uppercase()).is_some(),
1000 "known method"),
1001 Decision::B13Available => {
1002 DecisionResult::wrap(resource.available(context), "available")
1003 },
1004 Decision::B9MalformedRequest => {
1005 DecisionResult::wrap(resource.malformed_request(context), "malformed request")
1006 },
1007 Decision::B8Authorized => {
1008 match resource.not_authorized(context) {
1009 Some(realm) => {
1010 context.response.add_header("WWW-Authenticate", vec![HeaderValue::parse_string(realm.as_str())]);
1011 DecisionResult::False("is not authorized".to_string())
1012 },
1013 None => DecisionResult::True("is not authorized".to_string())
1014 }
1015 },
1016 Decision::B7Forbidden => {
1017 DecisionResult::wrap(resource.forbidden(context), "forbidden")
1018 },
1019 Decision::B6UnsupportedContentHeader => {
1020 DecisionResult::wrap(resource.unsupported_content_headers(context), "unsupported content headers")
1021 },
1022 Decision::B5UnknownContentType => {
1023 DecisionResult::wrap(context.request.is_put_or_post() && !acceptable_content_type(resource, context),
1024 "acceptable content types")
1025 },
1026 Decision::B4RequestEntityTooLarge => {
1027 DecisionResult::wrap(context.request.is_put_or_post() && !resource.valid_entity_length(context),
1028 "valid entity length")
1029 },
1030 Decision::B3Options => DecisionResult::wrap(context.request.is_options(), "options"),
1031 Decision::C3AcceptExists => DecisionResult::wrap(context.request.has_accept_header(), "has accept header"),
1032 Decision::C4AcceptableMediaTypeAvailable => match content_negotiation::matching_content_type(resource, &context.request) {
1033 Some(media_type) => {
1034 context.selected_media_type = Some(media_type);
1035 DecisionResult::True("acceptable media type is available".to_string())
1036 },
1037 None => DecisionResult::False("acceptable media type is not available".to_string())
1038 },
1039 Decision::D4AcceptLanguageExists => DecisionResult::wrap(context.request.has_accept_language_header(),
1040 "has accept language header"),
1041 Decision::D5AcceptableLanguageAvailable => match content_negotiation::matching_language(resource, &context.request) {
1042 Some(language) => {
1043 if language != "*" {
1044 context.selected_language = Some(language.clone());
1045 context.response.add_header("Content-Language", vec![HeaderValue::parse_string(&language)]);
1046 }
1047 DecisionResult::True("acceptable language is available".to_string())
1048 },
1049 None => DecisionResult::False("acceptable language is not available".to_string())
1050 },
1051 Decision::E5AcceptCharsetExists => DecisionResult::wrap(context.request.has_accept_charset_header(),
1052 "accept charset exists"),
1053 Decision::E6AcceptableCharsetAvailable => match content_negotiation::matching_charset(resource, &context.request) {
1054 Some(charset) => {
1055 if charset != "*" {
1056 context.selected_charset = Some(charset.clone());
1057 }
1058 DecisionResult::True("acceptable charset is available".to_string())
1059 },
1060 None => DecisionResult::False("acceptable charset is not available".to_string())
1061 },
1062 Decision::F6AcceptEncodingExists => DecisionResult::wrap(context.request.has_accept_encoding_header(),
1063 "accept encoding exists"),
1064 Decision::F7AcceptableEncodingAvailable => match content_negotiation::matching_encoding(resource, &context.request) {
1065 Some(encoding) => {
1066 context.selected_encoding = Some(encoding.clone());
1067 if encoding != "identity" {
1068 context.response.add_header("Content-Encoding", vec![HeaderValue::parse_string(&encoding)]);
1069 }
1070 DecisionResult::True("acceptable encoding is available".to_string())
1071 },
1072 None => DecisionResult::False("acceptable encoding is not available".to_string())
1073 },
1074 Decision::G7ResourceExists => {
1075 DecisionResult::wrap(resource.resource_exists(context).await, "resource exists")
1076 },
1077 Decision::G8IfMatchExists => DecisionResult::wrap(context.request.has_header("If-Match"),
1078 "match exists"),
1079 Decision::G9IfMatchStarExists | &Decision::H7IfMatchStarExists => DecisionResult::wrap(
1080 context.request.has_header_value("If-Match", "*"), "match star exists"),
1081 Decision::G11EtagInIfMatch => DecisionResult::wrap(resource_etag_matches_header_values(resource, context, "If-Match"),
1082 "etag in if match"),
1083 Decision::H10IfUnmodifiedSinceExists => DecisionResult::wrap(context.request.has_header("If-Unmodified-Since"),
1084 "unmodified since exists"),
1085 Decision::H11IfUnmodifiedSinceValid => DecisionResult::wrap(validate_header_date(&context.request, "If-Unmodified-Since", &mut context.if_unmodified_since),
1086 "unmodified since valid"),
1087 Decision::H12LastModifiedGreaterThanUMS => {
1088 match context.if_unmodified_since {
1089 Some(unmodified_since) => {
1090 match resource.last_modified(context) {
1091 Some(datetime) => DecisionResult::wrap(datetime > unmodified_since,
1092 "resource last modified date is greater than unmodified since"),
1093 None => DecisionResult::False("resource has no last modified date".to_string())
1094 }
1095 },
1096 None => DecisionResult::False("resource does not provide last modified date".to_string())
1097 }
1098 },
1099 Decision::I7Put => if context.request.is_put() {
1100 context.new_resource = true;
1101 DecisionResult::True("is a PUT request".to_string())
1102 } else {
1103 DecisionResult::False("is not a PUT request".to_string())
1104 },
1105 Decision::I12IfNoneMatchExists => DecisionResult::wrap(context.request.has_header("If-None-Match"),
1106 "none match exists"),
1107 Decision::I13IfNoneMatchStarExists => DecisionResult::wrap(context.request.has_header_value("If-None-Match", "*"),
1108 "none match star exists"),
1109 Decision::J18GetHead => DecisionResult::wrap(context.request.is_get_or_head(),
1110 "is GET or HEAD request"),
1111 Decision::K7ResourcePreviouslyExisted => {
1112 DecisionResult::wrap(resource.previously_existed(context), "resource previously existed")
1113 },
1114 Decision::K13ETagInIfNoneMatch => DecisionResult::wrap(resource_etag_matches_header_values(resource, context, "If-None-Match"),
1115 "ETag in if none match"),
1116 Decision::L5HasMovedTemporarily => {
1117 match resource.moved_temporarily(context) {
1118 Some(location) => {
1119 context.response.add_header("Location", vec![HeaderValue::basic(&location)]);
1120 DecisionResult::True("resource has moved temporarily".to_string())
1121 },
1122 None => DecisionResult::False("resource has not moved temporarily".to_string())
1123 }
1124 },
1125 Decision::L7Post | &Decision::M5Post | &Decision::N16Post => DecisionResult::wrap(context.request.is_post(),
1126 "a POST request"),
1127 Decision::L13IfModifiedSinceExists => DecisionResult::wrap(context.request.has_header("If-Modified-Since"),
1128 "if modified since exists"),
1129 Decision::L14IfModifiedSinceValid => DecisionResult::wrap(validate_header_date(&context.request,
1130 "If-Modified-Since", &mut context.if_modified_since), "modified since valid"),
1131 Decision::L15IfModifiedSinceGreaterThanNow => {
1132 let datetime = context.if_modified_since.unwrap();
1133 let timezone = datetime.timezone();
1134 DecisionResult::wrap(datetime > Utc::now().with_timezone(&timezone),
1135 "modified since greater than now")
1136 },
1137 Decision::L17IfLastModifiedGreaterThanMS => {
1138 match context.if_modified_since {
1139 Some(unmodified_since) => {
1140 match resource.last_modified(context) {
1141 Some(datetime) => DecisionResult::wrap(datetime > unmodified_since,
1142 "last modified greater than modified since"),
1143 None => DecisionResult::False("resource has no last modified date".to_string())
1144 }
1145 },
1146 None => DecisionResult::False("resource does not return if_modified_since".to_string())
1147 }
1148 },
1149 Decision::I4HasMovedPermanently | &Decision::K5HasMovedPermanently => {
1150 match resource.moved_permanently(context) {
1151 Some(location) => {
1152 context.response.add_header("Location", vec![HeaderValue::basic(&location)]);
1153 DecisionResult::True("resource has moved permanently".to_string())
1154 },
1155 None => DecisionResult::False("resource has not moved permanently".to_string())
1156 }
1157 },
1158 Decision::M7PostToMissingResource | &Decision::N5PostToMissingResource => {
1159 if resource.allow_missing_post(context) {
1160 context.new_resource = true;
1161 DecisionResult::True("resource allows POST to missing resource".to_string())
1162 } else {
1163 DecisionResult::False("resource does not allow POST to missing resource".to_string())
1164 }
1165 },
1166 Decision::M16Delete => DecisionResult::wrap(context.request.is_delete(),
1167 "a DELETE request"),
1168 Decision::M20DeleteEnacted => {
1169 match resource.delete_resource(context).await {
1170 Ok(result) => DecisionResult::wrap(result, "resource DELETE succeeded"),
1171 Err(status) => DecisionResult::StatusCode(status)
1172 }
1173 },
1174 Decision::N11Redirect => {
1175 if resource.post_is_create(context) {
1176 match resource.create_path(context).await {
1177 Ok(path) => {
1178 let base_path = sanitise_path(&context.request.base_path);
1179 let new_path = join_paths(&base_path, &sanitise_path(&path));
1180 context.request.request_path = path.clone();
1181 context.response.add_header("Location", vec![HeaderValue::basic(&new_path)]);
1182 DecisionResult::wrap(context.redirect, "should redirect")
1183 },
1184 Err(status) => DecisionResult::StatusCode(status)
1185 }
1186 } else {
1187 match resource.process_post(context).await {
1188 Ok(_) => DecisionResult::wrap(context.redirect, "processing POST succeeded, should redirect?"),
1189 Err(status) => DecisionResult::StatusCode(status)
1190 }
1191 }
1192 },
1193 Decision::P3Conflict | &Decision::O14Conflict => {
1194 DecisionResult::wrap(resource.is_conflict(context), "resource conflict")
1195 },
1196 Decision::P11NewResource => {
1197 if context.request.is_put() {
1198 match resource.process_put(context).await {
1199 Ok(_) => DecisionResult::wrap(context.new_resource, "process PUT succeeded"),
1200 Err(status) => DecisionResult::StatusCode(status)
1201 }
1202 } else {
1203 DecisionResult::wrap(context.new_resource, "is new resource?")
1204 }
1205 },
1206 Decision::O16Put => DecisionResult::wrap(context.request.is_put(), "a PUT request"),
1207 Decision::O18MultipleRepresentations => {
1208 DecisionResult::wrap(resource.multiple_choices(context), "multiple choices exist")
1209 },
1210 Decision::O20ResponseHasBody => DecisionResult::wrap(context.response.has_body(), "response has a body"),
1211 _ => DecisionResult::False("default decision is false".to_string())
1212 }
1213}
1214
1215async fn execute_state_machine(
1216 context: &mut WebmachineContext,
1217 resource: &(dyn Resource + Send + Sync)
1218) {
1219 let mut state = Decision::Start;
1220 let mut decisions: Vec<(Decision, bool, Decision)> = Vec::new();
1221 let mut loop_count = 0;
1222 while !state.is_terminal() {
1223 loop_count += 1;
1224 if loop_count >= MAX_STATE_MACHINE_TRANSITIONS {
1225 panic!("State machine has not terminated within {} transitions!", loop_count);
1226 }
1227 trace!("state is {:?}", state);
1228 trace!("context is {:?}", context);
1229 state = match TRANSITION_MAP.get(&state) {
1230 Some(transition) => match transition {
1231 Transition::To(decision) => {
1232 trace!("Transitioning to {:?}", decision);
1233 decision.clone()
1234 },
1235 Transition::Branch(decision_true, decision_false) => {
1236 match execute_decision(&state, context, resource).await {
1237 DecisionResult::True(reason) => {
1238 trace!("Transitioning from {:?} to {:?} as decision is true -> {}", state, decision_true, reason);
1239 decisions.push((state, true, decision_true.clone()));
1240 decision_true.clone()
1241 },
1242 DecisionResult::False(reason) => {
1243 trace!("Transitioning from {:?} to {:?} as decision is false -> {}", state, decision_false, reason);
1244 decisions.push((state, false, decision_false.clone()));
1245 decision_false.clone()
1246 },
1247 DecisionResult::StatusCode(code) => {
1248 let decision = Decision::End(code);
1249 trace!("Transitioning from {:?} to {:?} as decision is a status code", state, decision);
1250 decisions.push((state, false, decision.clone()));
1251 decision.clone()
1252 }
1253 }
1254 }
1255 },
1256 None => {
1257 error!("Error transitioning from {:?}, the TRANSITION_MAP is mis-configured", state);
1258 decisions.push((state, false, Decision::End(500)));
1259 Decision::End(500)
1260 }
1261 }
1262 }
1263 trace!("Final state is {:?}", state);
1264 match state {
1265 Decision::End(status) => context.response.status = status,
1266 Decision::A3Options => {
1267 context.response.status = 204;
1268 match resource.options(context) {
1269 Some(headers) => context.response.add_headers(headers),
1270 None => ()
1271 }
1272 },
1273 _ => ()
1274 }
1275}
1276
1277fn update_paths_for_resource(
1278 request: &mut WebmachineRequest,
1279 base_path: &str,
1280 mapped_parts: &Vec<(String, Option<String>)>
1281) {
1282 request.base_path = base_path.into();
1283 request.path_vars = mapped_parts.iter()
1284 .filter_map(|(part, id)| id.as_ref().map(|id| (id.clone(), part.clone())))
1285 .collect();
1286 let base_parts = base_path.split('/').count() - 1;
1287 let sub_parts = mapped_parts.iter()
1288 .dropping(base_parts)
1289 .map(|(part, _)| part)
1290 .collect_vec();
1291 request.sub_path = if sub_parts.is_empty() {
1292 None
1293 } else {
1294 Some(sub_parts.iter().join("/"))
1295 };
1296}
1297
1298fn parse_header_values(value: &str) -> Vec<HeaderValue> {
1299 if value.is_empty() {
1300 Vec::new()
1301 } else {
1302 value.split(',').map(|s| HeaderValue::parse_string(s.trim())).collect()
1303 }
1304}
1305
1306fn headers_from_http_request(headers: &HeaderMap<http::HeaderValue>) -> HashMap<String, Vec<HeaderValue>> {
1307 headers.keys()
1308 .map(|key| (key.to_string(), headers.get_all(key)
1309 .iter()
1310 .flat_map(|value| parse_header_values(value.to_str().unwrap_or_default()))
1311 .collect_vec()
1312 ))
1313 .collect()
1314}
1315
1316fn decode_query(query: &str) -> String {
1317 let mut chars = query.chars();
1318 let mut ch = chars.next();
1319 let mut result = String::new();
1320
1321 while ch.is_some() {
1322 let c = ch.unwrap();
1323 if c == '%' {
1324 let c1 = chars.next();
1325 let c2 = chars.next();
1326 match (c1, c2) {
1327 (Some(v1), Some(v2)) => {
1328 let mut s = String::new();
1329 s.push(v1);
1330 s.push(v2);
1331 let decoded: Result<Vec<u8>, _> = hex::decode(s);
1332 match decoded {
1333 Ok(n) => result.push(n[0] as char),
1334 Err(_) => {
1335 result.push('%');
1336 result.push(v1);
1337 result.push(v2);
1338 }
1339 }
1340 },
1341 (Some(v1), None) => {
1342 result.push('%');
1343 result.push(v1);
1344 },
1345 _ => result.push('%')
1346 }
1347 } else if c == '+' {
1348 result.push(' ');
1349 } else {
1350 result.push(c);
1351 }
1352
1353 ch = chars.next();
1354 }
1355
1356 result
1357}
1358
1359fn parse_query(query: &str) -> HashMap<String, Vec<String>> {
1360 if !query.is_empty() {
1361 query.split("&").map(|kv| {
1362 if kv.is_empty() {
1363 vec![]
1364 } else if kv.contains("=") {
1365 kv.splitn(2, "=").collect::<Vec<&str>>()
1366 } else {
1367 vec![kv]
1368 }
1369 }).fold(HashMap::new(), |mut map, name_value| {
1370 if !name_value.is_empty() {
1371 let name = decode_query(name_value[0]);
1372 let value = if name_value.len() > 1 {
1373 decode_query(name_value[1])
1374 } else {
1375 String::new()
1376 };
1377 map.entry(name).or_insert(vec![]).push(value);
1378 }
1379 map
1380 })
1381 } else {
1382 HashMap::new()
1383 }
1384}
1385
1386async fn request_from_http_request<BODY, E>(req: Request<BODY>) -> WebmachineRequest
1387 where BODY: Body<Error = E>,
1388 E: Display
1389{
1390 let request_path = req.uri().path().to_string();
1391 let method = req.method().to_string();
1392 let query = match req.uri().query() {
1393 Some(query) => parse_query(query),
1394 None => HashMap::new()
1395 };
1396 let headers = headers_from_http_request(req.headers());
1397
1398 let body = match req.collect().await {
1399 Ok(body) => {
1400 let body = body.to_bytes();
1401 if body.is_empty() {
1402 None
1403 } else {
1404 Some(body.clone())
1405 }
1406 }
1407 Err(err) => {
1408 error!("Failed to read the request body: {}", err);
1409 None
1410 }
1411 };
1412
1413 WebmachineRequest {
1414 request_path,
1415 base_path: "/".to_string(),
1416 sub_path: None,
1417 path_vars: Default::default(),
1418 method,
1419 headers,
1420 body,
1421 query
1422 }
1423}
1424
1425async fn finalise_response(context: &mut WebmachineContext, resource: &(dyn Resource + Send + Sync)) {
1426 if !context.response.has_header("Content-Type") {
1427 let media_type = match &context.selected_media_type {
1428 &Some(ref media_type) => media_type.clone(),
1429 &None => "application/json".to_string()
1430 };
1431 let charset = match &context.selected_charset {
1432 &Some(ref charset) => charset.clone(),
1433 &None => "ISO-8859-1".to_string()
1434 };
1435 let header = HeaderValue {
1436 value: media_type,
1437 params: hashmap!{ "charset".to_string() => charset },
1438 quote: false
1439 };
1440 context.response.add_header("Content-Type", vec![header]);
1441 }
1442
1443 let mut vary_header = if !context.response.has_header("Vary") {
1444 resource.variances()
1445 .iter()
1446 .map(|h| HeaderValue::parse_string(h))
1447 .collect()
1448 } else {
1449 Vec::new()
1450 };
1451
1452 if resource.languages_provided().len() > 1 {
1453 vary_header.push(h!("Accept-Language"));
1454 }
1455 if resource.charsets_provided().len() > 1 {
1456 vary_header.push(h!("Accept-Charset"));
1457 }
1458 if resource.encodings_provided().len() > 1 {
1459 vary_header.push(h!("Accept-Encoding"));
1460 }
1461 if resource.produces().len() > 1 {
1462 vary_header.push(h!("Accept"));
1463 }
1464
1465 if vary_header.len() > 1 {
1466 context.response.add_header("Vary", vary_header.iter().cloned().unique().collect());
1467 }
1468
1469 if context.request.is_get_or_head() {
1470 if let Some(etag) = resource.generate_etag(context) {
1471 context.response.add_header("ETag", vec![HeaderValue::basic(&etag).quote()]);
1472 }
1473 if let Some(datetime) = resource.expires(context) {
1474 context.response.add_header("Expires", vec![HeaderValue::basic(datetime.to_rfc2822()).quote()]);
1475 }
1476 if let Some(datetime) = resource.last_modified(context) {
1477 context.response.add_header("Last-Modified", vec![HeaderValue::basic(datetime.to_rfc2822()).quote()]);
1478 }
1479 }
1480
1481 if context.response.body.is_none() && context.response.status == 200 && context.request.is_get() {
1482 match resource.render_response(context).await {
1483 Ok(Some(body)) => context.response.body = Some(body),
1484 Ok(None) => (),
1485 Err(err) => {
1486 error!("render_response failed with an error: {}", err);
1487 context.response.status = 500;
1488 context.response.body = Some(Bytes::from(err.to_string()));
1489 }
1490 }
1491 }
1492
1493 resource.finish_request(context);
1494 resource.finalise_response(context);
1495
1496 if let Ok(duration) = context.start_time.elapsed() {
1497 context.response.add_header("Server-Timing", vec![HeaderValue {
1498 value: "response".to_string(),
1499 params: hashmap!{
1500 "desc".to_string() => "Total Response Time".to_string(),
1501 "dur".to_string() => format!("{}", duration.as_secs_f64())
1502 },
1503 quote: true
1504 }])
1505 }
1506
1507 let body_size = context.response.body.as_ref().map(|bytes| bytes.len()).unwrap_or_default();
1508 debug!(status = context.response.status, headers = ?context.response.headers, body_size, "Final response");
1509}
1510
1511fn generate_http_response(context: &WebmachineContext) -> http::Result<Response<Full<Bytes>>> {
1512 let mut response = Response::builder().status(context.response.status);
1513
1514 for (header, values) in context.response.headers.clone() {
1515 let header_values = values.iter().map(|h| h.to_string()).join(", ");
1516 response = response.header(&header, &header_values);
1517 }
1518 match context.response.body.clone() {
1519 Some(body) => response.body(Full::new(body.into())),
1520 None => response.body(Full::new(Bytes::default()))
1521 }
1522}
1523
1524pub struct WebmachineDispatcher {
1526 pub routes: BTreeMap<&'static str, Box<dyn Resource + Send + Sync>>
1528}
1529
1530impl WebmachineDispatcher {
1531 pub async fn dispatch(&self, req: Request<Incoming>) -> http::Result<Response<Full<Bytes>>> {
1534 let mut context = self.context_from_http_request(req).await;
1535 self.dispatch_to_resource(&mut context).await;
1536 generate_http_response(&context)
1537 }
1538
1539 async fn context_from_http_request(&self, req: Request<Incoming>) -> WebmachineContext {
1540 let now = SystemTime::now();
1541 let request = request_from_http_request(req).await;
1542 WebmachineContext {
1543 request,
1544 response: WebmachineResponse::default(),
1545 start_time: now,
1546 .. WebmachineContext::default()
1547 }
1548 }
1549
1550 fn match_paths(&self, request: &WebmachineRequest) -> Vec<(String, Vec<(String, Option<String>)>)> {
1551 self.routes
1552 .keys()
1553 .filter_map(|k| map_path(request.request_path.as_str(), k)
1554 .map(|result| (k.to_string(), result)))
1555 .collect()
1556 }
1557
1558 fn lookup_resource(&self, path: &str) -> Option<&(dyn Resource + Send + Sync)> {
1559 self.routes.get(path)
1560 .map(|resource| resource.as_ref())
1561 }
1562
1563 pub async fn dispatch_to_resource(&self, context: &mut WebmachineContext) {
1566 let body_size = context.request.body.as_ref().map(|bytes| bytes.len()).unwrap_or_default();
1567 debug!(method = context.request.method, request_path = context.request.request_path,
1568 headers = ?context.request.headers, query = ?context.request.query, body_size,
1569 "Incoming request");
1570 let matching_paths = self.match_paths(&context.request);
1571 let ordered_by_length = matching_paths.iter()
1572 .cloned()
1573 .sorted_by(|(a, _), (b, _)| Ord::cmp(&b.len(), &a.len()))
1574 .collect_vec();
1575 match ordered_by_length.first() {
1576 Some((path, parts)) => {
1577 update_paths_for_resource(&mut context.request, path, parts);
1578 if let Some(resource) = self.lookup_resource(path) {
1579 trace!("Dispatching to resource {:?}", resource);
1580 execute_state_machine(context, resource).await;
1581 finalise_response(context, resource).await;
1582 } else {
1583 context.response.status = 404;
1584 }
1585 },
1586 None => context.response.status = 404
1587 };
1588 }
1589
1590 pub fn box_resource<R: Resource + Send + Sync + 'static>(resource: R) -> Box<dyn Resource + Send + Sync> {
1592 Box::new(resource)
1593 }
1594}
1595
1596#[cfg(test)]
1597mod tests;
1598
1599#[cfg(test)]
1600mod content_negotiation_tests;