1use std::{ffi::OsStr, future::Future, path::PathBuf};
4
5pub use cegla::client::CgiBuilder;
6
7use cegla::{
8 client::{convert_to_http_response, CgiResponseInner},
9 CgiEnvironment, CgiIncoming,
10};
11use http_body::Body;
12use tokio::io::{AsyncRead, AsyncWrite};
13use tokio_util::io::StreamReader;
14
15pub trait Runtime {
17 type Child: Child;
18
19 fn spawn(&self, future: impl Future + 'static);
21
22 fn start_child(
24 &self,
25 cmd: &OsStr,
26 args: &[&OsStr],
27 env: CgiEnvironment,
28 cwd: Option<PathBuf>,
29 ) -> Result<Self::Child, std::io::Error>;
30}
31
32pub trait SendRuntime {
34 type Child: SendChild;
35
36 fn spawn(&self, future: impl Future + Send + 'static);
38
39 fn start_child(
41 &self,
42 cmd: &OsStr,
43 args: &[&OsStr],
44 env: CgiEnvironment,
45 cwd: Option<PathBuf>,
46 ) -> Result<Self::Child, std::io::Error>;
47}
48
49pub trait Child {
51 type Stdin: AsyncWrite + Unpin + 'static;
52 type Stdout: AsyncRead + Unpin + 'static;
53 type Stderr: AsyncRead + Unpin + 'static;
54
55 fn stdin(&mut self) -> Option<Self::Stdin>;
57
58 fn stdout(&mut self) -> Option<Self::Stdout>;
60
61 fn stderr(&mut self) -> Option<Self::Stderr>;
63
64 fn try_status(&mut self) -> std::io::Result<Option<std::process::ExitStatus>>;
66}
67
68pub trait SendChild {
70 type Stdin: AsyncWrite + Send + Unpin + 'static;
71 type Stdout: AsyncRead + Send + Unpin + 'static;
72 type Stderr: AsyncRead + Send + Unpin + 'static;
73
74 fn stdin(&mut self) -> Option<Self::Stdin>;
76
77 fn stdout(&mut self) -> Option<Self::Stdout>;
79
80 fn stderr(&mut self) -> Option<Self::Stderr>;
82
83 fn try_status(&mut self) -> std::io::Result<Option<std::process::ExitStatus>>;
85}
86
87pub async fn execute_cgi<B, R>(
89 request: http::Request<B>,
90 runtime: R,
91 cmd: &OsStr,
92 args: &[&OsStr],
93 env: CgiBuilder,
94 cwd: Option<PathBuf>,
95) -> Result<
96 (
97 http::Response<CgiIncoming<CgiResponseInner<<R::Child as Child>::Stdout>>>,
98 Option<<R::Child as Child>::Stderr>,
99 Option<std::process::ExitStatus>,
100 ),
101 std::io::Error,
102>
103where
104 B: Body + 'static,
105 B::Data: AsRef<[u8]> + Send + 'static,
106 B::Error: Into<std::io::Error>,
107 R: Runtime,
108{
109 let (cgi_environment, cgi_data) = env.build(request);
110 let mut child = runtime.start_child(cmd, args, cgi_environment, cwd)?;
111
112 let mut stdin = child.stdin().ok_or(std::io::Error::other("Failed to take stdin"))?;
113 let stdout = child.stdout().ok_or(std::io::Error::other("Failed to take stdout"))?;
114 let stderr = child.stderr();
115
116 let mut cgi_data_reader = StreamReader::new(cgi_data);
117 runtime.spawn(async move {
118 let _ = tokio::io::copy(&mut cgi_data_reader, &mut stdin).await;
119 });
120
121 Ok((convert_to_http_response(stdout).await?, stderr, child.try_status()?))
122}
123
124pub async fn execute_cgi_send<B, R>(
126 request: http::Request<B>,
127 runtime: R,
128 cmd: &OsStr,
129 args: &[&OsStr],
130 env: CgiBuilder,
131 cwd: Option<PathBuf>,
132) -> Result<
133 (
134 http::Response<CgiIncoming<CgiResponseInner<<R::Child as SendChild>::Stdout>>>,
135 Option<<R::Child as SendChild>::Stderr>,
136 Option<std::process::ExitStatus>,
137 ),
138 std::io::Error,
139>
140where
141 B: Body + Send + 'static,
142 B::Data: AsRef<[u8]> + Send + 'static,
143 B::Error: Into<std::io::Error>,
144 R: SendRuntime,
145{
146 let (cgi_environment, cgi_data) = env.build(request);
147 let mut child = runtime.start_child(cmd, args, cgi_environment, cwd)?;
148
149 let mut stdin = child.stdin().ok_or(std::io::Error::other("Failed to take stdin"))?;
150 let stdout = child.stdout().ok_or(std::io::Error::other("Failed to take stdout"))?;
151 let stderr = child.stderr();
152
153 let mut cgi_data_reader = StreamReader::new(cgi_data);
154 runtime.spawn(async move {
155 let _ = tokio::io::copy(&mut cgi_data_reader, &mut stdin).await;
156 });
157
158 Ok((convert_to_http_response(stdout).await?, stderr, child.try_status()?))
159}