@@ -86,7 +86,7 @@ impl StatusLine {
86
86
/// The content type can be updated with a call to `set_content_type`.
87
87
#[ derive( Debug , PartialEq , Eq ) ]
88
88
pub struct ResponseHeaders {
89
- content_length : i32 ,
89
+ content_length : Option < i32 > ,
90
90
content_type : MediaType ,
91
91
deprecation : bool ,
92
92
server : String ,
@@ -151,15 +151,15 @@ impl ResponseHeaders {
151
151
self . write_allow_header ( buf) ?;
152
152
self . write_deprecation_header ( buf) ?;
153
153
154
- if self . content_length != 0 {
154
+ if let Some ( content_length) = self . content_length {
155
155
buf. write_all ( Header :: ContentType . raw ( ) ) ?;
156
156
buf. write_all ( & [ COLON , SP ] ) ?;
157
157
buf. write_all ( self . content_type . as_str ( ) . as_bytes ( ) ) ?;
158
158
buf. write_all ( & [ CR , LF ] ) ?;
159
159
160
160
buf. write_all ( Header :: ContentLength . raw ( ) ) ?;
161
161
buf. write_all ( & [ COLON , SP ] ) ?;
162
- buf. write_all ( self . content_length . to_string ( ) . as_bytes ( ) ) ?;
162
+ buf. write_all ( content_length. to_string ( ) . as_bytes ( ) ) ?;
163
163
buf. write_all ( & [ CR , LF ] ) ?;
164
164
165
165
if self . accept_encoding {
@@ -173,8 +173,8 @@ impl ResponseHeaders {
173
173
buf. write_all ( & [ CR , LF ] )
174
174
}
175
175
176
- // Sets the content length to be written in the HTTP response.
177
- fn set_content_length ( & mut self , content_length : i32 ) {
176
+ /// Sets the content length to be written in the HTTP response.
177
+ pub fn set_content_length ( & mut self , content_length : Option < i32 > ) {
178
178
self . content_length = content_length;
179
179
}
180
180
@@ -217,10 +217,39 @@ pub struct Response {
217
217
218
218
impl Response {
219
219
/// Creates a new HTTP `Response` with an empty body.
220
+ ///
221
+ /// Although there are several cases where Content-Length field must not
222
+ /// be sent, micro-http omits Content-Length field when the response
223
+ /// status code is 1XX or 204. If needed, users can remove it by calling
224
+ /// `set_content_length(None)`.
225
+ ///
226
+ /// https://datatracker.ietf.org/doc/html/rfc9110#name-content-length
227
+ /// > A server MAY send a Content-Length header field in a response to a
228
+ /// > HEAD request (Section 9.3.2); a server MUST NOT send Content-Length
229
+ /// > in such a response unless its field value equals the decimal number
230
+ /// > of octets that would have been sent in the content of a response if
231
+ /// > the same request had used the GET method.
232
+ /// >
233
+ /// > A server MAY send a Content-Length header field in a 304 (Not
234
+ /// > Modified) response to a conditional GET request (Section 15.4.5); a
235
+ /// > server MUST NOT send Content-Length in such a response unless its
236
+ /// > field value equals the decimal number of octets that would have been
237
+ /// > sent in the content of a 200 (OK) response to the same request.
238
+ /// >
239
+ /// > A server MUST NOT send a Content-Length header field in any response
240
+ /// > with a status code of 1xx (Informational) or 204 (No Content). A
241
+ /// > server MUST NOT send a Content-Length header field in any 2xx
242
+ /// > (Successful) response to a CONNECT request (Section 9.3.6).
220
243
pub fn new ( http_version : Version , status_code : StatusCode ) -> Self {
221
244
Self {
222
245
status_line : StatusLine :: new ( http_version, status_code) ,
223
- headers : ResponseHeaders :: default ( ) ,
246
+ headers : ResponseHeaders {
247
+ content_length : match status_code {
248
+ StatusCode :: Continue | StatusCode :: NoContent => None ,
249
+ _ => Some ( 0 ) ,
250
+ } ,
251
+ ..Default :: default ( )
252
+ } ,
224
253
body : Default :: default ( ) ,
225
254
}
226
255
}
@@ -230,10 +259,18 @@ impl Response {
230
259
/// This function has side effects because it also updates the headers:
231
260
/// - `ContentLength`: this is set to the length of the specified body.
232
261
pub fn set_body ( & mut self , body : Body ) {
233
- self . headers . set_content_length ( body. len ( ) as i32 ) ;
262
+ self . headers . set_content_length ( Some ( body. len ( ) as i32 ) ) ;
234
263
self . body = Some ( body) ;
235
264
}
236
265
266
+ /// Updates the content length of the `Response`.
267
+ ///
268
+ /// It is recommended to use this method only when removing Content-Length
269
+ /// field if the response status is not 1XX or 204.
270
+ pub fn set_content_length ( & mut self , content_length : Option < i32 > ) {
271
+ self . headers . set_content_length ( content_length) ;
272
+ }
273
+
237
274
/// Updates the content type of the `Response`.
238
275
pub fn set_content_type ( & mut self , content_type : MediaType ) {
239
276
self . headers . set_content_type ( content_type) ;
@@ -296,7 +333,7 @@ impl Response {
296
333
297
334
/// Returns the Content Length of the response.
298
335
pub fn content_length ( & self ) -> i32 {
299
- self . headers . content_length
336
+ self . headers . content_length . unwrap_or ( 0 )
300
337
}
301
338
302
339
/// Returns the Content Type of the response.
@@ -359,8 +396,10 @@ mod tests {
359
396
let expected_response: & ' static [ u8 ] = b"HTTP/1.0 200 \r \n \
360
397
Server: Firecracker API\r \n \
361
398
Connection: keep-alive\r \n \
362
- Allow: GET, PATCH, PUT\r \n \r \n ";
363
- let mut response_buf: [ u8 ; 90 ] = [ 0 ; 90 ] ;
399
+ Allow: GET, PATCH, PUT\r \n \
400
+ Content-Type: application/json\r \n \
401
+ Content-Length: 0\r \n \r \n ";
402
+ let mut response_buf: [ u8 ; 141 ] = [ 0 ; 141 ] ;
364
403
assert ! ( response. write_all( & mut response_buf. as_mut( ) ) . is_ok( ) ) ;
365
404
assert_eq ! ( response_buf. as_ref( ) , expected_response) ;
366
405
@@ -480,4 +519,35 @@ mod tests {
480
519
let another_response = Response :: new ( Version :: Http10 , StatusCode :: BadRequest ) ;
481
520
assert_ne ! ( response, another_response) ;
482
521
}
522
+
523
+ #[ test]
524
+ fn test_content_length ( ) {
525
+ // If the status code is 1XX or 204, Content-Length field must not exist.
526
+ let response = Response :: new ( Version :: Http10 , StatusCode :: Continue ) ;
527
+ let expected_response: & ' static [ u8 ] = b"HTTP/1.0 100 \r \n \
528
+ Server: Firecracker API\r \n \
529
+ Connection: keep-alive\r \n \r \n ";
530
+ let mut response_buf: [ u8 ; 66 ] = [ 0 ; 66 ] ;
531
+ assert ! ( response. write_all( & mut response_buf. as_mut( ) ) . is_ok( ) ) ;
532
+ assert_eq ! ( response_buf. as_ref( ) , expected_response) ;
533
+
534
+ let response = Response :: new ( Version :: Http10 , StatusCode :: NoContent ) ;
535
+ let expected_response: & ' static [ u8 ] = b"HTTP/1.0 204 \r \n \
536
+ Server: Firecracker API\r \n \
537
+ Connection: keep-alive\r \n \r \n ";
538
+ let mut response_buf: [ u8 ; 66 ] = [ 0 ; 66 ] ;
539
+ assert ! ( response. write_all( & mut response_buf. as_mut( ) ) . is_ok( ) ) ;
540
+ assert_eq ! ( response_buf. as_ref( ) , expected_response) ;
541
+
542
+ // If not 1XX or 204, Content-Length field must exist even if the body isn't set.
543
+ let response = Response :: new ( Version :: Http10 , StatusCode :: OK ) ;
544
+ let expected_response: & ' static [ u8 ] = b"HTTP/1.0 200 \r \n \
545
+ Server: Firecracker API\r \n \
546
+ Connection: keep-alive\r \n \
547
+ Content-Type: application/json\r \n \
548
+ Content-Length: 0\r \n \r \n ";
549
+ let mut response_buf: [ u8 ; 117 ] = [ 0 ; 117 ] ;
550
+ assert ! ( response. write_all( & mut response_buf. as_mut( ) ) . is_ok( ) ) ;
551
+ assert_eq ! ( response_buf. as_ref( ) , expected_response) ;
552
+ }
483
553
}
0 commit comments