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(&self, cmd: &OsStr, args: &[&OsStr], env: CgiEnvironment) -> Result<Self::Child, std::io::Error>;
24}
25
26pub trait SendRuntime {
28 type Child: SendChild;
29
30 fn spawn(&self, future: impl Future + Send + 'static);
32
33 fn start_child(
35 &self,
36 cmd: &OsStr,
37 args: &[&OsStr],
38 env: CgiEnvironment,
39 cwd: Option<PathBuf>,
40 ) -> Result<Self::Child, std::io::Error>;
41}
42
43pub trait Child {
45 type Stdin: AsyncWrite + Unpin + 'static;
46 type Stdout: AsyncRead + Unpin + 'static;
47 type Stderr: AsyncRead + Unpin + 'static;
48
49 fn stdin(&mut self) -> Option<Self::Stdin>;
51
52 fn stdout(&mut self) -> Option<Self::Stdout>;
54
55 fn stderr(&mut self) -> Option<Self::Stderr>;
57
58 fn try_status(&mut self) -> std::io::Result<Option<std::process::ExitStatus>>;
60}
61
62pub trait SendChild {
64 type Stdin: AsyncWrite + Send + Unpin + 'static;
65 type Stdout: AsyncRead + Send + Unpin + 'static;
66 type Stderr: AsyncRead + Send + Unpin + 'static;
67
68 fn stdin(&mut self) -> Option<Self::Stdin>;
70
71 fn stdout(&mut self) -> Option<Self::Stdout>;
73
74 fn stderr(&mut self) -> Option<Self::Stderr>;
76
77 fn try_status(&mut self) -> std::io::Result<Option<std::process::ExitStatus>>;
79}
80
81pub async fn execute_cgi<B, R>(
83 request: http::Request<B>,
84 runtime: R,
85 cmd: &OsStr,
86 args: &[&OsStr],
87 env: CgiBuilder,
88) -> Result<
89 (
90 http::Response<CgiIncoming<CgiResponseInner<<R::Child as Child>::Stdout>>>,
91 Option<<R::Child as Child>::Stderr>,
92 Option<std::process::ExitStatus>,
93 ),
94 std::io::Error,
95>
96where
97 B: Body + 'static,
98 B::Data: AsRef<[u8]> + Send + 'static,
99 B::Error: Into<std::io::Error>,
100 R: Runtime,
101{
102 let (cgi_environment, cgi_data) = env.build(request);
103 let mut child = runtime.start_child(cmd, args, cgi_environment)?;
104
105 let mut stdin = child.stdin().ok_or(std::io::Error::other("Failed to take stdin"))?;
106 let stdout = child.stdout().ok_or(std::io::Error::other("Failed to take stdout"))?;
107 let stderr = child.stderr();
108
109 let mut cgi_data_reader = StreamReader::new(cgi_data);
110 runtime.spawn(async move {
111 let _ = tokio::io::copy(&mut cgi_data_reader, &mut stdin).await;
112 });
113
114 Ok((convert_to_http_response(stdout).await?, stderr, child.try_status()?))
115}
116
117pub async fn execute_cgi_send<B, R>(
119 request: http::Request<B>,
120 runtime: R,
121 cmd: &OsStr,
122 args: &[&OsStr],
123 env: CgiBuilder,
124 cwd: Option<PathBuf>,
125) -> Result<
126 (
127 http::Response<CgiIncoming<CgiResponseInner<<R::Child as SendChild>::Stdout>>>,
128 Option<<R::Child as SendChild>::Stderr>,
129 Option<std::process::ExitStatus>,
130 ),
131 std::io::Error,
132>
133where
134 B: Body + Send + 'static,
135 B::Data: AsRef<[u8]> + Send + 'static,
136 B::Error: Into<std::io::Error>,
137 R: SendRuntime,
138{
139 let (cgi_environment, cgi_data) = env.build(request);
140 let mut child = runtime.start_child(cmd, args, cgi_environment, cwd)?;
141
142 let mut stdin = child.stdin().ok_or(std::io::Error::other("Failed to take stdin"))?;
143 let stdout = child.stdout().ok_or(std::io::Error::other("Failed to take stdout"))?;
144 let stderr = child.stderr();
145
146 let mut cgi_data_reader = StreamReader::new(cgi_data);
147 runtime.spawn(async move {
148 let _ = tokio::io::copy(&mut cgi_data_reader, &mut stdin).await;
149 });
150
151 Ok((convert_to_http_response(stdout).await?, stderr, child.try_status()?))
152}