@@ -5,8 +5,9 @@ use std::{
5
5
io:: { BufRead , BufReader , Read } ,
6
6
net:: TcpStream ,
7
7
str:: FromStr ,
8
+ usize,
8
9
} ;
9
- use tucana:: shared:: Value ;
10
+ use tucana:: shared:: { value :: Kind , Struct , Value } ;
10
11
11
12
#[ derive( Debug ) ]
12
13
pub enum HttpOption {
@@ -42,30 +43,104 @@ impl ToString for HttpOption {
42
43
}
43
44
44
45
#[ derive( Debug ) ]
45
- pub struct HttpRequest {
46
- pub method : HttpOption ,
47
- pub path : String ,
48
- pub version : String ,
49
- pub headers : Vec < String > ,
50
- pub body : Option < Value > ,
46
+ pub struct HeaderMap {
47
+ pub fields : HashMap < String , String > ,
51
48
}
52
49
53
- # [ derive ( Debug ) ]
54
- pub enum PrimitiveValue {
55
- String ( String ) ,
56
- Number ( f64 ) ,
57
- Boolean ( bool ) ,
58
- }
50
+ impl HeaderMap {
51
+ pub fn new ( ) -> Self {
52
+ HeaderMap {
53
+ fields : HashMap :: new ( ) ,
54
+ }
55
+ }
59
56
60
- #[ derive( Debug ) ]
61
- pub struct PrimitiveStruct {
62
- pub fields : HashMap < String , PrimitiveValue > ,
57
+ /// Create a new HeaderMap from a vector of strings.
58
+ ///
59
+ /// Each string should be in the format "key: value".
60
+ ///
61
+ /// # Examples
62
+ ///
63
+ /// ```
64
+ /// let header = vec![
65
+ /// "Content-Type: application/json".to_string(),
66
+ /// "User-Agent: Mozilla/5.0".to_string(),
67
+ /// ];
68
+ /// let header_map = HeaderMap::from_vec(header);
69
+ /// assert_eq!(header_map.get("content-type"), Some(&"application/json".to_string()));
70
+ /// assert_eq!(header_map.get("user-agent"), Some(&"Mozilla/5.0".to_string()));
71
+ /// ```
72
+ pub fn from_vec ( header : Vec < String > ) -> Self {
73
+ let mut header_map = HeaderMap :: new ( ) ;
74
+
75
+ for param in header {
76
+ let mut parts = param. split ( ": " ) ;
77
+ let key = match parts. next ( ) {
78
+ Some ( key) => key. to_lowercase ( ) ,
79
+ None => continue ,
80
+ } ;
81
+ let value = match parts. next ( ) {
82
+ Some ( value) => value. to_lowercase ( ) ,
83
+ None => continue ,
84
+ } ;
85
+
86
+ header_map. add ( key, value) ;
87
+ }
88
+
89
+ header_map
90
+ }
91
+
92
+ #[ inline]
93
+ pub fn add ( & mut self , key : String , value : String ) {
94
+ self . fields . insert ( key, value) ;
95
+ }
96
+
97
+ #[ inline]
98
+ pub fn get ( & self , key : & str ) -> Option < & String > {
99
+ self . fields . get ( key)
100
+ }
63
101
}
64
102
65
103
#[ derive( Debug ) ]
66
- pub struct HttpParameter {
67
- pub url_query : Option < PrimitiveStruct > ,
68
- pub url_parameters : Option < PrimitiveStruct > ,
104
+ pub struct HttpRequest {
105
+ pub method : HttpOption ,
106
+ pub path : String ,
107
+ pub version : String ,
108
+ pub headers : HeaderMap ,
109
+
110
+ /// The body of the request.
111
+ ///
112
+ /// # Example
113
+ /// If the url was called:
114
+ ///
115
+ /// url: .../api/users/123/posts/456?filter=recent&sort=asc
116
+ ///
117
+ /// from the regex: "^/api/users/(?P<user_id>\d+)/posts/(?P<post_id>\d+)(\?.*)?$"
118
+ ///
119
+ /// With the request body:
120
+ ///
121
+ /// ```json
122
+ /// {
123
+ /// "first": 2,
124
+ /// "second": 300
125
+ /// }
126
+ /// ```
127
+ /// The equivalent HTTP request body will look like:
128
+ /// ```json
129
+ /// {
130
+ /// "url": {
131
+ /// "user_id": "123",
132
+ /// "post_id": "456",
133
+ /// },
134
+ /// "query": {
135
+ /// "filter": "recent",
136
+ /// "sort": "asc"
137
+ /// },
138
+ /// "body": {
139
+ /// "first": "1",
140
+ /// "second": "2"
141
+ /// }
142
+ /// }
143
+ /// ```
69
144
pub body : Option < Value > ,
70
145
}
71
146
@@ -85,29 +160,7 @@ pub fn convert_to_http_request(stream: &TcpStream) -> Result<HttpRequest, HttpRe
85
160
}
86
161
87
162
// Parse headers
88
- let mut http_request = parse_request ( raw_http_request) ?;
89
-
90
- // Read body if Content-Length is specified
91
- for header in & http_request. headers {
92
- if header. to_lowercase ( ) . starts_with ( "content-length:" ) {
93
- let content_length: usize = header
94
- . split ( ':' )
95
- . nth ( 1 )
96
- . and_then ( |s| s. trim ( ) . parse ( ) . ok ( ) )
97
- . unwrap_or ( 0 ) ;
98
-
99
- if content_length > 0 {
100
- let mut body = vec ! [ 0 ; content_length] ;
101
- if let Ok ( _) = buf_reader. read_exact ( & mut body) {
102
- // Parse JSON body
103
- if let Ok ( json_value) = serde_json:: from_slice :: < serde_json:: Value > ( & body) {
104
- http_request. body = Some ( to_tucana_value ( json_value) ) ;
105
- }
106
- }
107
- }
108
- break ;
109
- }
110
- }
163
+ let http_request = parse_request ( raw_http_request, buf_reader) ?;
111
164
112
165
log:: debug!( "Received HTTP Request: {:?}" , & http_request) ;
113
166
@@ -121,8 +174,14 @@ pub fn convert_to_http_request(stream: &TcpStream) -> Result<HttpRequest, HttpRe
121
174
Ok ( http_request)
122
175
}
123
176
124
- fn parse_request ( raw_http_request : Vec < String > ) -> Result < HttpRequest , HttpResponse > {
177
+ #[ inline]
178
+ fn parse_request (
179
+ raw_http_request : Vec < String > ,
180
+ mut buf_reader : BufReader < & TcpStream > ,
181
+ ) -> Result < HttpRequest , HttpResponse > {
125
182
let params = & raw_http_request[ 0 ] ;
183
+ let headers = raw_http_request[ 1 ..raw_http_request. len ( ) ] . to_vec ( ) ;
184
+ let header_map = HeaderMap :: from_vec ( headers) ;
126
185
127
186
if params. is_empty ( ) {
128
187
return Err ( HttpResponse :: bad_request (
@@ -152,11 +211,77 @@ fn parse_request(raw_http_request: Vec<String>) -> Result<HttpRequest, HttpRespo
152
211
}
153
212
} ;
154
213
214
+ let mut body_values: HashMap < String , Value > = HashMap :: new ( ) ;
215
+
216
+ if let Some ( content_length) = header_map. get ( "content-length" ) {
217
+ let size: usize = match content_length. parse ( ) {
218
+ Ok ( len) => len,
219
+ Err ( _) => {
220
+ return Err ( HttpResponse :: bad_request (
221
+ "Invalid content-length header" . to_string ( ) ,
222
+ HashMap :: new ( ) ,
223
+ ) )
224
+ }
225
+ } ;
226
+
227
+ let mut body = vec ! [ 0 ; size] ;
228
+ if let Ok ( _) = buf_reader. read_exact ( & mut body) {
229
+ if let Ok ( json_value) = serde_json:: from_slice :: < serde_json:: Value > ( & body) {
230
+ body_values. insert ( "body" . to_string ( ) , to_tucana_value ( json_value) ) ;
231
+ }
232
+ }
233
+ } ;
234
+
235
+ if path. contains ( "?" ) {
236
+ let mut fields: HashMap < String , Value > = HashMap :: new ( ) ;
237
+ if let Some ( ( _, query) ) = path. split_once ( "?" ) {
238
+ let values = query. split ( "&" ) ;
239
+
240
+ for value in values {
241
+ let mut parts = value. split ( "=" ) ;
242
+ let key = match parts. next ( ) {
243
+ Some ( key) => key. to_string ( ) ,
244
+ None => continue ,
245
+ } ;
246
+
247
+ let value = match parts. next ( ) {
248
+ Some ( value) => value. to_string ( ) ,
249
+ None => continue ,
250
+ } ;
251
+
252
+ fields. insert (
253
+ key,
254
+ Value {
255
+ kind : Some ( Kind :: StringValue ( value) ) ,
256
+ } ,
257
+ ) ;
258
+ }
259
+ } ;
260
+
261
+ if !fields. is_empty ( ) {
262
+ let value = Value {
263
+ kind : Some ( Kind :: StructValue ( Struct { fields } ) ) ,
264
+ } ;
265
+
266
+ body_values. insert ( "query" . to_string ( ) , value) ;
267
+ }
268
+ } ;
269
+
270
+ let body = if !body_values. is_empty ( ) {
271
+ Some ( Value {
272
+ kind : Some ( Kind :: StructValue ( Struct {
273
+ fields : body_values,
274
+ } ) ) ,
275
+ } )
276
+ } else {
277
+ None
278
+ } ;
279
+
155
280
Ok ( HttpRequest {
156
281
method,
157
282
path : path. to_string ( ) ,
158
283
version : version. to_string ( ) ,
159
- headers : raw_http_request . clone ( ) ,
160
- body : None ,
284
+ headers : header_map ,
285
+ body,
161
286
} )
162
287
}
0 commit comments