Skip to main content

witchcraft_server/
witchcraft.rs

1// Copyright 2022 Palantir Technologies, Inc.
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.
14use crate::blocking::conjure::ConjureBlockingEndpoint;
15use crate::blocking::pool::ThreadPool;
16use crate::debug::DiagnosticRegistry;
17use crate::endpoint::conjure::ConjureEndpoint;
18use crate::endpoint::extended_path::ExtendedPathEndpoint;
19use crate::endpoint::WitchcraftEndpoint;
20use crate::health::HealthCheckRegistry;
21use crate::readiness::ReadinessCheckRegistry;
22use crate::shutdown_hooks::ShutdownHooks;
23use crate::{blocking, RequestBody, ResponseWriter};
24use conjure_http::server::{AsyncService, BoxAsyncEndpoint, ConjureRuntime, Endpoint, Service};
25use conjure_runtime::ClientFactory;
26use futures_util::Future;
27use std::sync::Arc;
28use tokio::runtime::Handle;
29use witchcraft_metrics::MetricRegistry;
30use witchcraft_server_config::install::InstallConfig;
31
32/// The Witchcraft server context.
33pub struct Witchcraft {
34    pub(crate) metrics: Arc<MetricRegistry>,
35    pub(crate) health_checks: Arc<HealthCheckRegistry>,
36    pub(crate) readiness_checks: Arc<ReadinessCheckRegistry>,
37    pub(crate) diagnostics: Arc<DiagnosticRegistry>,
38    pub(crate) client_factory: ClientFactory,
39    pub(crate) handle: Handle,
40    pub(crate) install_config: InstallConfig,
41    pub(crate) thread_pool: Option<Arc<ThreadPool>>,
42    pub(crate) thread_prefix: Option<String>,
43    pub(crate) endpoints: Vec<Box<dyn WitchcraftEndpoint + Sync + Send>>,
44    pub(crate) shutdown_hooks: ShutdownHooks,
45    pub(crate) conjure_runtime: Arc<ConjureRuntime>,
46}
47
48impl Witchcraft {
49    /// Returns a reference to the server's metric registry.
50    #[inline]
51    pub fn metrics(&self) -> &Arc<MetricRegistry> {
52        &self.metrics
53    }
54
55    /// Returns a reference to the server's health check registry.
56    #[inline]
57    pub fn health_checks(&self) -> &Arc<HealthCheckRegistry> {
58        &self.health_checks
59    }
60
61    /// Returns a reference to the server's readiness check registry.
62    #[inline]
63    pub fn readiness_checks(&self) -> &Arc<ReadinessCheckRegistry> {
64        &self.readiness_checks
65    }
66
67    /// Returns a reference to the server's HTTP client factory.
68    #[inline]
69    pub fn client_factory(&self) -> &ClientFactory {
70        &self.client_factory
71    }
72
73    /// Returns a reference to the server's diagnostics registry.
74    #[inline]
75    pub fn diagnostics(&self) -> &Arc<DiagnosticRegistry> {
76        &self.diagnostics
77    }
78
79    /// Returns a reference to a handle to the server's Tokio runtime.
80    #[inline]
81    pub fn handle(&self) -> &Handle {
82        &self.handle
83    }
84
85    /// Installs an async service at the server's root.
86    pub fn app<T>(&mut self, service: T)
87    where
88        T: AsyncService<RequestBody, ResponseWriter>,
89    {
90        self.endpoints(None, service.endpoints(&self.conjure_runtime), true)
91    }
92
93    /// Installs an async service under the server's `/api` prefix.
94    pub fn api<T>(&mut self, service: T)
95    where
96        T: AsyncService<RequestBody, ResponseWriter>,
97    {
98        self.endpoints(Some("/api"), service.endpoints(&self.conjure_runtime), true)
99    }
100
101    pub(crate) fn endpoints(
102        &mut self,
103        prefix: Option<&str>,
104        endpoints: Vec<BoxAsyncEndpoint<'static, RequestBody, ResponseWriter>>,
105        track_metrics: bool,
106    ) {
107        let metrics = if track_metrics {
108            Some(&*self.metrics)
109        } else {
110            None
111        };
112
113        self.endpoints.extend(
114            endpoints
115                .into_iter()
116                .map(|e| Box::new(ConjureEndpoint::new(metrics, e)))
117                .map(|e| extend_path(e, self.install_config.context_path(), prefix)),
118        )
119    }
120
121    /// Installs a blocking service at the server's root.
122    pub fn blocking_app<T>(&mut self, service: T)
123    where
124        T: Service<blocking::RequestBody, blocking::ResponseWriter>,
125    {
126        self.blocking_endpoints(None, service.endpoints(&self.conjure_runtime))
127    }
128
129    /// Installs a blocking service under the server's `/api` prefix.
130    pub fn blocking_api<T>(&mut self, service: T)
131    where
132        T: Service<blocking::RequestBody, blocking::ResponseWriter>,
133    {
134        self.blocking_endpoints(Some("/api"), service.endpoints(&self.conjure_runtime))
135    }
136
137    fn blocking_endpoints(
138        &mut self,
139        prefix: Option<&str>,
140        endpoints: Vec<
141            Box<dyn Endpoint<blocking::RequestBody, blocking::ResponseWriter> + Sync + Send>,
142        >,
143    ) {
144        let thread_pool = self.thread_pool.get_or_insert_with(|| {
145            Arc::new(ThreadPool::new(
146                &self.install_config,
147                &self.metrics,
148                self.thread_prefix.clone(),
149            ))
150        });
151
152        self.endpoints.extend(
153            endpoints
154                .into_iter()
155                .map(|e| Box::new(ConjureBlockingEndpoint::new(&self.metrics, thread_pool, e)))
156                .map(|e| extend_path(e, self.install_config.context_path(), prefix)),
157        )
158    }
159
160    /// Adds a future that will be run when the server begins its shutdown process.
161    ///
162    /// The server will not shut down until the future completes or the configured shutdown timeout elapses.
163    pub fn on_shutdown<F>(&mut self, future: F)
164    where
165        F: Future<Output = ()> + 'static + Send,
166    {
167        self.shutdown_hooks.push(future)
168    }
169}
170
171fn extend_path(
172    endpoint: Box<dyn WitchcraftEndpoint + Sync + Send>,
173    context_path: &str,
174    prefix: Option<&str>,
175) -> Box<dyn WitchcraftEndpoint + Sync + Send> {
176    let context_path = if context_path == "/" {
177        ""
178    } else {
179        context_path
180    };
181    let prefix = format!("{context_path}{}", prefix.unwrap_or(""));
182
183    if prefix.is_empty() {
184        endpoint
185    } else {
186        Box::new(ExtendedPathEndpoint::new(endpoint, &prefix))
187    }
188}