virtual_hosts_module/
lib.rs

1// Copyright 2024 Wladimir Palant
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! # Virtual Hosts Module for Pingora
16//!
17//! This module simplifies dealing with virtual hosts. It wraps any handler implementing
18//! [`module_utils::RequestFilter`] and its configuration, allowing to supply a different
19//! configuration for that handler for each virtual host and subdirectories of that host. For
20//! example, if Static Files Module is the wrapped handler, the configuration file might look like this:
21//!
22//! ```yaml
23//! vhosts:
24//!     localhost:8000:
25//!         aliases:
26//!             - 127.0.0.1:8000
27//!             - "[::1]:8000"
28//!         root: ./local-debug-root
29//!     example.com:
30//!         aliases:
31//!             - www.example.com
32//!         default: true
33//!         root: ./production-root
34//!         subdirs:
35//!             /metrics
36//!                 root: ./metrics
37//!             /test:
38//!                 strip_prefix: true
39//!                 root: ./local-debug-root
40//!                 redirect_prefix: /test
41//! ```
42//!
43//! A virtual host configuration adds three configuration settings to the configuration of the
44//! wrapped handler:
45//!
46//! * `aliases` lists additional host names that should share the same configuration.
47//! * `default` can be set to `true` to indicate that this configuration should apply to all host
48//!   names not listed explicitly.
49//! * `subdirs` maps subdirectories to their respective configuration. The configuration is that of
50//!   the wrapped handler with the added `strip_prefix` setting. If `true`, this setting will
51//!   remove the subdirectory path from the URI before the request is passed on to the handler.
52//!
53//! If no default host entry is present and a request is made for an unknown host name, this
54//! handler will leave the request unhandled. Otherwise the handling is delegated to the wrapped
55//! handler.
56//!
57//! When selecting a subdirectory configuration, longer matching paths are preferred. Matching
58//! always happens against full file names, meaning that URI `/test/abc` matches the subdirectory
59//! `/test` whereas the URI `/test_abc` doesn’t. If no matching path is found, the host
60//! configuration will be used.
61//!
62//! *Note*: When the `strip_prefix` option is used, the subsequent handlers will receive a URI
63//! which doesn’t match the actual URI of the request. This might result in wrong links or
64//! redirects. When using Static Files Module you can set `redirect_prefix` setting like in the
65//! example above to compensate. Upstream responses might have to be corrected via Pingora’s
66//! `upstream_response_filter`.
67
68//! ## Code example
69//!
70//! Usually, the virtual hosts configuration will be read from a configuration file and used to
71//! instantiate the corresponding handler. This is how it would be done:
72//!
73//! ```rust
74//! use pingora_core::server::configuration::{Opt, ServerConf};
75//! use module_utils::{FromYaml, merge_conf};
76//! use static_files_module::{StaticFilesConf, StaticFilesHandler};
77//! use structopt::StructOpt;
78//! use virtual_hosts_module::{VirtualHostsConf, VirtualHostsHandler};
79//!
80//! // Combine Pingora server configuration with virtual hosts wrapping static files configuration.
81//! #[merge_conf]
82//! struct Conf {
83//!     server: ServerConf,
84//!     virtual_hosts: VirtualHostsConf<StaticFilesConf>,
85//! }
86//!
87//! // Read command line options and configuration file.
88//! let opt = Opt::from_args();
89//! let conf = opt
90//!     .conf
91//!     .as_ref()
92//!     .and_then(|path| Conf::load_from_yaml(path).ok())
93//!     .unwrap_or_else(Conf::default);
94//!
95//! // Create handler from configuration
96//! let handler: VirtualHostsHandler<StaticFilesHandler> = conf.virtual_hosts.try_into().unwrap();
97//! ```
98//!
99//! You can then use that handler in your server implementation:
100//!
101//! ```rust
102//! use async_trait::async_trait;
103//! use pingora_core::upstreams::peer::HttpPeer;
104//! use pingora_core::Error;
105//! use pingora_proxy::{ProxyHttp, Session};
106//! use module_utils::RequestFilter;
107//! use static_files_module::StaticFilesHandler;
108//! use virtual_hosts_module::VirtualHostsHandler;
109//!
110//! pub struct MyServer {
111//!     handler: VirtualHostsHandler<StaticFilesHandler>,
112//! }
113//!
114//! #[async_trait]
115//! impl ProxyHttp for MyServer {
116//!     type CTX = <VirtualHostsHandler<StaticFilesHandler> as RequestFilter>::CTX;
117//!     fn new_ctx(&self) -> Self::CTX {
118//!         VirtualHostsHandler::<StaticFilesHandler>::new_ctx()
119//!     }
120//!
121//!     async fn request_filter(
122//!         &self,
123//!         session: &mut Session,
124//!         ctx: &mut Self::CTX,
125//!     ) -> Result<bool, Box<Error>> {
126//!         self.handler.handle(session, ctx).await
127//!     }
128//!
129//!     async fn upstream_peer(
130//!         &self,
131//!         _session: &mut Session,
132//!         _ctx: &mut Self::CTX,
133//!     ) -> Result<Box<HttpPeer>, Box<Error>> {
134//!         // Virtual hosts handler didn't handle the request, meaning no matching virtual host in
135//!         // configuration. Delegate to upstream peer.
136//!         Ok(Box::new(HttpPeer::new(
137//!             "example.com:443",
138//!             true,
139//!             "example.com".to_owned(),
140//!         )))
141//!     }
142//! }
143//! ```
144//!
145//! For complete and more comprehensive code see `virtual-hosts` example in the repository.
146
147mod configuration;
148mod handler;
149
150pub use configuration::{
151    SubDirCombined, SubDirConf, VirtualHostCombined, VirtualHostConf, VirtualHostsConf,
152};
153pub use handler::VirtualHostsHandler;