rust_webx_host/
context.rs1use http_body_util::Full;
4use hyper::body::{Bytes, Incoming};
5use hyper::Request;
6use rust_webx_core::auth::IClaims;
7use rust_webx_core::error::Result;
8use rust_webx_core::http::{IClaimsExt, IHttpContext, IHttpRequest, IHttpResponse};
9
10use crate::problem_response::{build_problem, problem_to_bytes};
11use std::cell::RefCell;
12use std::collections::HashMap;
13
14pub struct HttpContext {
19 req: HttpRequest,
20 resp: HttpResponse,
21 claims: RefCell<Option<Box<dyn IClaims>>>,
23}
24
25impl HttpContext {
26 pub async fn new(req: Request<Incoming>, max_body_size: usize) -> Self {
32 use http_body_util::BodyExt;
33
34 let (parts, mut body) = req.into_parts();
35 let method = parts.method.to_string();
36 let path = parts.uri.path().to_string();
37
38 let mut headers = HashMap::new();
40 for (name, value) in parts.headers.iter() {
41 if let Ok(v) = value.to_str() {
42 headers.insert(name.to_string(), v.to_string());
43 }
44 }
45
46 let query_params = parts
48 .uri
49 .query()
50 .map(parse_query_string)
51 .unwrap_or_default();
52
53 let mut body_bytes = Vec::new();
55 let mut total_size: usize = 0;
56 let mut payload_too_large = false;
57
58 while let Some(frame_result) = body.frame().await {
59 match frame_result {
60 Ok(frame) => {
61 if let Some(data) = frame.data_ref() {
62 total_size = total_size.saturating_add(data.len());
63 if total_size > max_body_size {
64 payload_too_large = true;
65 break;
66 }
67 body_bytes.extend_from_slice(data);
68 }
69 }
70 Err(_) => {
71 break;
72 }
73 }
74 }
75
76 let req = HttpRequest {
77 method,
78 path,
79 headers,
80 query_params,
81 route_params: HashMap::new(),
82 route_pattern: None,
83 body_bytes,
84 };
85
86 let mut resp = HttpResponse::new(200);
87
88 if payload_too_large {
89 resp.set_status(413);
90 let problem = build_problem(
91 413,
92 "Request body exceeds maximum allowed size",
93 );
94 resp.body = Some(problem_to_bytes(&problem));
95 resp.headers.insert(
96 "content-type".to_string(),
97 "application/problem+json".to_string(),
98 );
99 }
100
101 Self {
102 req,
103 resp,
104 claims: RefCell::new(None),
105 }
106 }
107
108 pub fn into_response(self) -> hyper::Response<Full<Bytes>> {
109 self.resp.into_hyper()
110 }
111}
112
113impl IClaimsExt for HttpContext {
114 fn set_claims(&mut self, claims: Box<dyn IClaims>) {
115 *self.claims.borrow_mut() = Some(claims);
116 }
117
118 fn claims(&self) -> Option<&dyn IClaims> {
119 let borrowed = self.claims.borrow();
120 borrowed
125 .as_ref()
126 .map(|b| unsafe { &*(&**b as *const dyn IClaims) })
127 }
128}
129
130impl IHttpContext for HttpContext {
131 fn request(&self) -> &dyn IHttpRequest {
132 &self.req
133 }
134
135 fn request_mut(&mut self) -> &mut dyn IHttpRequest {
136 &mut self.req
137 }
138
139 fn response(&self) -> &dyn IHttpResponse {
140 &self.resp
141 }
142
143 fn response_mut(&mut self) -> &mut dyn IHttpResponse {
144 &mut self.resp
145 }
146}
147
148pub struct HttpRequest {
153 method: String,
154 path: String,
155 headers: HashMap<String, String>,
156 query_params: HashMap<String, String>,
157 route_params: HashMap<String, String>,
158 route_pattern: Option<String>,
159 body_bytes: Vec<u8>,
160}
161
162#[async_trait::async_trait]
163impl IHttpRequest for HttpRequest {
164 fn method(&self) -> &str {
165 &self.method
166 }
167
168 fn path(&self) -> &str {
169 &self.path
170 }
171
172 fn header(&self, name: &str) -> Option<&str> {
173 self.headers.get(name).map(|s| s.as_str())
174 }
175
176 fn query(&self) -> &HashMap<String, String> {
177 &self.query_params
178 }
179
180 fn route_params(&self) -> &HashMap<String, String> {
181 &self.route_params
182 }
183
184 fn route_params_mut(&mut self) -> &mut HashMap<String, String> {
185 &mut self.route_params
186 }
187
188 fn route_pattern(&self) -> Option<&str> {
189 self.route_pattern.as_deref()
190 }
191
192 fn route_pattern_mut(&mut self) -> &mut Option<String> {
193 &mut self.route_pattern
194 }
195
196 async fn body_bytes(&self) -> Result<Vec<u8>> {
197 Ok(self.body_bytes.clone())
198 }
199
200 async fn body_text(&self) -> Result<String> {
201 String::from_utf8(self.body_bytes.clone())
202 .map_err(|e| rust_webx_core::error::Error::Http(e.to_string()))
203 }
204}
205
206pub struct HttpResponse {
208 status: u16,
209 headers: HashMap<String, String>,
210 body: Option<Vec<u8>>,
211}
212
213impl HttpResponse {
214 pub fn new(status: u16) -> Self {
215 let mut headers = HashMap::new();
216 headers.insert("content-type".to_string(), "application/json".to_string());
217 Self {
218 status,
219 headers,
220 body: None,
221 }
222 }
223
224 pub fn into_hyper(self) -> hyper::Response<Full<Bytes>> {
225 let mut builder = hyper::Response::builder().status(self.status);
226 for (key, value) in &self.headers {
227 builder = builder.header(key.as_str(), value.as_str());
228 }
229 let body_bytes = self.body.unwrap_or_default();
230 builder
231 .body(Full::new(Bytes::from(body_bytes)))
232 .unwrap_or_else(|_| {
233 hyper::Response::builder()
234 .status(hyper::StatusCode::INTERNAL_SERVER_ERROR)
235 .body(Full::new(Bytes::from("Internal Server Error")))
236 .unwrap()
237 })
238 }
239}
240
241#[async_trait::async_trait]
242impl IHttpResponse for HttpResponse {
243 fn status(&self) -> u16 {
244 self.status
245 }
246
247 fn has_body(&self) -> bool {
248 self.body.is_some()
249 }
250
251 fn set_status(&mut self, code: u16) {
252 self.status = code;
253 }
254
255 fn set_header(&mut self, key: &str, value: &str) {
256 self.headers.insert(key.to_string(), value.to_string());
257 }
258
259 async fn write_bytes(&mut self, data: Vec<u8>) -> Result<()> {
260 self.body = Some(data);
261 Ok(())
262 }
263
264 async fn write_text(&mut self, text: &str) -> Result<()> {
265 self.body = Some(text.as_bytes().to_vec());
266 Ok(())
267 }
268
269 fn body_bytes(&self) -> Vec<u8> {
270 self.body.clone().unwrap_or_default()
271 }
272
273 fn header(&self, key: &str) -> Option<&str> {
274 self.headers.get(key).map(|s| s.as_str())
275 }
276}
277
278fn parse_query_string(query: &str) -> HashMap<String, String> {
280 let mut params = HashMap::new();
281 for pair in query.split('&') {
282 let mut parts = pair.splitn(2, '=');
283 if let (Some(key), Some(value)) = (parts.next(), parts.next()) {
284 params.insert(percent_decode(key), percent_decode(value));
285 }
286 }
287 params
288}
289
290fn percent_decode(s: &str) -> String {
291 let mut result = String::with_capacity(s.len());
292 let mut chars = s.chars();
293 while let Some(c) = chars.next() {
294 if c == '%' {
295 let hex: String = chars.by_ref().take(2).collect();
296 if let Ok(byte) = u8::from_str_radix(&hex, 16) {
297 result.push(byte as char);
298 }
299 } else if c == '+' {
300 result.push(' ');
301 } else {
302 result.push(c);
303 }
304 }
305 result
306}