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"),
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, "new resource creation succeeded")
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 state = match TRANSITION_MAP.get(&state) {
1229 Some(transition) => match transition {
1230 Transition::To(decision) => {
1231 trace!("Transitioning to {:?}", decision);
1232 decision.clone()
1233 },
1234 Transition::Branch(decision_true, decision_false) => {
1235 match execute_decision(&state, context, resource).await {
1236 DecisionResult::True(reason) => {
1237 trace!("Transitioning from {:?} to {:?} as decision is true -> {}", state, decision_true, reason);
1238 decisions.push((state, true, decision_true.clone()));
1239 decision_true.clone()
1240 },
1241 DecisionResult::False(reason) => {
1242 trace!("Transitioning from {:?} to {:?} as decision is false -> {}", state, decision_false, reason);
1243 decisions.push((state, false, decision_false.clone()));
1244 decision_false.clone()
1245 },
1246 DecisionResult::StatusCode(code) => {
1247 let decision = Decision::End(code);
1248 trace!("Transitioning from {:?} to {:?} as decision is a status code", state, decision);
1249 decisions.push((state, false, decision.clone()));
1250 decision.clone()
1251 }
1252 }
1253 }
1254 },
1255 None => {
1256 error!("Error transitioning from {:?}, the TRANSITION_MAP is mis-configured", state);
1257 decisions.push((state, false, Decision::End(500)));
1258 Decision::End(500)
1259 }
1260 }
1261 }
1262 trace!("Final state is {:?}", state);
1263 match state {
1264 Decision::End(status) => context.response.status = status,
1265 Decision::A3Options => {
1266 context.response.status = 204;
1267 match resource.options(context) {
1268 Some(headers) => context.response.add_headers(headers),
1269 None => ()
1270 }
1271 },
1272 _ => ()
1273 }
1274}
1275
1276fn update_paths_for_resource(
1277 request: &mut WebmachineRequest,
1278 base_path: &str,
1279 mapped_parts: &Vec<(String, Option<String>)>
1280) {
1281 request.base_path = base_path.into();
1282 request.path_vars = mapped_parts.iter()
1283 .filter_map(|(part, id)| id.as_ref().map(|id| (id.clone(), part.clone())))
1284 .collect();
1285 let base_parts = base_path.split('/').count() - 1;
1286 let sub_parts = mapped_parts.iter()
1287 .dropping(base_parts)
1288 .map(|(part, _)| part)
1289 .collect_vec();
1290 request.sub_path = if sub_parts.is_empty() {
1291 None
1292 } else {
1293 Some(sub_parts.iter().join("/"))
1294 };
1295}
1296
1297fn parse_header_values(value: &str) -> Vec<HeaderValue> {
1298 if value.is_empty() {
1299 Vec::new()
1300 } else {
1301 value.split(',').map(|s| HeaderValue::parse_string(s.trim())).collect()
1302 }
1303}
1304
1305fn headers_from_http_request(headers: &HeaderMap<http::HeaderValue>) -> HashMap<String, Vec<HeaderValue>> {
1306 headers.keys()
1307 .map(|key| (key.to_string(), headers.get_all(key)
1308 .iter()
1309 .flat_map(|value| parse_header_values(value.to_str().unwrap_or_default()))
1310 .collect_vec()
1311 ))
1312 .collect()
1313}
1314
1315fn decode_query(query: &str) -> String {
1316 let mut chars = query.chars();
1317 let mut ch = chars.next();
1318 let mut result = String::new();
1319
1320 while ch.is_some() {
1321 let c = ch.unwrap();
1322 if c == '%' {
1323 let c1 = chars.next();
1324 let c2 = chars.next();
1325 match (c1, c2) {
1326 (Some(v1), Some(v2)) => {
1327 let mut s = String::new();
1328 s.push(v1);
1329 s.push(v2);
1330 let decoded: Result<Vec<u8>, _> = hex::decode(s);
1331 match decoded {
1332 Ok(n) => result.push(n[0] as char),
1333 Err(_) => {
1334 result.push('%');
1335 result.push(v1);
1336 result.push(v2);
1337 }
1338 }
1339 },
1340 (Some(v1), None) => {
1341 result.push('%');
1342 result.push(v1);
1343 },
1344 _ => result.push('%')
1345 }
1346 } else if c == '+' {
1347 result.push(' ');
1348 } else {
1349 result.push(c);
1350 }
1351
1352 ch = chars.next();
1353 }
1354
1355 result
1356}
1357
1358fn parse_query(query: &str) -> HashMap<String, Vec<String>> {
1359 if !query.is_empty() {
1360 query.split("&").map(|kv| {
1361 if kv.is_empty() {
1362 vec![]
1363 } else if kv.contains("=") {
1364 kv.splitn(2, "=").collect::<Vec<&str>>()
1365 } else {
1366 vec![kv]
1367 }
1368 }).fold(HashMap::new(), |mut map, name_value| {
1369 if !name_value.is_empty() {
1370 let name = decode_query(name_value[0]);
1371 let value = if name_value.len() > 1 {
1372 decode_query(name_value[1])
1373 } else {
1374 String::new()
1375 };
1376 map.entry(name).or_insert(vec![]).push(value);
1377 }
1378 map
1379 })
1380 } else {
1381 HashMap::new()
1382 }
1383}
1384
1385async fn request_from_http_request<BODY, E>(req: Request<BODY>) -> WebmachineRequest
1386 where BODY: Body<Error = E>,
1387 E: Display
1388{
1389 let request_path = req.uri().path().to_string();
1390 let method = req.method().to_string();
1391 let query = match req.uri().query() {
1392 Some(query) => parse_query(query),
1393 None => HashMap::new()
1394 };
1395 let headers = headers_from_http_request(req.headers());
1396
1397 let body = match req.collect().await {
1398 Ok(body) => {
1399 let body = body.to_bytes();
1400 if body.is_empty() {
1401 None
1402 } else {
1403 Some(body.clone())
1404 }
1405 }
1406 Err(err) => {
1407 error!("Failed to read the request body: {}", err);
1408 None
1409 }
1410 };
1411
1412 WebmachineRequest {
1413 request_path,
1414 base_path: "/".to_string(),
1415 sub_path: None,
1416 path_vars: Default::default(),
1417 method,
1418 headers,
1419 body,
1420 query
1421 }
1422}
1423
1424async fn finalise_response(context: &mut WebmachineContext, resource: &(dyn Resource + Send + Sync)) {
1425 if !context.response.has_header("Content-Type") {
1426 let media_type = match &context.selected_media_type {
1427 &Some(ref media_type) => media_type.clone(),
1428 &None => "application/json".to_string()
1429 };
1430 let charset = match &context.selected_charset {
1431 &Some(ref charset) => charset.clone(),
1432 &None => "ISO-8859-1".to_string()
1433 };
1434 let header = HeaderValue {
1435 value: media_type,
1436 params: hashmap!{ "charset".to_string() => charset },
1437 quote: false
1438 };
1439 context.response.add_header("Content-Type", vec![header]);
1440 }
1441
1442 let mut vary_header = if !context.response.has_header("Vary") {
1443 resource.variances()
1444 .iter()
1445 .map(|h| HeaderValue::parse_string(h))
1446 .collect()
1447 } else {
1448 Vec::new()
1449 };
1450
1451 if resource.languages_provided().len() > 1 {
1452 vary_header.push(h!("Accept-Language"));
1453 }
1454 if resource.charsets_provided().len() > 1 {
1455 vary_header.push(h!("Accept-Charset"));
1456 }
1457 if resource.encodings_provided().len() > 1 {
1458 vary_header.push(h!("Accept-Encoding"));
1459 }
1460 if resource.produces().len() > 1 {
1461 vary_header.push(h!("Accept"));
1462 }
1463
1464 if vary_header.len() > 1 {
1465 context.response.add_header("Vary", vary_header.iter().cloned().unique().collect());
1466 }
1467
1468 if context.request.is_get_or_head() {
1469 if let Some(etag) = resource.generate_etag(context) {
1470 context.response.add_header("ETag", vec![HeaderValue::basic(&etag).quote()]);
1471 }
1472 if let Some(datetime) = resource.expires(context) {
1473 context.response.add_header("Expires", vec![HeaderValue::basic(datetime.to_rfc2822()).quote()]);
1474 }
1475 if let Some(datetime) = resource.last_modified(context) {
1476 context.response.add_header("Last-Modified", vec![HeaderValue::basic(datetime.to_rfc2822()).quote()]);
1477 }
1478 }
1479
1480 if context.response.body.is_none() && context.response.status == 200 && context.request.is_get() {
1481 match resource.render_response(context).await {
1482 Ok(Some(body)) => context.response.body = Some(body),
1483 Ok(None) => (),
1484 Err(err) => {
1485 error!("render_response failed with an error: {}", err);
1486 context.response.status = 500;
1487 context.response.body = Some(Bytes::from(err.to_string()));
1488 }
1489 }
1490 }
1491
1492 resource.finish_request(context);
1493 resource.finalise_response(context);
1494
1495 if let Ok(duration) = context.start_time.elapsed() {
1496 context.response.add_header("Server-Timing", vec![HeaderValue {
1497 value: "response".to_string(),
1498 params: hashmap!{
1499 "desc".to_string() => "Total Response Time".to_string(),
1500 "dur".to_string() => format!("{:?}", duration)
1501 },
1502 quote: true
1503 }])
1504 }
1505
1506 let body_size = context.response.body.as_ref().map(|bytes| bytes.len()).unwrap_or_default();
1507 debug!(status = context.response.status, headers = ?context.response.headers, body_size, "Final response");
1508}
1509
1510fn generate_http_response(context: &WebmachineContext) -> http::Result<Response<Full<Bytes>>> {
1511 let mut response = Response::builder().status(context.response.status);
1512
1513 for (header, values) in context.response.headers.clone() {
1514 let header_values = values.iter().map(|h| h.to_string()).join(", ");
1515 response = response.header(&header, &header_values);
1516 }
1517 match context.response.body.clone() {
1518 Some(body) => response.body(Full::new(body.into())),
1519 None => response.body(Full::new(Bytes::default()))
1520 }
1521}
1522
1523pub struct WebmachineDispatcher {
1525 pub routes: BTreeMap<&'static str, Box<dyn Resource + Send + Sync>>
1527}
1528
1529impl WebmachineDispatcher {
1530 pub async fn dispatch(&self, req: Request<Incoming>) -> http::Result<Response<Full<Bytes>>> {
1533 let mut context = self.context_from_http_request(req).await;
1534 self.dispatch_to_resource(&mut context).await;
1535 generate_http_response(&context)
1536 }
1537
1538 async fn context_from_http_request(&self, req: Request<Incoming>) -> WebmachineContext {
1539 let now = SystemTime::now();
1540 let request = request_from_http_request(req).await;
1541 WebmachineContext {
1542 request,
1543 response: WebmachineResponse::default(),
1544 start_time: now,
1545 .. WebmachineContext::default()
1546 }
1547 }
1548
1549 fn match_paths(&self, request: &WebmachineRequest) -> Vec<(String, Vec<(String, Option<String>)>)> {
1550 self.routes
1551 .keys()
1552 .filter_map(|k| map_path(request.request_path.as_str(), k)
1553 .map(|result| (k.to_string(), result)))
1554 .collect()
1555 }
1556
1557 fn lookup_resource(&self, path: &str) -> Option<&(dyn Resource + Send + Sync)> {
1558 self.routes.get(path)
1559 .map(|resource| resource.as_ref())
1560 }
1561
1562 pub async fn dispatch_to_resource(&self, context: &mut WebmachineContext) {
1565 let body_size = context.request.body.as_ref().map(|bytes| bytes.len()).unwrap_or_default();
1566 debug!(method = context.request.method, request_path = context.request.request_path,
1567 headers = ?context.request.headers, query = ?context.request.query, body_size,
1568 "Incoming request");
1569 let matching_paths = self.match_paths(&context.request);
1570 let ordered_by_length = matching_paths.iter()
1571 .cloned()
1572 .sorted_by(|(a, _), (b, _)| Ord::cmp(&b.len(), &a.len()))
1573 .collect_vec();
1574 match ordered_by_length.first() {
1575 Some((path, parts)) => {
1576 update_paths_for_resource(&mut context.request, path, parts);
1577 if let Some(resource) = self.lookup_resource(path) {
1578 trace!("Dispatching to resource {:?}", resource);
1579 execute_state_machine(context, resource).await;
1580 finalise_response(context, resource).await;
1581 } else {
1582 context.response.status = 404;
1583 }
1584 },
1585 None => context.response.status = 404
1586 };
1587 }
1588
1589 pub fn box_resource<R: Resource + Send + Sync + 'static>(resource: R) -> Box<dyn Resource + Send + Sync> {
1591 Box::new(resource)
1592 }
1593}
1594
1595#[cfg(test)]
1596mod tests;
1597
1598#[cfg(test)]
1599mod content_negotiation_tests;