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