cegla_cgi/
client.rs

1//! CGI implementation for web servers.
2
3use 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
15/// Runtime trait for CGI "client".
16pub trait Runtime {
17  type Child: Child;
18
19  /// Spawns a new task to execute the given future.
20  fn spawn(&self, future: impl Future + 'static);
21
22  /// Starts a child process with the given command and arguments.
23  fn start_child(&self, cmd: &OsStr, args: &[&OsStr], env: CgiEnvironment) -> Result<Self::Child, std::io::Error>;
24}
25
26/// `Send` runtime trait for CGI "client".
27pub trait SendRuntime {
28  type Child: SendChild;
29
30  /// Spawns a new task to execute the given future.
31  fn spawn(&self, future: impl Future + Send + 'static);
32
33  /// Starts a child process with the given command and arguments.
34  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
43/// Runtime trait for CGI child process.
44pub trait Child {
45  type Stdin: AsyncWrite + Unpin + 'static;
46  type Stdout: AsyncRead + Unpin + 'static;
47  type Stderr: AsyncRead + Unpin + 'static;
48
49  /// Obtains the standard input stream.
50  fn stdin(&mut self) -> Option<Self::Stdin>;
51
52  /// Obtains the standard output stream.
53  fn stdout(&mut self) -> Option<Self::Stdout>;
54
55  /// Obtains the standard error stream.
56  fn stderr(&mut self) -> Option<Self::Stderr>;
57
58  /// Returns the exit status if the process has exited.
59  fn try_status(&mut self) -> std::io::Result<Option<std::process::ExitStatus>>;
60}
61
62/// `Send` runtime trait for CGI child process.
63pub 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  /// Obtains the standard input stream.
69  fn stdin(&mut self) -> Option<Self::Stdin>;
70
71  /// Obtains the standard output stream.
72  fn stdout(&mut self) -> Option<Self::Stdout>;
73
74  /// Obtains the standard error stream.
75  fn stderr(&mut self) -> Option<Self::Stderr>;
76
77  /// Returns the exit status if the process has exited.
78  fn try_status(&mut self) -> std::io::Result<Option<std::process::ExitStatus>>;
79}
80
81/// Executes a CGI program, returning the response and error streams.
82pub 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
117/// Executes a CGI program, on a `Send` runtime, returning the response and error streams.
118pub 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}