1use core::{fmt, str};
2use std::{cmp::min, collections::HashMap, io::Error, sync::Arc};
3
4#[cfg(any(feature = "http", feature = "https"))]
5use std::net::IpAddr;
6
7use chrono::{TimeDelta, Utc};
8use serde_json::Value;
9use tokio::{
10 io::{AsyncReadExt, AsyncWriteExt},
11 net::TcpStream,
12 sync::{
13 mpsc::{self, Sender},
14 Mutex,
15 },
16 task::JoinHandle,
17};
18
19#[cfg(not(feature = "https"))]
20use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
21
22#[cfg(debug_assertions)]
23use tokio::sync::RwLock;
24#[cfg(feature = "https")]
25use tokio_rustls::TlsAcceptor;
26
27use super::{
28 action::{ActMap, Action, ActionData, Answer},
29 cache::CacheSys,
30 dbs::adapter::DB,
31 file::TempFile,
32 html::Html,
33 init::Addr,
34 lang::Lang,
35 log::Log,
36 mail::Mail,
37 request::{HttpVersion, RawData, WebFile},
38 route::Route,
39};
40
41#[cfg(feature = "fastcgi")]
42use super::workers::fastcgi;
43#[cfg(any(feature = "http", feature = "https"))]
44use super::workers::http;
45#[cfg(feature = "scgi")]
46use super::workers::scgi;
47#[cfg(feature = "uwsgi")]
48use super::workers::uwsgi;
49
50pub const BUFFER_SIZE: usize = 8192;
52
53const ONE_YEAR: i64 = 31622400;
55
56#[derive(Debug)]
58pub(crate) struct Worker;
59
60pub(crate) struct WorkerData {
62 pub engine: Arc<ActMap>,
64 #[cfg(not(debug_assertions))]
66 pub lang: Arc<Lang>,
67 #[cfg(debug_assertions)]
69 pub lang: Arc<RwLock<Lang>>,
70 #[cfg(not(debug_assertions))]
72 pub html: Arc<Html>,
73 #[cfg(debug_assertions)]
75 pub html: Arc<RwLock<Html>>,
76 pub cache: Arc<Mutex<CacheSys>>,
78 pub db: Arc<DB>,
80 pub session_key: Arc<String>,
82 pub salt: Arc<String>,
84 pub mail: Arc<Mutex<Mail>>,
86 pub action_index: Arc<Route>,
88 pub action_not_found: Arc<Route>,
90 pub action_err: Arc<Route>,
92 pub(crate) stop: Option<(Arc<Addr>, i64)>,
94 pub(crate) root: Arc<String>,
96 #[cfg(any(feature = "http", feature = "https"))]
98 pub(crate) ip: IpAddr,
99 #[cfg(feature = "https")]
101 pub(crate) acceptor: Arc<TlsAcceptor>,
102}
103
104pub(crate) enum StreamError {
106 Closed,
108 Error(Error),
110 Buffer,
112 Timeout,
114}
115
116pub(crate) struct StreamRead {
118 #[cfg(not(feature = "https"))]
120 tcp: OwnedReadHalf,
121 #[cfg(feature = "https")]
123 tcp: tokio::io::ReadHalf<tokio_rustls::server::TlsStream<TcpStream>>,
124 buf: [u8; BUFFER_SIZE],
126 len: usize,
128 shift: usize,
130}
131
132impl StreamRead {
133 pub async fn read(&mut self, timeout: u64) -> Result<(), StreamError> {
142 if self.shift == 0 && self.len == BUFFER_SIZE {
143 return Err(StreamError::Buffer);
144 } else if self.shift > 0 && self.len <= BUFFER_SIZE {
145 self.buf.copy_within(self.shift.., 0);
146 self.len -= self.shift;
147 self.shift = 0;
148 }
149
150 if timeout > 0 {
151 match tokio::time::timeout(std::time::Duration::from_millis(timeout), async {
152 match self.tcp.read(unsafe { self.buf.get_unchecked_mut(self.len..) }).await {
153 Ok(len) => {
154 if len == 0 {
155 Err(StreamError::Closed)
156 } else {
157 Ok(len)
158 }
159 }
160 Err(e) => Err(StreamError::Error(e)),
161 }
162 })
163 .await
164 {
165 Ok(res) => match res {
166 Ok(len) => {
167 self.len += len;
168 Ok(())
169 }
170 Err(e) => Err(e),
171 },
172 Err(_) => Err(StreamError::Timeout),
173 }
174 } else {
175 match self.tcp.read(unsafe { self.buf.get_unchecked_mut(self.len..) }).await {
176 Ok(len) => {
177 if len == 0 {
178 Err(StreamError::Closed)
179 } else {
180 self.len += len;
181 Ok(())
182 }
183 }
184 Err(e) => Err(StreamError::Error(e)),
185 }
186 }
187 }
188
189 pub fn get(&self, size: usize) -> &[u8] {
191 let size = min(self.shift + size, self.len);
192 unsafe { self.buf.get_unchecked(self.shift..size) }
193 }
194
195 pub fn shift(&mut self, shift: usize) {
197 self.shift += shift;
198 if self.shift > self.len {
199 self.shift = self.len;
200 }
201 }
202
203 pub fn available(&self) -> usize {
205 self.len - self.shift
206 }
207}
208
209pub(crate) struct StreamWrite {
211 pub tx: Arc<Sender<MessageWrite>>,
213}
214
215#[derive(Debug)]
216pub(crate) enum MessageWrite {
217 Message(Vec<u8>, bool),
218 End,
219}
220
221impl StreamWrite {
222 pub async fn new(
223 #[cfg(not(feature = "https"))] mut tcp: OwnedWriteHalf,
224 #[cfg(feature = "https")] mut tcp: tokio::io::WriteHalf<tokio_rustls::server::TlsStream<TcpStream>>,
225 ) -> (Arc<StreamWrite>, JoinHandle<()>) {
226 let (tx, mut rx) = mpsc::channel(32);
227 let stream = Arc::new(StreamWrite { tx: Arc::new(tx) });
228
229 let handle = tokio::spawn(async move {
230 while let Some(message) = rx.recv().await {
231 match message {
232 MessageWrite::Message(message, end) => {
233 #[cfg(feature = "fastcgi")]
234 let data = fastcgi::Net::write(message, end);
235 #[cfg(feature = "uwsgi")]
236 let data = uwsgi::Net::write(message, end);
237 #[cfg(feature = "scgi")]
238 let data = scgi::Net::write(message, end);
239 #[cfg(any(feature = "http", feature = "https"))]
240 let data = http::Net::write(message, end);
241
242 if let Err(e) = tcp.write_all(&data).await {
243 Log::warning(101, Some(e.to_string()));
244 }
245 }
246 MessageWrite::End => break,
247 }
248 }
249 });
250 (stream, handle)
251 }
252
253 pub async fn write(&self, data: Vec<u8>) {
255 if let Err(e) = self.tx.send(MessageWrite::Message(data, true)).await {
256 Log::warning(100, Some(e.to_string()));
257 }
258 }
259
260 async fn end(handle: JoinHandle<()>, tx: Arc<Sender<MessageWrite>>) {
261 if let Err(e) = tx.send(MessageWrite::End).await {
262 Log::warning(100, Some(e.to_string()));
263 }
264 if let Err(e) = handle.await {
265 Log::warning(102, Some(e.to_string()));
266 }
267 }
268}
269
270impl Worker {
271 pub async fn run(stream: TcpStream, data: WorkerData) {
278 #[cfg(not(feature = "https"))]
279 let (read, write) = stream.into_split();
280 #[cfg(feature = "https")]
281 let (read, write) = {
282 let tls_stream = match data.acceptor.accept(stream).await {
283 Ok(stream) => stream,
284 Err(e) => {
285 Log::warning(2007, Some(e.to_string()));
286 return;
287 }
288 };
289 let (tls_read, tls_write) = tokio::io::split(tls_stream);
290 (tls_read, tls_write)
291 };
292
293 let mut stream_read = StreamRead {
294 tcp: read,
295 buf: [0; BUFFER_SIZE],
296 len: 0,
297 shift: 0,
298 };
299 let (stream_write, handle) = StreamWrite::new(write).await;
300 let tx = Arc::clone(&stream_write.tx);
301 if let Err(e) = stream_read.read(300).await {
302 match e {
303 StreamError::Closed => {}
304 StreamError::Error(e) => {
305 Log::warning(2000, Some(e.to_string()));
306 }
307 StreamError::Buffer => {
308 Log::warning(2006, None);
309 }
310 StreamError::Timeout => {
311 Log::warning(2001, None);
312 }
313 }
314 return;
315 }
316 #[cfg(feature = "fastcgi")]
317 fastcgi::Net::run(stream_read, stream_write, data).await;
318 #[cfg(feature = "uwsgi")]
319 uwsgi::Net::run(stream_read, stream_write, data).await;
320 #[cfg(feature = "scgi")]
321 scgi::Net::run(stream_read, stream_write, data).await;
322 #[cfg(any(feature = "http", feature = "https"))]
323 http::Net::run(stream_read, stream_write, data).await;
324
325 StreamWrite::end(handle, tx).await;
326 }
327
328 pub fn http_code_get(code: u16) -> &'static str {
330 match code {
331 100 => "Continue",
332 101 => "Switching Protocols",
333 102 => "Processing",
334 103 => "Early Hints",
335 200 => "OK",
336 201 => "Created",
337 202 => "Accepted",
338 203 => "Non-Authoritative Information",
339 204 => "No Content",
340 205 => "Reset Content",
341 206 => "Partial Content",
342 207 => "Multi-Status",
343 208 => "Already Reported",
344 226 => "IM Used",
345 300 => "Multiple Choices",
346 301 => "Moved Permanently",
347 302 => "Found",
348 303 => "See Other",
349 304 => "Not Modified",
350 305 => "Use Proxy",
351 306 => "(Unused)",
352 307 => "Temporary Redirect",
353 308 => "Permanent Redirect",
354 400 => "Bad Request",
355 401 => "Unauthorized",
356 402 => "Payment Required",
357 403 => "Forbidden",
358 404 => "Not Found",
359 405 => "Method Not Allowed",
360 406 => "Not Acceptable",
361 407 => "Proxy Authentication Required",
362 408 => "Request Timeout",
363 409 => "Conflict",
364 410 => "Gone",
365 411 => "Length Required",
366 412 => "Precondition Failed",
367 413 => "Content Too Large",
368 414 => "URI Too Long",
369 415 => "Unsupported Media Type",
370 416 => "Range Not Satisfiable",
371 417 => "Expectation Failed",
372 418 => "(Unused)",
373 421 => "Misdirected Request",
374 422 => "Unprocessable Content",
375 423 => "Locked",
376 424 => "Failed Dependency",
377 425 => "Too Early",
378 426 => "Upgrade Required",
379 428 => "Precondition Required",
380 429 => "Too Many Requests",
381 431 => "Request Header Fields Too Large",
382 451 => "Unavailable For Legal Reasons",
383 500 => "Internal Server Error",
384 501 => "Not Implemented",
385 502 => "Bad Gateway",
386 503 => "Service Unavailable",
387 504 => "Gateway Timeout",
388 505 => "HTTP Version Not Supported",
389 506 => "Variant Also Negotiates",
390 507 => "Insufficient Storage",
391 508 => "Loop Detected",
392 510 => "Not Extended (OBSOLETED)",
393 511 => "Network Authentication Required",
394 _ => "Unassigned",
395 }
396 }
397
398 pub(crate) async fn call_action(data: ActionData) -> Vec<u8> {
404 #[cfg(debug_assertions)]
405 Log::info(228, Some(format!("{} {:?} {}{}", data.request.ip, data.request.method, data.request.site, data.request.url)));
406
407 #[cfg(debug_assertions)]
409 Worker::reload(Arc::clone(&data.lang), Arc::clone(&data.html)).await;
410
411 let status = match data.request.version {
412 HttpVersion::None => "Status:",
413 HttpVersion::HTTP1_0 => "HTTP/1.0",
414 HttpVersion::HTTP1_1 => "HTTP/1.1",
415 HttpVersion::HTTP2 => "HTTP/2",
416 };
417
418 let mut action = match Action::new(data).await {
419 Ok(action) => action,
420 Err((redirect, files)) => {
421 tokio::spawn(async move {
423 let mut vec = Vec::with_capacity(32);
424 for files in files.into_values() {
425 for f in files {
426 vec.push(f.tmp)
427 }
428 }
429 Action::clean_file(vec).await;
430 });
431
432 let mut answer: Vec<u8> = Vec::with_capacity(512);
434 if redirect.permanently {
435 answer.extend_from_slice(
436 format!("{status} 301 {}\r\nLocation: {}\r\n\r\n", Worker::http_code_get(301), redirect.url).as_bytes(),
437 );
438 } else {
439 answer.extend_from_slice(
440 format!("{status} 302 {}\r\nLocation: {}\r\n\r\n", Worker::http_code_get(302), redirect.url).as_bytes(),
441 );
442 }
443 return answer;
444 }
445 };
446
447 let result = match Action::run(&mut action).await {
448 Answer::Raw(answer) => answer,
449 Answer::String(answer) => answer.into_bytes(),
450 Answer::None => Vec::new(),
451 };
452
453 let answer = if !action.header_send {
454 let capacity = result.len() + 4096;
457 let mut answer = Worker::get_header(capacity, &action, Some(result.len()));
458 answer.extend_from_slice(&result);
459 answer
460 } else {
461 result
462 };
463
464 tokio::spawn(async move {
466 Action::end(action).await;
467 });
468 answer
469 }
470
471 #[cfg(debug_assertions)]
473 async fn reload(lang: Arc<RwLock<Lang>>, html: Arc<RwLock<Html>>) {
474 let changed = lang.read().await.check_time().await;
475 if changed {
476 let mut lang = lang.write().await;
477 let files = Lang::get_files(Arc::clone(&lang.root)).await;
478 lang.load(files).await;
479 }
480 let changed = html.read().await.check_time().await;
481 if changed {
482 let mut html = html.write().await;
483 html.load().await;
484 }
485 }
486
487 fn get_header(capacity: usize, action: &Action, content_length: Option<usize>) -> Vec<u8> {
488 let status = match action.request.version {
489 HttpVersion::None => "Status:",
490 HttpVersion::HTTP1_0 => "HTTP/1.0",
491 HttpVersion::HTTP1_1 => "HTTP/1.1",
492 HttpVersion::HTTP2 => "HTTP/2",
493 };
494
495 let mut answer: Vec<u8> = Vec::with_capacity(capacity);
497 if let Some(redirect) = action.response.redirect.as_ref() {
498 if redirect.permanently {
499 answer
500 .extend_from_slice(format!("{status} 301 {}\r\nLocation: {}\r\n", Worker::http_code_get(301), redirect.url).as_bytes());
501 } else {
502 answer
503 .extend_from_slice(format!("{status} 302 {}\r\nLocation: {}\r\n", Worker::http_code_get(302), redirect.url).as_bytes());
504 }
505 } else if let Some(code) = action.response.http_code {
506 answer.extend_from_slice(format!("{status} {} {}\r\n", code, Worker::http_code_get(code)).as_bytes());
507 } else {
508 answer.extend_from_slice(format!("{status} 200 {}\r\n", Worker::http_code_get(200)).as_bytes());
509 }
510
511 let sec = TimeDelta::new(ONE_YEAR, 0).unwrap_or_else(TimeDelta::zero);
513 let time = Utc::now() + sec;
514 let date = time.format("%a, %d-%b-%Y %H:%M:%S GMT").to_string();
515 let secure = if action.request.scheme == "https" { "Secure; " } else { "" };
516
517 answer.extend_from_slice(
518 format!(
519 "Set-Cookie: {}={}; Expires={}; Max-Age={}; path=/; domain={}; {}SameSite=none\r\n",
520 action.session.session_key, &action.session.key, date, ONE_YEAR, action.request.host, secure
521 )
522 .as_bytes(),
523 );
524 match &action.response.content_type {
526 Some(content_type) => answer.extend_from_slice(format!("Content-Type: {}\r\n", content_type).as_bytes()),
527 None => answer.extend_from_slice(b"Content-Type: text/html; charset=utf-8\r\n"),
528 }
529 answer.extend_from_slice(b"Connection: Keep-Alive\r\n");
530 for (name, val) in &action.response.headers {
532 answer.extend_from_slice(format!("{}: {}\r\n", name, val).as_bytes());
533 }
534 if let Some(len) = content_length {
536 answer.extend_from_slice(format!("Content-Length: {}\r\n", len).as_bytes());
537 }
538 answer.extend_from_slice(b"\r\n");
539
540 answer
541 }
542
543 pub async fn write(action: &Action, src: Vec<u8>) {
544 let src = if !action.header_send {
545 let mut vec = Worker::get_header(src.len() + 4096, action, None);
546 vec.extend_from_slice(&src);
547 vec
548 } else {
549 src
550 };
551 if let Err(e) = action.tx.send(MessageWrite::Message(src, false)).await {
552 Log::warning(100, Some(e.to_string()));
553 }
554 }
555
556 pub async fn read_input(
568 data: Vec<u8>,
569 content_type: Option<String>,
570 ) -> (HashMap<String, String>, HashMap<String, Vec<WebFile>>, RawData) {
571 let mut post = HashMap::new();
572 let mut file = HashMap::new();
573 let mut raw = RawData::None;
574
575 if let Some(c) = content_type {
577 if c == "application/x-www-form-urlencoded" {
579 if !data.is_empty() {
580 if let Ok(s) = std::str::from_utf8(&data) {
581 let val: Vec<&str> = s.split('&').collect();
582 post.reserve(val.len());
583 for v in val {
584 let val: Vec<&str> = v.splitn(2, '=').collect();
585 match val.len() {
586 1 => post.insert(v.to_owned(), String::new()),
587 _ => post.insert(val[0].to_owned(), val[1].to_owned()),
588 };
589 }
590 }
591 }
592 } else if c == "application/json;charset=UTF-8" {
593 raw = match serde_json::from_slice::<Value>(&data) {
594 Ok(v) => RawData::Json(v),
595 Err(_) => match String::from_utf8(data.clone()) {
596 Ok(s) => RawData::String(s),
597 Err(e) => RawData::Raw(e.into_bytes()),
598 },
599 };
600 } else if c.len() > 30 {
601 if let "multipart/form-data; boundary=" = &c[..30] {
603 let boundary = format!("--{}", &c[30..]);
604 let stop: [u8; 4] = [13, 10, 13, 10];
605 if !data.is_empty() {
606 let mut seek: usize = 0;
607 let mut start: usize;
608 let b_len = boundary.len();
609 let len = data.len() - 4;
610 let mut found: Option<(usize, &str)> = None;
611 while seek < len {
612 if boundary.as_bytes() == &data[seek..seek + b_len] {
614 if seek + b_len == len {
615 if let Some((l, h)) = found {
616 let d = &data[l..seek - 2];
617 Worker::get_post_file(h, d, &mut post, &mut file).await;
618 };
619 break;
620 }
621 seek += b_len + 2;
622 start = seek;
623 while seek < len {
624 if stop == data[seek..seek + 4] {
625 if let Ok(s) = std::str::from_utf8(&data[start..seek]) {
626 if let Some((l, h)) = found {
627 let d = &data[l..start - b_len - 4];
628 Worker::get_post_file(h, d, &mut post, &mut file).await;
629 };
630 found = Some((seek + 4, s));
631 }
632 seek += 4;
633 break;
634 } else {
635 seek += 1;
636 }
637 }
638 } else {
639 seek += 1;
640 }
641 }
642 }
643 } else {
644 raw = match String::from_utf8(data.clone()) {
645 Ok(s) => RawData::String(s),
646 Err(e) => RawData::Raw(e.into_bytes()),
647 }
648 }
649 } else {
650 raw = match String::from_utf8(data.clone()) {
651 Ok(s) => RawData::String(s),
652 Err(e) => RawData::Raw(e.into_bytes()),
653 }
654 }
655 } else if !data.is_empty() {
656 raw = match String::from_utf8(data.clone()) {
657 Ok(s) => RawData::String(s),
658 Err(e) => RawData::Raw(e.into_bytes()),
659 }
660 }
661 (post, file, raw)
662 }
663
664 async fn get_post_file(header: &str, data: &[u8], post: &mut HashMap<String, String>, file: &mut HashMap<String, Vec<WebFile>>) {
666 let h: Vec<&str> = header.splitn(3, "; ").collect();
667 let len = h.len();
668
669 if len == 2 {
671 if let Ok(v) = std::str::from_utf8(data) {
672 let k = &h[1][6..h[1].len() - 1];
673 post.insert(k.to_owned(), v.to_owned());
674 }
675 } else if len == 3 {
676 let k = h[1][6..h[1].len() - 1].to_owned();
678 let n: Vec<&str> = h[2].splitn(2, "\r\n").collect();
679 let n = &n[0][10..n[0].len() - 1];
680
681 let path = TempFile::new_name();
682 if TempFile::write(&path, data).await.is_ok() {
683 if file.get(&k).is_none() {
684 file.insert(k.to_owned(), Vec::with_capacity(16));
685 } else if let Some(d) = file.get_mut(&k) {
686 d.push(WebFile {
687 size: data.len(),
688 name: n.to_owned(),
689 tmp: path,
690 })
691 };
692 }
693 }
694 }
695
696 #[cfg(feature = "https")]
697 pub fn load_cert(root: Arc<String>) -> Result<Arc<TlsAcceptor>, Error> {
698 use std::{
699 fs::File,
700 io::{BufReader, ErrorKind},
701 };
702
703 use rustls::{
704 pki_types::{CertificateDer, PrivateKeyDer},
705 ServerConfig,
706 };
707 use rustls_pemfile::{certs, read_all, Item};
708
709 let mut cert_file = BufReader::new(File::open(format!("{root}/ssl/certificate.crt"))?);
710 let mut key_file = BufReader::new(File::open(format!("{root}/ssl/privateKey.key"))?);
711
712 let certs = certs(&mut cert_file).collect::<Result<Vec<CertificateDer<'static>>, Error>>()?;
713
714 let pem_files = match read_all(&mut key_file).next() {
715 Some(file) => file?,
716 None => return Err(Error::new(ErrorKind::Other, format!("Private key not found in file {root}/ssl/privateKey.key",))),
717 };
718 let key = match pem_files {
719 Item::Pkcs1Key(key) => PrivateKeyDer::Pkcs1(key),
720 Item::Pkcs8Key(key) => PrivateKeyDer::Pkcs8(key),
721 Item::Sec1Key(key) => PrivateKeyDer::Sec1(key),
722 e => return Err(Error::new(ErrorKind::Other, format!("Private key not support {:?} in file {root}/ssl/privateKey.key", e))),
723 };
724
725 let tls_config = match ServerConfig::builder().with_no_client_auth().with_single_cert(certs, key) {
726 Ok(config) => Arc::new(config),
727 Err(e) => return Err(Error::new(ErrorKind::Other, e)),
728 };
729
730 Ok(Arc::new(TlsAcceptor::from(tls_config)))
731 }
732}
733
734impl fmt::Debug for WorkerData {
735 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
736 let WorkerData {
737 engine,
738 lang,
739 html,
740 cache,
741 db,
742 session_key,
743 salt,
744 mail,
745 action_index,
746 action_not_found,
747 action_err,
748 stop,
749 root,
750 #[cfg(any(feature = "http", feature = "https"))]
751 ip,
752 #[cfg(feature = "https")]
753 acceptor: _,
754 } = self;
755 #[cfg(feature = "https")]
756 let res = f
757 .debug_struct("WorkerData")
758 .field("engine", &engine)
759 .field("lang", &lang)
760 .field("html", &html)
761 .field("cache", &cache)
762 .field("db", &db)
763 .field("session_key", &session_key)
764 .field("salt", &salt)
765 .field("mail", &mail)
766 .field("action_index", &action_index)
767 .field("action_not_found", &action_not_found)
768 .field("action_err", &action_err)
769 .field("stop", &stop)
770 .field("root", &root)
771 .field("ip", &ip)
772 .field("acceptor", &"Not implemented")
773 .finish();
774 #[cfg(feature = "http")]
775 let res = f
776 .debug_struct("WorkerData")
777 .field("engine", &engine)
778 .field("lang", &lang)
779 .field("html", &html)
780 .field("cache", &cache)
781 .field("db", &db)
782 .field("session_key", &session_key)
783 .field("salt", &salt)
784 .field("mail", &mail)
785 .field("action_index", &action_index)
786 .field("action_not_found", &action_not_found)
787 .field("action_err", &action_err)
788 .field("stop", &stop)
789 .field("root", &root)
790 .field("ip", &ip)
791 .field("acceptor", &"Not implemented")
792 .finish();
793 #[cfg(not(any(feature = "http", feature = "https")))]
794 let res = f
795 .debug_struct("WorkerData")
796 .field("engine", &engine)
797 .field("lang", &lang)
798 .field("html", &html)
799 .field("cache", &cache)
800 .field("db", &db)
801 .field("session_key", &session_key)
802 .field("salt", &salt)
803 .field("mail", &mail)
804 .field("action_index", &action_index)
805 .field("action_not_found", &action_not_found)
806 .field("action_err", &action_err)
807 .field("stop", &stop)
808 .field("root", &root)
809 .finish();
810 res
811 }
812}