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 = "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 = "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 {
131 Self::new(params, stdin.compat())
132 }
133}
134
135#[cfg(all(test, feature = "http"))]
136mod http_tests {
137 use crate::{Params, Request, io};
138
139 #[test]
140 fn request_from_http_with_extras() {
141 let request = ::http::Request::builder()
142 .method(::http::Method::POST)
143 .uri("/submit?foo=bar")
144 .header(::http::header::HOST, "example.com")
145 .body(io::Cursor::new(b"body".to_vec()))
146 .unwrap();
147
148 let extras = Params::default()
149 .script_filename("/srv/www/index.php")
150 .script_name("/index.php");
151 let request = Request::try_from_http_with(request, extras).unwrap();
152
153 assert_eq!(request.params()["REQUEST_METHOD"], "POST");
154 assert_eq!(request.params()["QUERY_STRING"], "foo=bar");
155 assert_eq!(request.params()["HTTP_HOST"], "example.com");
156 assert_eq!(request.params()["SCRIPT_FILENAME"], "/srv/www/index.php");
157 }
158
159 #[test]
160 fn request_into_http_preserves_stream_body() {
161 let params = Params::default()
162 .request_method("GET")
163 .request_uri("/index.php");
164 let request = Request::new(params, io::empty());
165
166 let http_request = request.try_into_http().unwrap();
167
168 assert_eq!(http_request.method(), ::http::Method::GET);
169 assert_eq!(http_request.uri(), "/index.php");
170 }
171}