1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
use std::{sync::Arc, time::Duration};

use tokio::{io::AsyncReadExt, net::TcpStream, sync::Mutex, time::timeout};

use super::{
    action::ActMap,
    cache::Cache,
    html::Html,
    lang::Lang,
    log::Log,
    pool::DBPool,
    workers::{fastcgi, grpc, http, scgi, uwsgi},
};

/// Connection processing workflow
pub struct Worker;

/// Worker type
///
/// # Values
///
/// * `FastCGI` - FastCGI protocol.
/// * `Uwsgi` - UWSGI protocol.
/// * `Grpc` - GRPC protocol.
/// * `Scgi` - SCGI protocol.
/// * `Http` - HTTP or WebSocket protocol.
enum WorkerType {
    /// FastCGI protocol.
    FastCGI,
    /// UWSGI protocol.
    Uwsgi,
    /// GRPC protocol.
    Grpc,
    /// SCGI protocol.
    Scgi,
    /// HTTP or WebSocket protocol.
    Http,
}

/// General data
///
/// # Values
///
/// * `engine: Arc<ActMap>` - Engine - binary tree of controller functions.
/// * `lang: Arc<Lang>` - I18n system.
/// * `html: Arc<Html>` - Template maker.
/// * `cache: Arc<Mutex<Cache>>` - Cache system.
/// * `db: Arc<DBPool>` - Database connections pool.
/// * `salt: Arc<String>` - Salt for a crypto functions.
pub struct WorkerData {
    /// Engine - binary tree of controller functions.
    pub engine: Arc<ActMap>,
    /// I18n system.
    pub lang: Arc<Lang>,
    /// Template maker.
    pub html: Arc<Html>,
    /// Cache system.
    pub cache: Arc<Mutex<Cache>>,
    /// Database connections pool.
    pub db: Arc<DBPool>,
    /// Salt for a crypto functions.
    pub salt: Arc<String>,
}

impl Worker {
    /// Run main worker
    ///
    /// # Params
    ///
    /// * `mut stream: TcpStream` - Tokio tcp stream.
    /// * `data: WorkerData` - General data for the web engine.
    pub async fn run(mut stream: TcpStream, data: WorkerData) {
        // Read first data from stream to detect protocol
        let mut buf: [u8; 8192] = [0; 8192];
        let mut len = 0;
        if let Err(e) = timeout(Duration::from_secs(1), async {
            len = match stream.read(&mut buf).await {
                Ok(len) => len,
                Err(e) => {
                    Log::warning(2000, Some(e.to_string()));
                    return;
                }
            };
        })
        .await
        {
            Log::warning(2001, Some(e.to_string()));
            return;
        };

        match Worker::detect(&buf).await {
            WorkerType::FastCGI => fastcgi::Net::run(stream, data, buf, len).await,
            WorkerType::Uwsgi => uwsgi::Net::run(stream, data, buf, len).await,
            WorkerType::Grpc => grpc::Net::run(stream, data, buf, len).await,
            WorkerType::Scgi => scgi::Net::run(stream, data, buf, len).await,
            WorkerType::Http => http::Net::run(stream, data, buf, len).await,
        }
    }

    /// Autodetect protocol
    ///
    /// # Params
    ///
    /// * `slice: &[u8; 8192]` - Slice of some first data from stream
    ///
    /// # Return
    /// * `WorkerType` - Type of the protocol
    ///
    /// # Notice
    ///
    /// This is a very easy and fast way to determine the protocol, but you shouldn't rely on it.
    async fn detect(slice: &[u8; 8192]) -> WorkerType {
        if slice[0..1] == [1] {
            // FastCGI starts with a byte equal to 1 (version)
            WorkerType::FastCGI
        } else if slice[0..1] == [0] {
            // UWSGI starts with a byte equal to 0 (modifier1)
            WorkerType::Uwsgi
        } else if slice[0..14]
            == [
                0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x32, 0x2e, 0x30,
            ]
        {
            // gRPC starts with a bytes equal to "PRI * HTTP/2.0"
            WorkerType::Grpc
        } else if slice[0..6].iter().enumerate().any(|(idx, byte)| {
            if *byte == 0x3a {
                let num = String::from_utf8_lossy(&slice[..idx]);
                num.parse::<u16>().is_ok()
            } else {
                false
            }
        }) {
            // SCGI starts with bytes equal to a number (string format) and the character ":"
            WorkerType::Scgi
        } else {
            // Everything else to be HTTP or WebSocket
            WorkerType::Http
        }
    }
}