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