1use std::collections::HashMap;
11
12use http::Response;
13use http_body_util::BodyExt;
14use tokio::io::{AsyncReadExt, AsyncWriteExt};
15use tokio::net::TcpStream;
16use tracing::debug;
17
18use crate::{Body, ProxyError, goals};
19
20pub struct ScgiHandler {
22 addr: String,
24 env: HashMap<String, String>,
26}
27
28impl ScgiHandler {
29 pub fn new(addr: String, env: HashMap<String, String>) -> Self {
30 Self { addr, env }
31 }
32}
33
34#[salvo::async_trait]
35impl salvo::Handler for ScgiHandler {
36 async fn handle(
37 &self,
38 req: &mut salvo::Request,
39 _depot: &mut salvo::Depot,
40 res: &mut salvo::Response,
41 ctrl: &mut salvo::FlowCtrl,
42 ) {
43 let client_addr = crate::hoops::client_addr(req);
44 let request = match goals::strip_request(req) {
45 Ok(r) => r,
46 Err(e) => {
47 goals::merge_response(res, e.into_response());
48 ctrl.skip_rest();
49 return;
50 }
51 };
52 let response = self
53 .run(request, client_addr)
54 .await
55 .unwrap_or_else(|e| e.into_response());
56 goals::merge_response(res, response);
57 ctrl.skip_rest();
58 }
59}
60
61impl ScgiHandler {
62 async fn run(
63 &self,
64 request: http::Request<Body>,
65 client_addr: std::net::SocketAddr,
66 ) -> Result<Response<Body>, ProxyError> {
67 let (parts, body) = request.into_parts();
68 let body_bytes = body
69 .collect()
70 .await
71 .map_err(|e| ProxyError::Internal(format!("body collect: {e}")))?
72 .to_bytes();
73
74 let mut headers: Vec<u8> = Vec::new();
78 push_scgi_header(
79 &mut headers,
80 "CONTENT_LENGTH",
81 &body_bytes.len().to_string(),
82 );
83 push_scgi_header(&mut headers, "SCGI", "1");
84 push_scgi_header(&mut headers, "REQUEST_METHOD", parts.method.as_str());
85 push_scgi_header(&mut headers, "REQUEST_URI", &parts.uri.to_string());
86 push_scgi_header(
87 &mut headers,
88 "QUERY_STRING",
89 parts.uri.query().unwrap_or(""),
90 );
91 push_scgi_header(
92 &mut headers,
93 "SERVER_PROTOCOL",
94 &format!("{:?}", parts.version),
95 );
96 push_scgi_header(&mut headers, "REMOTE_ADDR", &client_addr.ip().to_string());
97 push_scgi_header(&mut headers, "REMOTE_PORT", &client_addr.port().to_string());
98 push_scgi_header(&mut headers, "SERVER_SOFTWARE", "gatel");
99 push_scgi_header(&mut headers, "GATEWAY_INTERFACE", "CGI/1.1");
100
101 let path = parts
102 .uri
103 .path_and_query()
104 .map(|pq| pq.as_str().to_string())
105 .unwrap_or_else(|| parts.uri.path().to_string());
106 push_scgi_header(&mut headers, "SCRIPT_NAME", parts.uri.path());
107 push_scgi_header(&mut headers, "PATH_INFO", parts.uri.path());
108 push_scgi_header(&mut headers, "DOCUMENT_URI", &path);
109
110 if let Some(ct) = parts
111 .headers
112 .get("content-type")
113 .and_then(|v| v.to_str().ok())
114 {
115 push_scgi_header(&mut headers, "CONTENT_TYPE", ct);
116 }
117
118 if let Some(host) = parts.headers.get("host").and_then(|v| v.to_str().ok()) {
119 push_scgi_header(
120 &mut headers,
121 "SERVER_NAME",
122 host.split(':').next().unwrap_or(host),
123 );
124 if let Some(port) = host.split(':').nth(1) {
125 push_scgi_header(&mut headers, "SERVER_PORT", port);
126 }
127 }
128
129 for (name, value) in &parts.headers {
131 if let Ok(v) = value.to_str() {
132 let env_name = format!("HTTP_{}", name.as_str().to_uppercase().replace('-', "_"));
133 push_scgi_header(&mut headers, &env_name, v);
134 }
135 }
136
137 for (k, v) in &self.env {
139 push_scgi_header(&mut headers, k, v);
140 }
141
142 let header_len = headers.len();
144 let mut payload = format!("{header_len}:").into_bytes();
145 payload.extend_from_slice(&headers);
146 payload.push(b',');
147 payload.extend_from_slice(&body_bytes);
148
149 debug!(addr = %self.addr, "connecting to SCGI server");
150
151 let mut stream = TcpStream::connect(&self.addr)
153 .await
154 .map_err(|e| ProxyError::Internal(format!("SCGI connect to {}: {e}", self.addr)))?;
155
156 stream.write_all(&payload).await.map_err(ProxyError::Io)?;
157 stream.flush().await.map_err(ProxyError::Io)?;
158
159 let mut response_buf = Vec::new();
160 stream
161 .read_to_end(&mut response_buf)
162 .await
163 .map_err(ProxyError::Io)?;
164
165 crate::proxy::cgi::parse_cgi_response(&response_buf)
167 }
168}
169
170fn push_scgi_header(buf: &mut Vec<u8>, name: &str, value: &str) {
176 buf.extend_from_slice(name.as_bytes());
177 buf.push(0);
178 buf.extend_from_slice(value.as_bytes());
179 buf.push(0);
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185
186 #[test]
187 fn test_push_scgi_header() {
188 let mut buf = Vec::new();
189 push_scgi_header(&mut buf, "CONTENT_LENGTH", "0");
190 assert_eq!(buf.len(), 17);
192 assert_eq!(buf[14], 0); assert_eq!(buf[15], b'0');
194 assert_eq!(buf[16], 0); }
196}