use std::{cell::Ref, cell::RefMut, fmt, net};
use crate::http::header;
use crate::http::{HeaderMap, HttpMessage, Method, Payload, RequestHead, Response, Uri, Version};
use crate::io::{IoRef, types};
use crate::router::{Path, Resource};
use crate::util::Extensions;
use super::config::WebAppConfig;
use super::info::ConnectionInfo;
use super::{AppState, HttpRequest, WebResponse, WebResponseError, rmap::ResourceMap};
pub struct WebRequest<St = ()> {
pub(crate) req: HttpRequest,
payload: Payload,
state: St,
}
impl<St> WebRequest<St> {
pub(crate) fn new(req: HttpRequest, payload: Payload, state: St) -> Self {
WebRequest {
req,
payload,
state,
}
}
#[inline]
pub fn error_response<AppSt, E>(self, st: &AppSt, err: &E) -> WebResponse
where
AppSt: AppState,
E: WebResponseError<AppSt, AppSt::Error>,
{
WebResponse::new(err.error_response(st), self.req)
}
}
impl WebRequest<()> {
pub fn from_request(req: HttpRequest) -> Self {
WebRequest {
req,
payload: Payload::None,
state: (),
}
}
}
impl<St> WebRequest<St> {
pub fn into_parts(self) -> (HttpRequest, Payload, St) {
(self.req, self.payload, self.state)
}
pub fn from_parts(req: HttpRequest, payload: Payload, state: St) -> Self {
WebRequest {
req,
payload,
state,
}
}
#[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).app_config().state()
}
#[inline]
pub fn extensions(&self) -> Ref<'_, Extensions> {
self.req.extensions()
}
#[inline]
pub fn extensions_mut(&self) -> RefMut<'_, Extensions> {
self.req.extensions_mut()
}
pub fn st(&self) -> &St {
&self.state
}
pub fn st_mut(&mut self) -> &mut St {
&mut self.state
}
#[inline]
pub fn map_state<F, NewSt>(self, f: F) -> WebRequest<NewSt>
where
F: FnOnce(St) -> NewSt,
{
let WebRequest {
req,
payload,
state,
} = self;
WebRequest {
req,
payload,
state: f(state),
}
}
}
impl<St> Resource<Uri> for WebRequest<St> {
fn path(&self) -> &str {
self.match_info().path()
}
fn resource_path(&mut self) -> &mut Path<Uri> {
self.match_info_mut()
}
}
impl<St> HttpMessage for WebRequest<St> {
#[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<St> fmt::Debug for WebRequest<St> {
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, 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.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));
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>"));
}
}