use std::{cell::Ref, cell::RefMut, fmt, marker::PhantomData, net, rc::Rc};
use crate::http::{
HeaderMap, HttpMessage, Method, Payload, RequestHead, Response, Uri, Version, header,
};
use crate::io::{IoRef, types};
use crate::router::{Path, Resource};
use crate::util::Extensions;
use super::config::WebAppConfig;
use super::error::{ErrorRenderer, WebResponseError};
use super::httprequest::HttpRequest;
use super::info::ConnectionInfo;
use super::response::WebResponse;
use super::rmap::ResourceMap;
use super::service::AppState;
pub struct WebRequest<Err> {
req: HttpRequest,
_t: PhantomData<Err>,
}
impl<Err: ErrorRenderer> WebRequest<Err> {
#[inline]
pub fn render_error<E: WebResponseError<Err>>(self, err: &E) -> WebResponse {
WebResponse::new(err.error_response(&self.req), self.req)
}
#[inline]
pub fn error_response<E: Into<Err::Container>>(self, err: E) -> WebResponse {
WebResponse::from_err::<Err, E>(err, self.req)
}
}
impl<Err> WebRequest<Err> {
pub(crate) fn new(req: HttpRequest) -> Self {
WebRequest {
req,
_t: PhantomData,
}
}
pub fn into_parts(mut self) -> (HttpRequest, Payload) {
let pl = Rc::get_mut(&mut (self.req).0).unwrap().payload.take();
(self.req, pl)
}
pub fn from_parts(
mut req: HttpRequest,
pl: Payload,
) -> Result<Self, (HttpRequest, Payload)> {
if Rc::strong_count(&req.0) == 1 {
Rc::get_mut(&mut req.0).unwrap().payload = pl;
Ok(WebRequest::new(req))
} else {
Err((req, pl))
}
}
pub fn from_request(req: HttpRequest) -> Result<Self, HttpRequest> {
if Rc::strong_count(&req.0) == 1 {
Ok(WebRequest::new(req))
} else {
Err(req)
}
}
#[inline]
pub fn into_response<R: Into<Response>>(self, res: R) -> WebResponse {
WebResponse::new(res.into(), self.req)
}
#[inline]
pub fn io(&self) -> Option<&IoRef> {
self.head().io.as_ref()
}
#[inline]
pub fn head(&self) -> &RequestHead {
self.req.head()
}
#[inline]
pub fn head_mut(&mut self) -> &mut RequestHead {
self.req.head_mut()
}
#[inline]
pub fn uri(&self) -> &Uri {
&self.head().uri
}
#[inline]
pub fn method(&self) -> &Method {
&self.head().method
}
#[inline]
pub fn version(&self) -> Version {
self.head().version
}
#[inline]
pub fn headers(&self) -> &HeaderMap {
&self.head().headers
}
#[inline]
pub fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.head_mut().headers
}
#[inline]
pub fn path(&self) -> &str {
self.head().uri.path()
}
#[inline]
pub fn query_string(&self) -> &str {
self.uri().query().unwrap_or_default()
}
#[inline]
pub fn peer_addr(&self) -> Option<net::SocketAddr> {
self.head()
.io
.as_ref()
.and_then(|io| io.query::<types::PeerAddr>().get().map(|addr| addr.0))
}
#[inline]
pub fn connection_info(&self) -> Ref<'_, ConnectionInfo> {
ConnectionInfo::get(self.head(), self.app_config())
}
#[inline]
pub fn match_info(&self) -> &Path<Uri> {
self.req.match_info()
}
#[inline]
pub fn match_info_mut(&mut self) -> &mut Path<Uri> {
self.req.match_info_mut()
}
#[inline]
pub fn resource_map(&self) -> &ResourceMap {
self.req.resource_map()
}
#[inline]
pub fn app_config(&self) -> &WebAppConfig {
self.req.app_config()
}
#[inline]
pub fn app_state<T: 'static>(&self) -> Option<&T> {
(self.req).0.app_state.get::<T>()
}
#[inline]
pub fn take_payload(&mut self) -> Payload {
Rc::get_mut(&mut (self.req).0).unwrap().payload.take()
}
#[inline]
pub fn set_payload(&mut self, payload: Payload) {
Rc::get_mut(&mut (self.req).0).unwrap().payload = payload;
}
pub(super) fn set_state_container(&mut self, state: AppState) {
Rc::get_mut(&mut (self.req).0).unwrap().app_state = state;
}
#[inline]
pub fn extensions(&self) -> Ref<'_, Extensions> {
self.req.extensions()
}
#[inline]
pub fn extensions_mut(&self) -> RefMut<'_, Extensions> {
self.req.extensions_mut()
}
}
impl<Err> Resource<Uri> for WebRequest<Err> {
fn path(&self) -> &str {
self.match_info().path()
}
fn resource_path(&mut self) -> &mut Path<Uri> {
self.match_info_mut()
}
}
impl<Err> HttpMessage for WebRequest<Err> {
#[inline]
fn message_headers(&self) -> &HeaderMap {
&self.head().headers
}
#[inline]
fn message_extensions(&self) -> Ref<'_, Extensions> {
self.req.extensions()
}
#[inline]
fn message_extensions_mut(&self) -> RefMut<'_, Extensions> {
self.req.extensions_mut()
}
}
impl<Err: ErrorRenderer> fmt::Debug for WebRequest<Err> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(
f,
"\nWebRequest {:?} {}:{}",
self.head().version,
self.head().method,
self.path()
)?;
if !self.query_string().is_empty() {
writeln!(f, " query: ?{:?}", self.query_string())?;
}
if !self.match_info().is_empty() {
writeln!(f, " params: {:?}", self.match_info())?;
}
writeln!(f, " headers:")?;
for (key, val) in self.headers() {
if key == header::AUTHORIZATION {
writeln!(f, " {key:?}: <REDACTED>")?;
} else {
writeln!(f, " {key:?}: {val:?}")?;
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::http::{self, HttpMessage, header};
use crate::web::HttpResponse;
use crate::web::test::TestRequest;
#[test]
fn test_request() {
let mut req = TestRequest::default().to_srv_request();
assert_eq!(req.head_mut().version, http::Version::HTTP_11);
assert!(req.peer_addr().is_none());
let err = http::error::PayloadError::Overflow;
let res: HttpResponse = req.render_error(&err).into();
assert_eq!(res.status(), http::StatusCode::PAYLOAD_TOO_LARGE);
let req = TestRequest::default().to_srv_request();
let err = http::error::PayloadError::Overflow;
let res: HttpResponse = req.error_response(err).into();
assert_eq!(res.status(), http::StatusCode::PAYLOAD_TOO_LARGE);
let mut req = TestRequest::default().to_srv_request();
req.headers_mut().insert(
header::CONTENT_TYPE,
header::HeaderValue::from_static("text"),
);
req.headers_mut().insert(
header::AUTHORIZATION,
header::HeaderValue::from_static("text"),
);
req.headers_mut().remove(header::CONTENT_TYPE);
assert!(!req.headers().contains_key(header::CONTENT_TYPE));
assert!(!req.message_headers().contains_key(header::CONTENT_TYPE));
let pl = req.take_payload();
req.set_payload(pl);
req.extensions_mut().insert("TEXT".to_string());
assert_eq!(req.message_extensions().get::<String>().unwrap(), "TEXT");
req.message_extensions_mut().remove::<String>();
assert!(!req.extensions().contains::<String>());
let t = format!("{req:?}");
assert!(t.contains("\"authorization\": <REDACTED>"));
}
}