fastcgi_client/
request.rs1#[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
31pub 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 pub fn new(params: Params<'a>, stdin: I) -> Self {
48 Self { params, stdin }
49 }
50
51 pub fn params(&self) -> &Params<'a> {
53 &self.params
54 }
55
56 pub fn params_mut(&mut self) -> &mut Params<'a> {
58 &mut self.params
59 }
60
61 pub fn stdin(&self) -> &I {
63 &self.stdin
64 }
65
66 pub fn stdin_mut(&mut self) -> &mut I {
68 &mut self.stdin
69 }
70
71 #[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 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 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 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 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}