use http_body_util::Full;
use hyper::body::{Bytes, Incoming};
use hyper::Request;
use rust_webx_core::auth::IClaims;
use rust_webx_core::error::Result;
use rust_webx_core::http::{IClaimsExt, IHttpContext, IHttpRequest, IHttpResponse};
use crate::problem_response::{build_problem, problem_to_bytes};
use std::collections::HashMap;
pub struct HttpContext {
req: HttpRequest,
resp: HttpResponse,
claims: Option<Box<dyn IClaims>>,
}
impl HttpContext {
pub async fn new(req: Request<Incoming>, max_body_size: usize) -> Self {
use http_body_util::BodyExt;
let (parts, mut body) = req.into_parts();
let method = parts.method.to_string();
let path = parts.uri.path().to_string();
let mut headers = HashMap::new();
for (name, value) in parts.headers.iter() {
if let Ok(v) = value.to_str() {
headers.insert(name.to_string(), v.to_string());
}
}
let query_params = parts
.uri
.query()
.map(parse_query_string)
.unwrap_or_default();
let mut body_bytes = Vec::new();
let mut total_size: usize = 0;
let mut payload_too_large = false;
while let Some(frame_result) = body.frame().await {
match frame_result {
Ok(frame) => {
if let Some(data) = frame.data_ref() {
total_size = total_size.saturating_add(data.len());
if total_size > max_body_size {
payload_too_large = true;
break;
}
body_bytes.extend_from_slice(data);
}
}
Err(_) => {
break;
}
}
}
let req = HttpRequest {
method,
path,
headers,
query_params,
route_params: HashMap::new(),
route_pattern: None,
body_bytes,
};
let mut resp = HttpResponse::new(200);
if payload_too_large {
resp.set_status(413);
let problem = build_problem(
413,
"Request body exceeds maximum allowed size",
);
resp.body = Some(problem_to_bytes(&problem));
resp.headers.insert(
"content-type".to_string(),
"application/problem+json".to_string(),
);
}
Self {
req,
resp,
claims: None,
}
}
pub fn into_response(self) -> hyper::Response<Full<Bytes>> {
self.resp.into_hyper()
}
}
impl IClaimsExt for HttpContext {
fn set_claims(&mut self, claims: Box<dyn IClaims>) {
self.claims = Some(claims);
}
fn claims(&self) -> Option<&dyn IClaims> {
self.claims.as_deref()
}
}
impl IHttpContext for HttpContext {
fn request(&self) -> &dyn IHttpRequest {
&self.req
}
fn request_mut(&mut self) -> &mut dyn IHttpRequest {
&mut self.req
}
fn response(&self) -> &dyn IHttpResponse {
&self.resp
}
fn response_mut(&mut self) -> &mut dyn IHttpResponse {
&mut self.resp
}
}
pub struct HttpRequest {
method: String,
path: String,
headers: HashMap<String, String>,
query_params: HashMap<String, String>,
route_params: HashMap<String, String>,
route_pattern: Option<String>,
body_bytes: Vec<u8>,
}
#[async_trait::async_trait]
impl IHttpRequest for HttpRequest {
fn method(&self) -> &str {
&self.method
}
fn path(&self) -> &str {
&self.path
}
fn header(&self, name: &str) -> Option<&str> {
self.headers.get(name).map(|s| s.as_str())
}
fn query(&self) -> &HashMap<String, String> {
&self.query_params
}
fn route_params(&self) -> &HashMap<String, String> {
&self.route_params
}
fn route_params_mut(&mut self) -> &mut HashMap<String, String> {
&mut self.route_params
}
fn route_pattern(&self) -> Option<&str> {
self.route_pattern.as_deref()
}
fn route_pattern_mut(&mut self) -> &mut Option<String> {
&mut self.route_pattern
}
async fn body_bytes(&self) -> Result<Vec<u8>> {
Ok(self.body_bytes.clone())
}
async fn body_text(&self) -> Result<String> {
String::from_utf8(self.body_bytes.clone())
.map_err(|e| rust_webx_core::error::Error::Http(e.to_string()))
}
}
pub struct HttpResponse {
status: u16,
headers: HashMap<String, String>,
body: Option<Vec<u8>>,
}
impl HttpResponse {
pub fn new(status: u16) -> Self {
let mut headers = HashMap::new();
headers.insert("content-type".to_string(), "application/json".to_string());
Self {
status,
headers,
body: None,
}
}
pub fn into_hyper(self) -> hyper::Response<Full<Bytes>> {
let mut builder = hyper::Response::builder().status(self.status);
for (key, value) in &self.headers {
builder = builder.header(key.as_str(), value.as_str());
}
let body_bytes = self.body.unwrap_or_default();
builder
.body(Full::new(Bytes::from(body_bytes)))
.unwrap_or_else(|_| {
hyper::Response::builder()
.status(hyper::StatusCode::INTERNAL_SERVER_ERROR)
.body(Full::new(Bytes::from("Internal Server Error")))
.unwrap()
})
}
}
#[async_trait::async_trait]
impl IHttpResponse for HttpResponse {
fn status(&self) -> u16 {
self.status
}
fn has_body(&self) -> bool {
self.body.is_some()
}
fn set_status(&mut self, code: u16) {
self.status = code;
}
fn set_header(&mut self, key: &str, value: &str) {
self.headers.insert(key.to_string(), value.to_string());
}
fn remove_header(&mut self, key: &str) {
self.headers.remove(key);
}
async fn write_bytes(&mut self, data: Vec<u8>) -> Result<()> {
self.body = Some(data);
Ok(())
}
async fn write_text(&mut self, text: &str) -> Result<()> {
self.body = Some(text.as_bytes().to_vec());
Ok(())
}
fn body_bytes(&self) -> Vec<u8> {
self.body.clone().unwrap_or_default()
}
fn header(&self, key: &str) -> Option<&str> {
self.headers.get(key).map(|s| s.as_str())
}
}
fn parse_query_string(query: &str) -> HashMap<String, String> {
let mut params = HashMap::new();
for pair in query.split('&') {
let mut parts = pair.splitn(2, '=');
if let (Some(key), Some(value)) = (parts.next(), parts.next()) {
params.insert(percent_decode(key), percent_decode(value));
}
}
params
}
fn percent_decode(s: &str) -> String {
let mut result = String::with_capacity(s.len());
let mut chars = s.chars();
while let Some(c) = chars.next() {
if c == '%' {
let hex: String = chars.by_ref().take(2).collect();
if let Ok(byte) = u8::from_str_radix(&hex, 16) {
result.push(byte as char);
}
} else if c == '+' {
result.push(' ');
} else {
result.push(c);
}
}
result
}