/rust/git/checkouts/micro-http-b6958a74e1f08106/3248cee/src/response.rs
Line | Count | Source |
1 | | // Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. |
2 | | // SPDX-License-Identifier: Apache-2.0 |
3 | | |
4 | | use std::collections::HashMap; |
5 | | use std::io::{Error as WriteError, Write}; |
6 | | |
7 | | use crate::ascii::{COLON, CR, LF, SP}; |
8 | | use crate::common::{Body, Version}; |
9 | | use crate::headers::{Header, MediaType}; |
10 | | use crate::HttpHeaderError; |
11 | | use crate::Method; |
12 | | |
13 | | /// Wrapper over a response status code. |
14 | | /// |
15 | | /// The status code is defined as specified in the |
16 | | /// [RFC](https://tools.ietf.org/html/rfc7231#section-6). |
17 | | #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
18 | | pub enum StatusCode { |
19 | | /// 100, Continue |
20 | | Continue, |
21 | | /// 200, OK |
22 | | OK, |
23 | | /// 204, No Content |
24 | | NoContent, |
25 | | /// 400, Bad Request |
26 | | BadRequest, |
27 | | /// 401, Unauthorized |
28 | | Unauthorized, |
29 | | /// 404, Not Found |
30 | | NotFound, |
31 | | /// 405, Method Not Allowed |
32 | | MethodNotAllowed, |
33 | | /// 413, Payload Too Large |
34 | | PayloadTooLarge, |
35 | | /// 429, Too Many Requests |
36 | | TooManyRequests, |
37 | | /// 500, Internal Server Error |
38 | | InternalServerError, |
39 | | /// 501, Not Implemented |
40 | | NotImplemented, |
41 | | /// 503, Service Unavailable |
42 | | ServiceUnavailable, |
43 | | } |
44 | | |
45 | | impl StatusCode { |
46 | | /// Returns the status code as bytes. |
47 | 0 | pub fn raw(self) -> &'static [u8; 3] { |
48 | 0 | match self { |
49 | 0 | Self::Continue => b"100", |
50 | 0 | Self::OK => b"200", |
51 | 0 | Self::NoContent => b"204", |
52 | 0 | Self::BadRequest => b"400", |
53 | 0 | Self::Unauthorized => b"401", |
54 | 0 | Self::NotFound => b"404", |
55 | 0 | Self::MethodNotAllowed => b"405", |
56 | 0 | Self::PayloadTooLarge => b"413", |
57 | 0 | Self::TooManyRequests => b"429", |
58 | 0 | Self::InternalServerError => b"500", |
59 | 0 | Self::NotImplemented => b"501", |
60 | 0 | Self::ServiceUnavailable => b"503", |
61 | | } |
62 | 0 | } |
63 | | } |
64 | | |
65 | | #[derive(Debug, PartialEq)] |
66 | | struct StatusLine { |
67 | | http_version: Version, |
68 | | status_code: StatusCode, |
69 | | } |
70 | | |
71 | | impl StatusLine { |
72 | 18.9k | fn new(http_version: Version, status_code: StatusCode) -> Self { |
73 | 18.9k | Self { |
74 | 18.9k | http_version, |
75 | 18.9k | status_code, |
76 | 18.9k | } |
77 | 18.9k | } |
78 | | |
79 | 0 | fn write_all<T: Write>(&self, mut buf: T) -> Result<(), WriteError> { |
80 | 0 | buf.write_all(self.http_version.raw())?; |
81 | 0 | buf.write_all(&[SP])?; |
82 | 0 | buf.write_all(self.status_code.raw())?; |
83 | 0 | buf.write_all(&[SP, CR, LF])?; |
84 | | |
85 | 0 | Ok(()) |
86 | 0 | } |
87 | | } |
88 | | |
89 | | /// Wrapper over the list of headers associated with a HTTP Response. |
90 | | /// When creating a ResponseHeaders object, the content type is initialized to `text/plain`. |
91 | | /// The content type can be updated with a call to `set_content_type`. |
92 | | #[derive(Debug, PartialEq, Eq)] |
93 | | pub struct ResponseHeaders { |
94 | | content_length: Option<i32>, |
95 | | content_type: MediaType, |
96 | | deprecation: bool, |
97 | | server: String, |
98 | | allow: Vec<Method>, |
99 | | accept_encoding: bool, |
100 | | custom_headers: HashMap<String, String>, |
101 | | } |
102 | | |
103 | | impl Default for ResponseHeaders { |
104 | 18.9k | fn default() -> Self { |
105 | 18.9k | Self { |
106 | 18.9k | content_length: Default::default(), |
107 | 18.9k | content_type: Default::default(), |
108 | 18.9k | deprecation: false, |
109 | 18.9k | server: String::from("Firecracker API"), |
110 | 18.9k | allow: Vec::new(), |
111 | 18.9k | accept_encoding: false, |
112 | 18.9k | custom_headers: HashMap::default(), |
113 | 18.9k | } |
114 | 18.9k | } |
115 | | } |
116 | | |
117 | | impl ResponseHeaders { |
118 | | // The logic pertaining to `Allow` header writing. |
119 | 0 | fn write_allow_header<T: Write>(&self, buf: &mut T) -> Result<(), WriteError> { |
120 | 0 | if self.allow.is_empty() { |
121 | 0 | return Ok(()); |
122 | 0 | } |
123 | | |
124 | 0 | buf.write_all(b"Allow: ")?; |
125 | | |
126 | 0 | let delimitator = b", "; |
127 | 0 | for (idx, method) in self.allow.iter().enumerate() { |
128 | 0 | buf.write_all(method.raw())?; |
129 | | // We check above that `self.allow` is not empty. |
130 | 0 | if idx < self.allow.len() - 1 { |
131 | 0 | buf.write_all(delimitator)?; |
132 | 0 | } |
133 | | } |
134 | | |
135 | 0 | buf.write_all(&[CR, LF]) |
136 | 0 | } |
137 | | |
138 | | // The logic pertaining to `Deprecation` header writing. |
139 | 0 | fn write_deprecation_header<T: Write>(&self, buf: &mut T) -> Result<(), WriteError> { |
140 | 0 | if !self.deprecation { |
141 | 0 | return Ok(()); |
142 | 0 | } |
143 | | |
144 | 0 | buf.write_all(b"Deprecation: true")?; |
145 | 0 | buf.write_all(&[CR, LF]) |
146 | 0 | } |
147 | | |
148 | | // The logic pertaining to custom headers writing. |
149 | 0 | fn write_custom_headers<T: Write>(&self, buf: &mut T) -> Result<(), WriteError> { |
150 | | // Note that all the custom headers have already been validated as US-ASCII. |
151 | 0 | for (header, value) in &self.custom_headers { |
152 | 0 | buf.write_all(header.as_bytes())?; |
153 | 0 | buf.write_all(&[COLON, SP])?; |
154 | 0 | buf.write_all(value.as_bytes())?; |
155 | 0 | buf.write_all(&[CR, LF])?; |
156 | | } |
157 | 0 | Ok(()) |
158 | 0 | } |
159 | | |
160 | | /// Writes the headers to `buf` using the HTTP specification. |
161 | 0 | pub fn write_all<T: Write>(&self, buf: &mut T) -> Result<(), WriteError> { |
162 | 0 | buf.write_all(Header::Server.raw())?; |
163 | 0 | buf.write_all(&[COLON, SP])?; |
164 | 0 | buf.write_all(self.server.as_bytes())?; |
165 | 0 | buf.write_all(&[CR, LF])?; |
166 | | |
167 | 0 | buf.write_all(b"Connection: keep-alive")?; |
168 | 0 | buf.write_all(&[CR, LF])?; |
169 | | |
170 | 0 | self.write_allow_header(buf)?; |
171 | 0 | self.write_deprecation_header(buf)?; |
172 | 0 | self.write_custom_headers(buf)?; |
173 | | |
174 | 0 | if let Some(content_length) = self.content_length { |
175 | 0 | buf.write_all(Header::ContentType.raw())?; |
176 | 0 | buf.write_all(&[COLON, SP])?; |
177 | 0 | buf.write_all(self.content_type.as_str().as_bytes())?; |
178 | 0 | buf.write_all(&[CR, LF])?; |
179 | | |
180 | 0 | buf.write_all(Header::ContentLength.raw())?; |
181 | 0 | buf.write_all(&[COLON, SP])?; |
182 | 0 | buf.write_all(content_length.to_string().as_bytes())?; |
183 | 0 | buf.write_all(&[CR, LF])?; |
184 | | |
185 | 0 | if self.accept_encoding { |
186 | 0 | buf.write_all(Header::AcceptEncoding.raw())?; |
187 | 0 | buf.write_all(&[COLON, SP])?; |
188 | 0 | buf.write_all(b"identity")?; |
189 | 0 | buf.write_all(&[CR, LF])?; |
190 | 0 | } |
191 | 0 | } |
192 | | |
193 | 0 | buf.write_all(&[CR, LF]) |
194 | 0 | } |
195 | | |
196 | | /// Sets the content length to be written in the HTTP response. |
197 | 18.0k | pub fn set_content_length(&mut self, content_length: Option<i32>) { |
198 | 18.0k | self.content_length = content_length; |
199 | 18.0k | } |
200 | | |
201 | | /// Sets the HTTP response header server. |
202 | 0 | pub fn set_server(&mut self, server: &str) { |
203 | 0 | self.server = String::from(server); |
204 | 0 | } |
205 | | |
206 | | /// Sets the content type to be written in the HTTP response. |
207 | 0 | pub fn set_content_type(&mut self, content_type: MediaType) { |
208 | 0 | self.content_type = content_type; |
209 | 0 | } |
210 | | |
211 | | /// Sets the `Deprecation` header to be written in the HTTP response. |
212 | | /// https://tools.ietf.org/id/draft-dalal-deprecation-header-03.html |
213 | | #[allow(unused)] |
214 | 0 | pub fn set_deprecation(&mut self) { |
215 | 0 | self.deprecation = true; |
216 | 0 | } |
217 | | |
218 | | /// Sets the encoding type to be written in the HTTP response. |
219 | | #[allow(unused)] |
220 | 0 | pub fn set_encoding(&mut self) { |
221 | 0 | self.accept_encoding = true; |
222 | 0 | } |
223 | | |
224 | | /// Sets custom headers to be written in the HTTP response. |
225 | 0 | pub fn set_custom_headers( |
226 | 0 | &mut self, |
227 | 0 | custom_headers: &HashMap<String, String>, |
228 | 0 | ) -> Result<(), HttpHeaderError> { |
229 | | // https://datatracker.ietf.org/doc/html/rfc7230 |
230 | | // HTTP headers MUST be US-ASCII. |
231 | 0 | if let Some((k, v)) = custom_headers |
232 | 0 | .iter() |
233 | 0 | .find(|(k, v)| !k.is_ascii() || !v.is_ascii()) |
234 | | { |
235 | 0 | return Err(HttpHeaderError::NonAsciiCharacter( |
236 | 0 | k.to_owned(), |
237 | 0 | v.to_owned(), |
238 | 0 | )); |
239 | 0 | } |
240 | 0 | self.custom_headers = custom_headers.to_owned(); |
241 | 0 | Ok(()) |
242 | 0 | } |
243 | | } |
244 | | |
245 | | /// Wrapper over an HTTP Response. |
246 | | /// |
247 | | /// The Response is created using a `Version` and a `StatusCode`. When creating a Response object, |
248 | | /// the body is initialized to `None` and the header is initialized with the `default` value. The body |
249 | | /// can be updated with a call to `set_body`. The header can be updated with `set_content_type` and |
250 | | /// `set_server`. |
251 | | #[derive(Debug, PartialEq)] |
252 | | pub struct Response { |
253 | | status_line: StatusLine, |
254 | | headers: ResponseHeaders, |
255 | | body: Option<Body>, |
256 | | } |
257 | | |
258 | | impl Response { |
259 | | /// Creates a new HTTP `Response` with an empty body. |
260 | | /// |
261 | | /// Although there are several cases where Content-Length field must not |
262 | | /// be sent, micro-http omits Content-Length field when the response |
263 | | /// status code is 1XX or 204. If needed, users can remove it by calling |
264 | | /// `set_content_length(None)`. |
265 | | /// |
266 | | /// https://datatracker.ietf.org/doc/html/rfc9110#name-content-length |
267 | | /// > A server MAY send a Content-Length header field in a response to a |
268 | | /// > HEAD request (Section 9.3.2); a server MUST NOT send Content-Length |
269 | | /// > in such a response unless its field value equals the decimal number |
270 | | /// > of octets that would have been sent in the content of a response if |
271 | | /// > the same request had used the GET method. |
272 | | /// > |
273 | | /// > A server MAY send a Content-Length header field in a 304 (Not |
274 | | /// > Modified) response to a conditional GET request (Section 15.4.5); a |
275 | | /// > server MUST NOT send Content-Length in such a response unless its |
276 | | /// > field value equals the decimal number of octets that would have been |
277 | | /// > sent in the content of a 200 (OK) response to the same request. |
278 | | /// > |
279 | | /// > A server MUST NOT send a Content-Length header field in any response |
280 | | /// > with a status code of 1xx (Informational) or 204 (No Content). A |
281 | | /// > server MUST NOT send a Content-Length header field in any 2xx |
282 | | /// > (Successful) response to a CONNECT request (Section 9.3.6). |
283 | 18.9k | pub fn new(http_version: Version, status_code: StatusCode) -> Self { |
284 | | Self { |
285 | 18.9k | status_line: StatusLine::new(http_version, status_code), |
286 | | headers: ResponseHeaders { |
287 | 18.9k | content_length: match status_code { |
288 | 943 | StatusCode::Continue | StatusCode::NoContent => None, |
289 | 18.0k | _ => Some(0), |
290 | | }, |
291 | 18.9k | ..Default::default() |
292 | | }, |
293 | 18.9k | body: Default::default(), |
294 | | } |
295 | 18.9k | } |
296 | | |
297 | | /// Updates the body of the `Response`. |
298 | | /// |
299 | | /// This function has side effects because it also updates the headers: |
300 | | /// - `ContentLength`: this is set to the length of the specified body. |
301 | 18.0k | pub fn set_body(&mut self, body: Body) { |
302 | 18.0k | self.headers.set_content_length(Some(body.len() as i32)); |
303 | 18.0k | self.body = Some(body); |
304 | 18.0k | } |
305 | | |
306 | | /// Updates the content length of the `Response`. |
307 | | /// |
308 | | /// It is recommended to use this method only when removing Content-Length |
309 | | /// field if the response status is not 1XX or 204. |
310 | 0 | pub fn set_content_length(&mut self, content_length: Option<i32>) { |
311 | 0 | self.headers.set_content_length(content_length); |
312 | 0 | } |
313 | | |
314 | | /// Updates the content type of the `Response`. |
315 | 0 | pub fn set_content_type(&mut self, content_type: MediaType) { |
316 | 0 | self.headers.set_content_type(content_type); |
317 | 0 | } |
318 | | |
319 | | /// Marks the `Response` as deprecated. |
320 | 0 | pub fn set_deprecation(&mut self) { |
321 | 0 | self.headers.set_deprecation(); |
322 | 0 | } |
323 | | |
324 | | /// Updates the encoding type of `Response`. |
325 | 0 | pub fn set_encoding(&mut self) { |
326 | 0 | self.headers.set_encoding(); |
327 | 0 | } |
328 | | |
329 | | /// Sets the HTTP response server. |
330 | 0 | pub fn set_server(&mut self, server: &str) { |
331 | 0 | self.headers.set_server(server); |
332 | 0 | } |
333 | | |
334 | | /// Sets the custom headers. |
335 | 0 | pub fn set_custom_headers( |
336 | 0 | &mut self, |
337 | 0 | custom_headers: &HashMap<String, String>, |
338 | 0 | ) -> Result<(), HttpHeaderError> { |
339 | 0 | self.headers.set_custom_headers(custom_headers) |
340 | 0 | } |
341 | | |
342 | | /// Gets a reference to the custom headers. |
343 | 0 | pub fn custom_headers(&self) -> &HashMap<String, String> { |
344 | 0 | &self.headers.custom_headers |
345 | 0 | } |
346 | | |
347 | | /// Sets the HTTP allowed methods. |
348 | 0 | pub fn set_allow(&mut self, methods: Vec<Method>) { |
349 | 0 | self.headers.allow = methods; |
350 | 0 | } |
351 | | |
352 | | /// Allows a specific HTTP method. |
353 | 0 | pub fn allow_method(&mut self, method: Method) { |
354 | 0 | self.headers.allow.push(method); |
355 | 0 | } |
356 | | |
357 | 0 | fn write_body<T: Write>(&self, mut buf: T) -> Result<(), WriteError> { |
358 | 0 | if let Some(ref body) = self.body { |
359 | 0 | buf.write_all(body.raw())?; |
360 | 0 | } |
361 | 0 | Ok(()) |
362 | 0 | } |
363 | | |
364 | | /// Writes the content of the `Response` to the specified `buf`. |
365 | | /// |
366 | | /// # Errors |
367 | | /// Returns an error when the buffer is not large enough. |
368 | 0 | pub fn write_all<T: Write>(&self, mut buf: &mut T) -> Result<(), WriteError> { |
369 | 0 | self.status_line.write_all(&mut buf)?; |
370 | 0 | self.headers.write_all(&mut buf)?; |
371 | 0 | self.write_body(&mut buf)?; |
372 | | |
373 | 0 | Ok(()) |
374 | 0 | } |
375 | | |
376 | | /// Returns the Status Code of the Response. |
377 | 0 | pub fn status(&self) -> StatusCode { |
378 | 0 | self.status_line.status_code |
379 | 0 | } |
380 | | |
381 | | /// Returns the Body of the response. If the response does not have a body, |
382 | | /// it returns None. |
383 | 0 | pub fn body(&self) -> Option<Body> { |
384 | 0 | self.body.clone() |
385 | 0 | } |
386 | | |
387 | | /// Returns the Content Length of the response. |
388 | 0 | pub fn content_length(&self) -> i32 { |
389 | 0 | self.headers.content_length.unwrap_or(0) |
390 | 0 | } |
391 | | |
392 | | /// Returns the Content Type of the response. |
393 | 0 | pub fn content_type(&self) -> MediaType { |
394 | 0 | self.headers.content_type |
395 | 0 | } |
396 | | |
397 | | /// Returns the deprecation status of the response. |
398 | 0 | pub fn deprecation(&self) -> bool { |
399 | 0 | self.headers.deprecation |
400 | 0 | } |
401 | | |
402 | | /// Returns the HTTP Version of the response. |
403 | 0 | pub fn http_version(&self) -> Version { |
404 | 0 | self.status_line.http_version |
405 | 0 | } |
406 | | |
407 | | /// Returns the allowed HTTP methods. |
408 | 0 | pub fn allow(&self) -> Vec<Method> { |
409 | 0 | self.headers.allow.clone() |
410 | 0 | } |
411 | | } |
412 | | |
413 | | #[cfg(test)] |
414 | | mod tests { |
415 | | use super::*; |
416 | | |
417 | | #[test] |
418 | | fn test_write_response() { |
419 | | let mut response = Response::new(Version::Http10, StatusCode::OK); |
420 | | let body = "This is a test"; |
421 | | response.set_body(Body::new(body)); |
422 | | response.set_content_type(MediaType::PlainText); |
423 | | response.set_encoding(); |
424 | | |
425 | | assert_eq!(response.status(), StatusCode::OK); |
426 | | assert_eq!(response.body().unwrap(), Body::new(body)); |
427 | | assert_eq!(response.http_version(), Version::Http10); |
428 | | assert_eq!(response.content_length(), 14); |
429 | | assert_eq!(response.content_type(), MediaType::PlainText); |
430 | | |
431 | | let expected_response: &'static [u8] = b"HTTP/1.0 200 \r\n\ |
432 | | Server: Firecracker API\r\n\ |
433 | | Connection: keep-alive\r\n\ |
434 | | Content-Type: text/plain\r\n\ |
435 | | Content-Length: 14\r\n\ |
436 | | Accept-Encoding: identity\r\n\r\n\ |
437 | | This is a test"; |
438 | | |
439 | | let mut response_buf: [u8; 153] = [0; 153]; |
440 | | assert!(response.write_all(&mut response_buf.as_mut()).is_ok()); |
441 | | assert_eq!(response_buf.as_ref(), expected_response); |
442 | | |
443 | | // Test response `Allow` header. |
444 | | let mut response = Response::new(Version::Http10, StatusCode::OK); |
445 | | let allowed_methods = vec![Method::Get, Method::Patch, Method::Put]; |
446 | | response.set_allow(allowed_methods.clone()); |
447 | | assert_eq!(response.allow(), allowed_methods); |
448 | | |
449 | | let expected_response: &'static [u8] = b"HTTP/1.0 200 \r\n\ |
450 | | Server: Firecracker API\r\n\ |
451 | | Connection: keep-alive\r\n\ |
452 | | Allow: GET, PATCH, PUT\r\n\ |
453 | | Content-Type: application/json\r\n\ |
454 | | Content-Length: 0\r\n\r\n"; |
455 | | let mut response_buf: [u8; 141] = [0; 141]; |
456 | | assert!(response.write_all(&mut response_buf.as_mut()).is_ok()); |
457 | | assert_eq!(response_buf.as_ref(), expected_response); |
458 | | |
459 | | // Test write failed. |
460 | | let mut response_buf: [u8; 1] = [0; 1]; |
461 | | assert!(response.write_all(&mut response_buf.as_mut()).is_err()); |
462 | | } |
463 | | |
464 | | #[test] |
465 | | fn test_set_server() { |
466 | | let mut response = Response::new(Version::Http10, StatusCode::OK); |
467 | | let body = "This is a test"; |
468 | | let server = "rust-vmm API"; |
469 | | response.set_body(Body::new(body)); |
470 | | response.set_content_type(MediaType::PlainText); |
471 | | response.set_server(server); |
472 | | |
473 | | assert_eq!(response.status(), StatusCode::OK); |
474 | | assert_eq!(response.body().unwrap(), Body::new(body)); |
475 | | assert_eq!(response.http_version(), Version::Http10); |
476 | | assert_eq!(response.content_length(), 14); |
477 | | assert_eq!(response.content_type(), MediaType::PlainText); |
478 | | |
479 | | let expected_response = format!( |
480 | | "HTTP/1.0 200 \r\n\ |
481 | | Server: {}\r\n\ |
482 | | Connection: keep-alive\r\n\ |
483 | | Content-Type: text/plain\r\n\ |
484 | | Content-Length: 14\r\n\r\n\ |
485 | | This is a test", |
486 | | server |
487 | | ); |
488 | | |
489 | | let mut response_buf: [u8; 123] = [0; 123]; |
490 | | assert!(response.write_all(&mut response_buf.as_mut()).is_ok()); |
491 | | assert!(response_buf.as_ref() == expected_response.as_bytes()); |
492 | | } |
493 | | |
494 | | #[test] |
495 | | fn test_status_code() { |
496 | | assert_eq!(StatusCode::Continue.raw(), b"100"); |
497 | | assert_eq!(StatusCode::OK.raw(), b"200"); |
498 | | assert_eq!(StatusCode::NoContent.raw(), b"204"); |
499 | | assert_eq!(StatusCode::BadRequest.raw(), b"400"); |
500 | | assert_eq!(StatusCode::Unauthorized.raw(), b"401"); |
501 | | assert_eq!(StatusCode::NotFound.raw(), b"404"); |
502 | | assert_eq!(StatusCode::MethodNotAllowed.raw(), b"405"); |
503 | | assert_eq!(StatusCode::PayloadTooLarge.raw(), b"413"); |
504 | | assert_eq!(StatusCode::TooManyRequests.raw(), b"429"); |
505 | | assert_eq!(StatusCode::InternalServerError.raw(), b"500"); |
506 | | assert_eq!(StatusCode::NotImplemented.raw(), b"501"); |
507 | | assert_eq!(StatusCode::ServiceUnavailable.raw(), b"503"); |
508 | | } |
509 | | |
510 | | #[test] |
511 | | fn test_allow_method() { |
512 | | let mut response = Response::new(Version::Http10, StatusCode::MethodNotAllowed); |
513 | | response.allow_method(Method::Get); |
514 | | response.allow_method(Method::Put); |
515 | | assert_eq!(response.allow(), vec![Method::Get, Method::Put]); |
516 | | } |
517 | | |
518 | | #[test] |
519 | | fn test_deprecation() { |
520 | | // Test a deprecated response with body. |
521 | | let mut response = Response::new(Version::Http10, StatusCode::OK); |
522 | | let body = "This is a test"; |
523 | | response.set_body(Body::new(body)); |
524 | | response.set_content_type(MediaType::PlainText); |
525 | | response.set_encoding(); |
526 | | response.set_deprecation(); |
527 | | |
528 | | assert_eq!(response.status(), StatusCode::OK); |
529 | | assert_eq!(response.body().unwrap(), Body::new(body)); |
530 | | assert_eq!(response.http_version(), Version::Http10); |
531 | | assert_eq!(response.content_length(), 14); |
532 | | assert_eq!(response.content_type(), MediaType::PlainText); |
533 | | assert!(response.deprecation()); |
534 | | |
535 | | let expected_response: &'static [u8] = b"HTTP/1.0 200 \r\n\ |
536 | | Server: Firecracker API\r\n\ |
537 | | Connection: keep-alive\r\n\ |
538 | | Deprecation: true\r\n\ |
539 | | Content-Type: text/plain\r\n\ |
540 | | Content-Length: 14\r\n\ |
541 | | Accept-Encoding: identity\r\n\r\n\ |
542 | | This is a test"; |
543 | | |
544 | | let mut response_buf: [u8; 172] = [0; 172]; |
545 | | assert!(response.write_all(&mut response_buf.as_mut()).is_ok()); |
546 | | assert_eq!(response_buf.as_ref(), expected_response); |
547 | | |
548 | | // Test a deprecated response without a body. |
549 | | let mut response = Response::new(Version::Http10, StatusCode::NoContent); |
550 | | response.set_deprecation(); |
551 | | |
552 | | assert_eq!(response.status(), StatusCode::NoContent); |
553 | | assert_eq!(response.http_version(), Version::Http10); |
554 | | assert!(response.deprecation()); |
555 | | |
556 | | let expected_response: &'static [u8] = b"HTTP/1.0 204 \r\n\ |
557 | | Server: Firecracker API\r\n\ |
558 | | Connection: keep-alive\r\n\ |
559 | | Deprecation: true\r\n\r\n"; |
560 | | |
561 | | let mut response_buf: [u8; 85] = [0; 85]; |
562 | | assert!(response.write_all(&mut response_buf.as_mut()).is_ok()); |
563 | | assert_eq!(response_buf.as_ref(), expected_response); |
564 | | } |
565 | | |
566 | | #[test] |
567 | | fn test_equal() { |
568 | | let response = Response::new(Version::Http10, StatusCode::MethodNotAllowed); |
569 | | let another_response = Response::new(Version::Http10, StatusCode::MethodNotAllowed); |
570 | | assert_eq!(response, another_response); |
571 | | |
572 | | let response = Response::new(Version::Http10, StatusCode::OK); |
573 | | let another_response = Response::new(Version::Http10, StatusCode::BadRequest); |
574 | | assert_ne!(response, another_response); |
575 | | } |
576 | | |
577 | | #[test] |
578 | | fn test_content_length() { |
579 | | // If the status code is 1XX or 204, Content-Length field must not exist. |
580 | | let response = Response::new(Version::Http10, StatusCode::Continue); |
581 | | let expected_response: &'static [u8] = b"HTTP/1.0 100 \r\n\ |
582 | | Server: Firecracker API\r\n\ |
583 | | Connection: keep-alive\r\n\r\n"; |
584 | | let mut response_buf: [u8; 66] = [0; 66]; |
585 | | assert!(response.write_all(&mut response_buf.as_mut()).is_ok()); |
586 | | assert_eq!(response_buf.as_ref(), expected_response); |
587 | | |
588 | | let response = Response::new(Version::Http10, StatusCode::NoContent); |
589 | | let expected_response: &'static [u8] = b"HTTP/1.0 204 \r\n\ |
590 | | Server: Firecracker API\r\n\ |
591 | | Connection: keep-alive\r\n\r\n"; |
592 | | let mut response_buf: [u8; 66] = [0; 66]; |
593 | | assert!(response.write_all(&mut response_buf.as_mut()).is_ok()); |
594 | | assert_eq!(response_buf.as_ref(), expected_response); |
595 | | |
596 | | // If not 1XX or 204, Content-Length field must exist even if the body isn't set. |
597 | | let response = Response::new(Version::Http10, StatusCode::OK); |
598 | | let expected_response: &'static [u8] = b"HTTP/1.0 200 \r\n\ |
599 | | Server: Firecracker API\r\n\ |
600 | | Connection: keep-alive\r\n\ |
601 | | Content-Type: application/json\r\n\ |
602 | | Content-Length: 0\r\n\r\n"; |
603 | | let mut response_buf: [u8; 117] = [0; 117]; |
604 | | assert!(response.write_all(&mut response_buf.as_mut()).is_ok()); |
605 | | assert_eq!(response_buf.as_ref(), expected_response); |
606 | | } |
607 | | |
608 | | #[test] |
609 | | fn test_custom_headers() { |
610 | | // Valid custom headers. |
611 | | let mut response = Response::new(Version::Http10, StatusCode::OK); |
612 | | let custom_headers = [("Foo".into(), "Bar".into())].into(); |
613 | | response.set_custom_headers(&custom_headers).unwrap(); |
614 | | let expected_response = b"HTTP/1.0 200 \r\n\ |
615 | | Server: Firecracker API\r\n\ |
616 | | Connection: keep-alive\r\n\ |
617 | | Foo: Bar\r\n\ |
618 | | Content-Type: application/json\r\n\ |
619 | | Content-Length: 0\r\n\r\n"; |
620 | | let mut response_buf: [u8; 127] = [0; 127]; |
621 | | response.write_all(&mut response_buf.as_mut()).unwrap(); |
622 | | assert_eq!(response_buf.as_ref(), expected_response); |
623 | | |
624 | | // Should fail to set custom headers including non-ASCII character. |
625 | | let mut response = Response::new(Version::Http10, StatusCode::OK); |
626 | | let custom_headers = [("Greek capital delta".into(), "Δ".into())].into(); |
627 | | assert_eq!( |
628 | | response.set_custom_headers(&custom_headers).unwrap_err(), |
629 | | HttpHeaderError::NonAsciiCharacter("Greek capital delta".into(), "Δ".into()) |
630 | | ); |
631 | | } |
632 | | } |