Skip to main content

fastcgi_client/
request.rs

1// Copyright 2022 jmjoy
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! FastCGI request structure and builders.
16//!
17//! This module provides the `Request` struct that encapsulates
18//! the parameters and stdin data for a FastCGI request.
19
20#[cfg(feature = "http")]
21use std::{borrow::Cow, collections::HashMap};
22
23use crate::{Params, io::AsyncRead};
24
25#[cfg(feature = "http")]
26use crate::{HttpConversionError, HttpConversionResult};
27
28#[cfg(feature = "runtime-tokio")]
29use crate::io::{TokioAsyncReadCompatExt, TokioCompat};
30
31/// FastCGI request containing parameters and stdin data.
32///
33/// This structure represents a complete FastCGI request with all necessary
34/// parameters and an optional stdin stream for request body data.
35pub struct Request<'a, I: AsyncRead + Unpin> {
36    pub(crate) params: Params<'a>,
37    pub(crate) stdin: I,
38}
39
40impl<'a, I: AsyncRead + Unpin> Request<'a, I> {
41    /// Creates a new FastCGI request with the given parameters and stdin.
42    ///
43    /// # Arguments
44    ///
45    /// * `params` - The FastCGI parameters
46    /// * `stdin` - The stdin stream for request body data
47    pub fn new(params: Params<'a>, stdin: I) -> Self {
48        Self { params, stdin }
49    }
50
51    /// Returns a reference to the request parameters.
52    pub fn params(&self) -> &Params<'a> {
53        &self.params
54    }
55
56    /// Returns a mutable reference to the request parameters.
57    pub fn params_mut(&mut self) -> &mut Params<'a> {
58        &mut self.params
59    }
60
61    /// Returns a reference to the stdin stream.
62    pub fn stdin(&self) -> &I {
63        &self.stdin
64    }
65
66    /// Returns a mutable reference to the stdin stream.
67    pub fn stdin_mut(&mut self) -> &mut I {
68        &mut self.stdin
69    }
70
71    /// Converts a FastCGI request into an `http::Request` without buffering the
72    /// body.
73    #[cfg(feature = "http")]
74    pub fn try_into_http(self) -> HttpConversionResult<::http::Request<I>> {
75        self.try_into()
76    }
77}
78
79#[cfg(feature = "http")]
80impl<I> Request<'static, I>
81where
82    I: AsyncRead + Unpin,
83{
84    /// Builds a FastCGI request from an `http::Request`, merging
85    /// caller-provided FastCGI extras such as `SCRIPT_FILENAME`.
86    pub fn try_from_http_with<'a>(
87        request: ::http::Request<I>, extras: Params<'a>,
88    ) -> HttpConversionResult<Self> {
89        let (parts, body) = request.into_parts();
90        let mut params: Params<'static> = parts.try_into()?;
91        for (name, value) in HashMap::<Cow<'a, str>, Cow<'a, str>>::from(extras) {
92            params.insert(
93                Cow::Owned(name.into_owned()),
94                Cow::Owned(value.into_owned()),
95            );
96        }
97        Ok(Request::new(params, body))
98    }
99
100    /// Builds a FastCGI request from an `http::Request` using only
101    /// HTTP-representable metadata.
102    pub fn try_from_http(request: ::http::Request<I>) -> HttpConversionResult<Self> {
103        Self::try_from_http_with(request, Params::default())
104    }
105}
106
107#[cfg(feature = "http")]
108impl<'a, I> TryFrom<Request<'a, I>> for ::http::Request<I>
109where
110    I: AsyncRead + Unpin,
111{
112    type Error = HttpConversionError;
113
114    fn try_from(request: Request<'a, I>) -> Result<Self, Self::Error> {
115        let (parts, body) = ((&request.params).try_into()?, request.stdin);
116        Ok(::http::Request::from_parts(parts, body))
117    }
118}
119
120#[cfg(feature = "runtime-tokio")]
121impl<'a, I> Request<'a, TokioCompat<I>>
122where
123    I: tokio::io::AsyncRead + Unpin,
124{
125    /// Creates a new FastCGI request from a Tokio reader.
126    pub fn new_tokio(params: Params<'a>, stdin: I) -> Self {
127        Self::new(params, stdin.compat())
128    }
129}
130
131#[cfg(feature = "runtime-smol")]
132impl<'a, I> Request<'a, I>
133where
134    I: AsyncRead + Unpin,
135{
136    /// Creates a new FastCGI request from a Smol-compatible reader.
137    pub fn new_smol(params: Params<'a>, stdin: I) -> Self {
138        Self::new(params, stdin)
139    }
140}
141
142#[cfg(all(test, feature = "http"))]
143mod http_tests {
144    use crate::{Params, Request, io};
145
146    #[test]
147    fn request_from_http_with_extras() {
148        let request = ::http::Request::builder()
149            .method(::http::Method::POST)
150            .uri("/submit?foo=bar")
151            .header(::http::header::HOST, "example.com")
152            .body(io::Cursor::new(b"body".to_vec()))
153            .unwrap();
154
155        let extras = Params::default()
156            .script_filename("/srv/www/index.php")
157            .script_name("/index.php");
158        let request = Request::try_from_http_with(request, extras).unwrap();
159
160        assert_eq!(request.params()["REQUEST_METHOD"], "POST");
161        assert_eq!(request.params()["QUERY_STRING"], "foo=bar");
162        assert_eq!(request.params()["HTTP_HOST"], "example.com");
163        assert_eq!(request.params()["SCRIPT_FILENAME"], "/srv/www/index.php");
164    }
165
166    #[test]
167    fn request_into_http_preserves_stream_body() {
168        let params = Params::default()
169            .request_method("GET")
170            .request_uri("/index.php");
171        let request = Request::new(params, io::empty());
172
173        let http_request = request.try_into_http().unwrap();
174
175        assert_eq!(http_request.method(), ::http::Method::GET);
176        assert_eq!(http_request.uri(), "/index.php");
177    }
178}