diff --git a/Cargo.toml b/Cargo.toml
index 94a8658..e2e7b8b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,4 +1,5 @@
[workspace]
+resolver = "2"
members = [
"tower-cgi",
"cgi-rs",
diff --git a/cgi-rs/Cargo.toml b/cgi-rs/Cargo.toml
index 102c53f..ee4b499 100644
--- a/cgi-rs/Cargo.toml
+++ b/cgi-rs/Cargo.toml
@@ -4,6 +4,8 @@ version = "0.1.0"
edition = "2021"
[dependencies]
-hyper = "0.14"
+http-body-util = "0.1.2"
+hyper = "1.6.0"
snafu = "0.8"
tokio = "1"
+bytes = "1.10.0"
diff --git a/cgi-rs/src/lib.rs b/cgi-rs/src/lib.rs
index e4d9fa6..6ad77e6 100644
--- a/cgi-rs/src/lib.rs
+++ b/cgi-rs/src/lib.rs
@@ -11,7 +11,9 @@
//! ## Examples
//! ### Parsing an HTTP Request
//! ```rust
-//! use hyper::{Request, Body};
+//! use hyper::Request;
+//! use hyper::body::Bytes;
+//! use http_body_util::Full;
//! use cgi_rs::CGIRequest;
//!
//! // In a CGI environment, the CGI server would set these variables, as well as others.
@@ -19,7 +21,7 @@
//! std::env::set_var("CONTENT_LENGTH", "0");
//! std::env::set_var("REQUEST_URI", "/");
//!
-//! let cgi_request: Request
= CGIRequest::from_env()
+//! let cgi_request: Request> = CGIRequest::>::from_env()
//! .and_then(Request::try_from).unwrap();
//!
//! assert_eq!(cgi_request.method(), "GET");
@@ -106,6 +108,7 @@ pub enum MetaVariableKind {
HttpHost,
HttpUserAgent,
HttpAccept,
+ HttpCookie,
ServerSignature,
DocumentRoot,
RequestScheme,
@@ -148,6 +151,7 @@ impl MetaVariableKind {
MetaVariableKind::ScriptFilename => "SCRIPT_FILENAME",
MetaVariableKind::RemotePort => "REMOTE_PORT",
MetaVariableKind::RequestUri => "REQUEST_URI",
+ MetaVariableKind::HttpCookie => "HTTP_COOKIE"
}
}
diff --git a/cgi-rs/src/request.rs b/cgi-rs/src/request.rs
index 63c8128..0f33293 100644
--- a/cgi-rs/src/request.rs
+++ b/cgi-rs/src/request.rs
@@ -1,14 +1,18 @@
+use std::convert::Infallible;
use crate::{error, CGIError, MetaVariable, MetaVariableKind, Result};
-use hyper::{Body as HttpBody, Request};
+use hyper::Request;
+use hyper::body::{Body, Bytes};
use snafu::ResultExt;
use std::io::{stdin, Read};
+use http_body_util::combinators::BoxBody;
+use http_body_util::Full;
-pub struct CGIRequest {
- pub request_body: HttpBody,
+pub struct CGIRequest {
+ pub request_body: B
}
-impl CGIRequest {
- pub fn from_env() -> Result {
+impl CGIRequest where B: Body {
+ pub fn from_env() -> Result>> {
let content_length = MetaVariableKind::ContentLength
.from_env()
.map(|content_length| {
@@ -19,8 +23,15 @@ impl CGIRequest {
.transpose()?
.unwrap_or_default();
- let request_body = HttpBody::from(Self::request_body_from_env(content_length)?);
- Ok(Self { request_body })
+ let read_content = Self::request_body_from_env(content_length)?;
+
+ let request_body = Bytes::from(read_content);
+
+ let full = Full::from(request_body);
+
+ let result = CGIRequest { request_body: full };
+
+ Ok(result)
}
pub fn var(&self, kind: MetaVariableKind) -> Option {
@@ -44,11 +55,22 @@ impl CGIRequest {
self.var(MetaVariableKind::RequestUri)
.map(|uri| Ok(uri.as_str()?.to_string()))
.unwrap_or_else(|| {
+
+ let path_info_str = match MetaVariableKind::PathInfo.try_from_env() {
+ Ok(meta_variable) => {
+ String::from(meta_variable.as_str().unwrap_or(""))
+ }
+ Err(_) => {
+ String::from("")
+ }
+ };
+
let script_name = MetaVariableKind::ScriptName.try_from_env()?;
let query_string = MetaVariableKind::QueryString.try_from_env()?;
Ok(format!(
- "{}?{}",
+ "{}{}?{}",
script_name.as_str()?,
+ path_info_str,
query_string.as_str()?
))
})
@@ -65,10 +87,11 @@ macro_rules! try_set_headers {
};
}
-impl TryFrom for Request {
+impl TryFrom> for Request where B: Body {
type Error = CGIError;
- fn try_from(cgi_request: CGIRequest) -> Result {
+ fn try_from(cgi_request: CGIRequest) -> Result {
+
let mut request_builder = Request::builder()
.method(
cgi_request
@@ -84,6 +107,7 @@ impl TryFrom for Request {
["Accept", MetaVariableKind::HttpAccept],
["Host", MetaVariableKind::HttpHost],
["User-Agent", MetaVariableKind::HttpUserAgent],
+ ["Cookie", MetaVariableKind::HttpCookie],
);
request_builder
diff --git a/cgi-rs/src/response.rs b/cgi-rs/src/response.rs
index 616fff0..ebdaf09 100644
--- a/cgi-rs/src/response.rs
+++ b/cgi-rs/src/response.rs
@@ -2,16 +2,19 @@ use crate::{error, CGIError, Result};
use hyper::{http::HeaderValue, HeaderMap, Response};
use snafu::ResultExt;
use std::io::Write;
+use bytes::Bytes;
+use http_body_util::{Full};
+use hyper::body::{Body};
#[derive(Debug)]
-pub struct CGIResponse {
- headers: HeaderMap,
- status: String,
- reason: Option,
- body: B,
+pub struct CGIResponse {
+ pub headers: HeaderMap,
+ pub status: String,
+ pub reason: Option,
+ pub body: Bytes,
}
-impl CGIResponse {
+impl CGIResponse {
pub async fn write_response_to_output(self, mut output: impl Write) -> Result<()> {
self.write_status(&mut output).await?;
self.write_headers(&mut output).await?;
@@ -50,29 +53,11 @@ impl CGIResponse {
}
async fn write_body(self, output: &mut impl Write) -> Result<()> {
- let body = hyper::body::to_bytes(self.body)
- .await
- .or_else(|_| error::BuildResponseSnafu.fail())?;
+ let body = self.body;
- output.write(&body).context(error::WriteResponseSnafu)?;
+ output.write(body.as_ref()).context(error::WriteResponseSnafu)?;
Ok(())
}
}
-impl TryFrom> for CGIResponse {
- type Error = CGIError;
-
- fn try_from(response: Response) -> Result {
- let headers = response.headers().clone();
- let status = response.status().to_string();
- let reason = response.status().canonical_reason().map(|s| s.to_string());
- let body = response.into_body();
- Ok(CGIResponse {
- headers,
- status,
- reason,
- body,
- })
- }
-}
diff --git a/sample-cgi-script/Cargo.toml b/sample-cgi-script/Cargo.toml
index 12561c6..a3ecd8c 100644
--- a/sample-cgi-script/Cargo.toml
+++ b/sample-cgi-script/Cargo.toml
@@ -6,6 +6,9 @@ license = "Apache-2.0"
publish = false
[dependencies]
-axum = "0.6"
+axum = "0.8.4"
tower-cgi = { path = "../tower-cgi" }
tokio = { version = "1", features = ["full"] }
+tower-sessions = "0.14.0"
+tower-cookies = "0.11.0"
+tower-sessions-file-based-store = "*"
diff --git a/sample-cgi-script/src/main.rs b/sample-cgi-script/src/main.rs
index 4495f83..af7b57b 100644
--- a/sample-cgi-script/src/main.rs
+++ b/sample-cgi-script/src/main.rs
@@ -1,12 +1,38 @@
use axum::{routing::get, Router};
+use axum::http::StatusCode;
+use axum::response::Response;
+use tower_cookies::{Cookie, Cookies};
+use tower_sessions::cookie::time::Duration;
+use tower_sessions::{MemoryStore, Session, SessionStore};
+use tower_sessions::session::Record;
use tower_cgi::serve_cgi;
+use tower_sessions_file_based_store::FileStore;
+
#[tokio::main]
async fn main() {
+ let session_store = FileStore::new("./", "prefix-", ".json");
+ // let session_store = MemoryStore::default();
+ let session_layer = tower_sessions::SessionManagerLayer::new(session_store)
+ .with_secure(false)
+ //.with_always_save(true)
+ .with_expiry(tower_sessions::Expiry::OnInactivity(Duration::seconds(15)));
+
let app = Router::new().route(
- "/cgi-bin/sample-cgi-server",
- get(|| async { "Hello, World!" }),
- );
+ "/cgi-bin/sample-cgi-server/",
+ get(|cookies: Cookies, session: Session| async move {
+ cookies.add(Cookie::new("hello_world", "hello_world"));
+ session.clear().await;
+ session.insert("foo", "bar").await.unwrap();
+ let value: String = session.get("foo").await.unwrap().unwrap_or("no value".to_string());
+
+ value
+
+ }),
+ ).route(
+ "/cgi-bin/sample-cgi-server/with/path-info",
+ get(|| async { "Hello, PATH_INFO" }),
+ ).layer(session_layer);
if let Err(e) = serve_cgi(app).await {
eprintln!("Error while serving CGI request: {}", e);
diff --git a/tower-cgi/Cargo.toml b/tower-cgi/Cargo.toml
index 3f7d6c8..5dda0f1 100644
--- a/tower-cgi/Cargo.toml
+++ b/tower-cgi/Cargo.toml
@@ -6,10 +6,11 @@ license = "Apache-2.0"
[dependencies]
cgi-rs = { path = "../cgi-rs" }
-hyper = { version = "0.14", default-features = false }
+hyper = { version = "1.6.0", default-features = false }
snafu = "0.8"
tower = { version = "0.5", default-features = false, features = ["util"] }
+http-body-util = "0.1.2"
+axum = "0.8.4"
[dev-dependencies]
-axum = "0.6"
tokio = { version = "1", features = ["full"] }
diff --git a/tower-cgi/src/lib.rs b/tower-cgi/src/lib.rs
index 38287b6..45dc744 100644
--- a/tower-cgi/src/lib.rs
+++ b/tower-cgi/src/lib.rs
@@ -17,10 +17,13 @@
//! ```
use cgi_rs::{CGIError, CGIRequest, CGIResponse};
-use hyper::{Body as HttpBody, Request, Response};
use snafu::ResultExt;
use std::convert::Infallible;
+use std::fmt::Debug;
use std::io::Write;
+use http_body_util::{Full, BodyExt};
+use hyper::body::{Body, Bytes};
+use hyper::{Request, Response};
use tower::{Service, ServiceExt};
/// Serve a CGI application.
@@ -28,11 +31,11 @@ use tower::{Service, ServiceExt};
/// Responses are emitted to stdout per the CGI RFC3875
pub async fn serve_cgi(app: S) -> Result<()>
where
- S: Service, Response = Response, Error = Infallible>
+ S: Service>, Response = Response, Error = Infallible>
+ Clone
+ Send
+ 'static,
- B: hyper::body::HttpBody,
+ B: Body, ::Error: Debug
{
serve_cgi_with_output(std::io::stdout(), app).await
}
@@ -42,13 +45,13 @@ where
/// Responses are emitted to the provided output stream.
pub async fn serve_cgi_with_output(output: impl Write, app: S) -> Result<()>
where
- S: Service, Response = Response, Error = Infallible>
+ S: Service>, Response = Response, Error = Infallible>
+ Clone
+ Send
+ 'static,
- B: hyper::body::HttpBody,
+ B: Body, ::Error: Debug
{
- let request = CGIRequest::from_env()
+ let request = CGIRequest::>::from_env()
.and_then(Request::try_from)
.context(error::CGIRequestParseSnafu)?;
@@ -57,7 +60,21 @@ where
.await
.expect("The Error type is Infallible, this should never fail.");
- let cgi_response: CGIResponse = response.try_into().context(error::CGIResponseParseSnafu)?;
+ let headers = response.headers().clone();
+ let status = response.status().to_string();
+ let reason = response.status().canonical_reason().map(|s| s.to_string());
+
+ let collected = response.into_body().collect().await;
+
+ let body_bytes = collected.unwrap().to_bytes();
+
+ let cgi_response = CGIResponse {
+ headers,
+ status,
+ reason,
+ body: body_bytes,
+ };
+
cgi_response
.write_response_to_output(output)
.await