Skip to main content

iris_core/protocols/stream/http/
transaction.rs

1//! HTTP transaction components.
2//!
3//! ## Remarks
4//! Iris currently only parses HTTP headers, and does not yet parse request/response bodies. This
5//! is an upcoming feature.
6
7use anyhow::{bail, Result};
8use httparse::{Request, Response, EMPTY_HEADER};
9use serde::Serialize;
10
11/// An HTTP Request
12#[derive(Debug, Default, Serialize, Clone)]
13pub struct HttpRequest {
14    pub method: Option<String>,
15    pub uri: Option<String>,
16    pub version: Option<String>,
17    pub user_agent: Option<String>,
18    pub cookie: Option<String>,
19    pub host: Option<String>,
20    pub content_length: Option<usize>,
21    pub content_type: Option<String>,
22    pub transfer_encoding: Option<String>,
23    // /// `false` if request body needs continuation pub is_complete: bool, /// Actual length in
24    // bytes of body data transferred from the client. pub body_len: usize,
25}
26
27impl HttpRequest {
28    pub(crate) fn parse_from(data: &[u8]) -> Result<Self> {
29        let mut request = HttpRequest::default();
30
31        const NUM_OF_HEADERS: usize = 20;
32        let mut headers = [EMPTY_HEADER; NUM_OF_HEADERS];
33        let mut req = Request::new(&mut headers[..]);
34        let status = req.parse(data);
35        if status.is_err() {
36            bail!("error");
37        }
38
39        if let Some(method) = req.method {
40            request.method = Some(method.to_owned());
41        }
42        if let Some(uri) = req.path {
43            request.uri = Some(uri.to_owned());
44        }
45        if let Some(version) = req.version {
46            request.version = Some(format!("HTTP/1.{}", version));
47        }
48        for hdr in &headers {
49            let name = hdr.name.to_lowercase();
50            match name.as_ref() {
51                "user-agent" => {
52                    let s = String::from_utf8_lossy(hdr.value).into_owned();
53                    request.user_agent = Some(s);
54                }
55                "cookie" => {
56                    let s = String::from_utf8_lossy(hdr.value).into_owned();
57                    request.cookie = Some(s);
58                }
59                "host" => {
60                    let s = String::from_utf8_lossy(hdr.value);
61                    request.host = Some(s.to_string());
62                }
63                "content-length" => {
64                    if let Ok(s) = std::str::from_utf8(hdr.value) {
65                        if let Ok(length) = str::parse::<usize>(s) {
66                            request.content_length = Some(length);
67                        }
68                    }
69                }
70                "content-type" => {
71                    let s = String::from_utf8_lossy(hdr.value).into_owned();
72                    request.content_type = Some(s);
73                }
74                "transfer-encoding" => {
75                    let s = String::from_utf8_lossy(hdr.value).to_lowercase();
76                    request.transfer_encoding = Some(s);
77                    // if &s == "chunked" {if let Ok(httparse::Status::Complete(sz)) = status {start
78                    //     = sz;} else {log::warn!("Parsing response failed"); return
79                    //     ParseResult::Error;
80                    //     }
81                    //     return request.get_chunk_loop(start, data, false);
82                    // }
83                }
84                _ => (),
85            }
86        }
87        Ok(request)
88    }
89}
90
91/// An HTTP Response
92#[derive(Debug, Default, Serialize, Clone)]
93pub struct HttpResponse {
94    pub version: Option<String>,
95    pub status_code: Option<u16>,
96    pub status_msg: Option<String>,
97    pub content_length: Option<usize>,
98    pub content_type: Option<String>,
99    pub transfer_encoding: Option<String>,
100    // /// `false` if response body needs continuation pub is_complete: bool, /// Actual length in
101    // bytes of body data transferred from the server. pub body_len: usize, pub chunk_length:
102    // Option<usize>, pub in_next_frame: bool,
103}
104
105impl HttpResponse {
106    pub(crate) fn parse_from(data: &[u8]) -> Result<(Self, usize)> {
107        let mut response = HttpResponse::default();
108
109        const NUM_OF_HEADERS: usize = 20;
110        let mut headers = [EMPTY_HEADER; NUM_OF_HEADERS];
111        let mut resp = Response::new(&mut headers[..]);
112        let status = resp.parse(data);
113        if status.is_err() {
114            bail!("error");
115        }
116
117        if let Some(version) = resp.version {
118            response.version = Some(format!("HTTP/1.{}", version));
119        }
120        if let Some(code) = resp.code {
121            response.status_code = Some(code);
122        }
123        if let Some(reason) = resp.reason {
124            response.status_msg = Some(reason.to_owned());
125        }
126
127        for hdr in &headers {
128            let name = hdr.name.to_lowercase();
129            match name.as_ref() {
130                "content-length" => {
131                    if let Ok(s) = std::str::from_utf8(hdr.value) {
132                        if let Ok(length) = str::parse::<usize>(s) {
133                            // if let Ok(httparse::Status::Complete(sz)) = status {response.body =
134                            //     data[sz..].to_vec();
135                            // }
136                            response.content_length = Some(length);
137                            // if length > response.body.len() {need_more = true;
138                            // }
139                            // continue;
140                        }
141                    }
142                }
143                "content-type" => {
144                    let s = String::from_utf8_lossy(hdr.value).into_owned();
145                    response.content_type = Some(s);
146                }
147                "transfer-encoding" => {
148                    let s = String::from_utf8_lossy(hdr.value).to_lowercase();
149                    response.transfer_encoding = Some(s);
150                    // if &s == "chunked" {if let Ok(httparse::Status::Complete(sz)) = status {start
151                    //     = sz;} else {log::warn!("Parsing response failed"); return
152                    //     ParseResult::Error;
153                    //     }
154                    //     return response.get_chunk_loop(start, data, false);
155                    // }
156                }
157                _ => (),
158            }
159        }
160        let consumed = match status.unwrap() {
161            httparse::Status::Complete(nbytes) => nbytes,
162            httparse::Status::Partial => data.len(),
163        };
164        Ok((response, consumed))
165    }
166}