1use std::{any::Any, any::TypeId, cell::UnsafeCell, net::SocketAddr, rc::Rc};
2
3use urly::Url;
4
5use crate::service::cfg::{CfgContext, Configuration};
6use crate::{router::ResourceDef, util::ByteString, util::HashMap};
7
8use super::httprequest::{HttpRequest, HttpRequestInner};
9use super::service::{AppServiceFactory, ServiceFactoryWrapper, WebServiceFactory};
10use super::{Resource, Route, State};
11
12#[derive(Debug)]
14pub struct WebAppConfig {
15 name: ByteString,
16 secure: bool,
17 host: String,
18 addr: SocketAddr,
19 config: CfgContext,
20 state: HashMap<TypeId, Box<dyn Any + Sync + Send>>,
21 pub(super) pool_size: usize,
22}
23
24impl Default for WebAppConfig {
25 fn default() -> Self {
26 WebAppConfig::new()
27 }
28}
29
30impl Configuration for WebAppConfig {
31 const NAME: &str = "Web app configuration";
32
33 fn ctx(&self) -> &CfgContext {
34 &self.config
35 }
36
37 fn set_ctx(&mut self, ctx: CfgContext) {
38 self.config = ctx;
39 }
40}
41
42impl WebAppConfig {
43 #[must_use]
44 pub fn new() -> Self {
46 WebAppConfig::with(
47 "ntex:web",
48 false,
49 "127.0.0.1:8080".parse().unwrap(),
50 "localhost:8080".to_owned(),
51 )
52 }
53
54 #[must_use]
55 pub fn with(name: &str, secure: bool, addr: SocketAddr, host: String) -> Self {
57 WebAppConfig {
58 secure,
59 host,
60 addr,
61 name: name.into(),
62 pool_size: 128,
63 state: HashMap::default(),
64 config: CfgContext::default(),
65 }
66 }
67
68 pub fn name(&self) -> &ByteString {
70 &self.name
71 }
72
73 pub fn host(&self) -> &str {
81 &self.host
82 }
83
84 pub fn secure(&self) -> bool {
86 self.secure
87 }
88
89 pub fn local_addr(&self) -> SocketAddr {
91 self.addr
92 }
93
94 pub fn state<T: 'static>(&self) -> Option<&T> {
96 self.state
97 .get(&TypeId::of::<T>())
98 .and_then(|boxed| boxed.downcast_ref())
99 }
100
101 #[must_use]
102 pub fn set_host(mut self, host: String) -> Self {
106 self.host = host;
107 self
108 }
109
110 #[must_use]
111 pub fn set_secure(mut self) -> Self {
113 self.secure = true;
114 self
115 }
116
117 #[must_use]
118 pub fn set_local_addr(mut self, addr: SocketAddr) -> Self {
120 self.addr = addr;
121 self
122 }
123
124 #[must_use]
125 pub fn set_pool_size(mut self, size: usize) -> Self {
129 self.pool_size = size;
130 self
131 }
132
133 #[must_use]
134 pub fn set_state<T: Send + Sync + 'static>(mut self, val: T) -> Self {
139 self.state
140 .insert(TypeId::of::<T>(), Box::new(val))
141 .and_then(|item| item.downcast::<T>().map(|boxed| *boxed).ok());
142 self
143 }
144
145 pub(crate) fn get_request(&self) -> Option<HttpRequest> {
147 CACHE.with(|cache| cache.with(self.config.id(), |cache| cache.pop().map(HttpRequest)))
148 }
149}
150
151pub(crate) fn put_request(id: usize, pool_size: usize, req: &mut Rc<HttpRequestInner>) {
153 CACHE.with(|cache| {
154 cache.with(id, |cache| {
155 if cache.len() < pool_size
156 && let Some(inner) = Rc::get_mut(req)
157 {
158 inner.head.clear();
161 inner.path.set(Url::new());
162 cache.push(req.clone());
163 }
164 });
165 });
166}
167
168#[derive(derive_more::Debug)]
174#[debug("ServiceConfig")]
175pub struct ServiceConfig<St, In = ()> {
176 pub(super) services: Vec<Box<dyn AppServiceFactory<St, In>>>,
177 pub(super) external: Vec<ResourceDef>,
178}
179
180impl<St: State, In: 'static> ServiceConfig<St, In> {
181 pub fn new(external: Vec<ResourceDef>) -> Self {
183 Self {
184 external,
185 services: Vec::new(),
186 }
187 }
188
189 pub fn route(&mut self, path: &str, mut route: Route<St, In>) -> &mut Self {
197 self.service(
198 Resource::new(path)
199 .add_guards(route.take_guards())
200 .route(route),
201 )
202 }
203
204 pub fn service<F>(&mut self, factory: F) -> &mut Self
225 where
226 F: WebServiceFactory<St, In> + 'static,
227 {
228 self.services
229 .push(Box::new(ServiceFactoryWrapper::new(factory)));
230 self
231 }
232
233 pub fn external_resource(&mut self, name: impl AsRef<str>, url: impl AsRef<str>) -> &mut Self {
241 let mut rdef = ResourceDef::new(url.as_ref());
242 rdef.set_name(name.as_ref());
243 self.external.push(rdef);
244 self
245 }
246}
247
248thread_local! {
249 static CACHE: LocalCache = LocalCache::new();
250}
251
252struct LocalCache {
254 cache: UnsafeCell<Vec<Vec<Rc<HttpRequestInner>>>>,
255}
256
257impl LocalCache {
258 fn new() -> Self {
259 Self {
260 cache: UnsafeCell::new(Vec::with_capacity(16)),
261 }
262 }
263
264 fn with<F, R>(&self, idx: usize, f: F) -> R
265 where
266 F: FnOnce(&mut Vec<Rc<HttpRequestInner>>) -> R,
267 {
268 let cache = unsafe { &mut *self.cache.get() };
269
270 while cache.len() <= idx {
271 cache.push(Vec::new());
272 }
273 f(&mut cache[idx])
274 }
275}
276
277#[cfg(test)]
278#[allow(clippy::assert_is_empty)]
279mod tests {
280 use super::*;
281 use crate::http::{Method, StatusCode};
282 use crate::util::Bytes;
283 use crate::web::test::{TestRequest, call_service, init_service, read_body};
284 use crate::web::{self, App, HttpRequest, HttpResponse};
285
286 #[crate::rt_test]
287 async fn test_webappconfig() {
288 let cfg = WebAppConfig::default()
289 .set_host("www.example.org".to_string())
290 .set_local_addr("127.0.0.1:8080".parse().unwrap())
291 .set_pool_size(256);
292 assert_eq!(cfg.host(), "www.example.org");
293 assert_eq!(cfg.local_addr(), "127.0.0.1:8080".parse().unwrap());
294 assert_eq!(cfg.pool_size, 256);
295 assert_eq!(cfg.name(), "ntex:web");
296 assert!(!cfg.secure());
297 assert!(cfg.set_secure().secure());
298 }
299
300 #[crate::rt_test]
301 async fn test_configure_external_resource() {
302 let srv = init_service(
303 App::new()
304 .configure(|cfg| {
305 cfg.external_resource("youtube", "https://youtube.com/watch/{video_id}");
306 })
307 .route(
308 "/test",
309 web::get().to(async move |req: HttpRequest| {
310 HttpResponse::Ok()
311 .body(format!("{}", req.url_for("youtube", ["12345"]).unwrap()))
312 }),
313 ),
314 )
315 .await;
316 let req = TestRequest::with_uri("/test").to_request();
317 let resp = call_service(&srv, req).await;
318 assert_eq!(resp.status(), StatusCode::OK);
319 let body = read_body(resp).await;
320 assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345"));
321 }
322
323 #[crate::rt_test]
324 async fn test_configure_service() {
325 let srv = init_service(App::new().configure(|cfg| {
326 cfg.service(
327 web::resource("/test").route(web::get().to(async || HttpResponse::Created())),
328 )
329 .route("/index.html", web::get().to(async || HttpResponse::Ok()));
330 }))
331 .await;
332
333 let req = TestRequest::with_uri("/test")
334 .method(Method::GET)
335 .to_request();
336 let resp = call_service(&srv, req).await;
337 assert_eq!(resp.status(), StatusCode::CREATED);
338
339 let req = TestRequest::with_uri("/index.html")
340 .method(Method::GET)
341 .to_request();
342 let resp = call_service(&srv, req).await;
343 assert_eq!(resp.status(), StatusCode::OK);
344 }
345
346 #[test]
347 fn test_new_service_config() {
348 let cfg: ServiceConfig<(), ()> = ServiceConfig::new(Vec::new());
349 assert!(cfg.services.is_empty());
350 assert!(cfg.external.is_empty());
351 }
352}