Repository: lipanski/mockito (Rust)

Issue:
custom "Content-Length"
For HTTP HEAD requests, the response can include a Content-Length indicating the size of the resource without actually returning the resource in the body. When I try setting `"Content-Length"` headers with `with_header()` and no body, mockito seems to append `", 0"` to the header.

I can try to do a PR to address this but it'd be good to know what the best solution would be. One possibility that I can think of is that the default behavior of computing the Content-Length from the body should be suppressed if the header is supplied explicitly via `with_header`.


Discussion:
I'll have a look. thanks for reporting.
@dherman took a while, but https://github.com/lipanski/mockito/pull/63 should do the job.

if you set `content-length` via `#with_header`, it will not be overriden any more. also, in the case of `HEAD` requests no body will be sent (previously the crate would either set an empty, 0 length body, or it would allow the developer to set a body, which I guess was wrong).

let me know if it works.



Patch:
diff --git a/src/request.rs b/src/request.rs
index f8b3672..9af671b 100644
--- a/src/request.rs
+++ b/src/request.rs
@@ -22,6 +22,10 @@ pub struct Request {
 }
 
 impl Request {
+    pub fn is_head(&self) -> bool {
+        self.method == "HEAD"
+    }
+
     pub fn is_ok(&self) -> bool {
         self.error.is_none()
     }
diff --git a/src/server.rs b/src/server.rs
index 6622a3e..06d90f4 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -135,7 +135,7 @@ fn handle_match_mock(request: Request, stream: TcpStream) {
         debug!("Mock found");
         found = true;
         mock.hits += 1;
-        respond_with_mock(stream, mock);
+        respond_with_mock(stream, mock, request.is_head());
     } else {
         debug!("Mock not found");
         found = false;
@@ -145,19 +145,30 @@ fn handle_match_mock(request: Request, stream: TcpStream) {
     if !found { state.unmatched_requests.push(request); }
 }
 
-fn respond<S: Display>(stream: TcpStream, status: S, headers: Option<&str>, body: Option<&str>) {
+fn respond<S: Display>(stream: TcpStream, status: S, headers: Option<&Vec<(String, String)>>, body: Option<&str>) {
     respond_bytes(stream, status, headers, body.map(|s| s.as_bytes()))
 }
 
-fn respond_bytes<S: Display>(mut stream: TcpStream, status: S, headers: Option<&str>, body: Option<&[u8]>) {
+fn respond_bytes<S: Display>(mut stream: TcpStream, status: S, headers: Option<&Vec<(String, String)>>, body: Option<&[u8]>) {
     let mut response = Vec::from(format!("HTTP/1.1 {}\r\n", status));
+    let mut has_content_length_header = false;
 
     if let Some(headers) = headers {
-        response.extend(headers.as_bytes());
+        for &(ref key, ref value) in headers {
+            response.extend(key.as_bytes());
+            response.extend(b": ");
+            response.extend(value.as_bytes());
+            response.extend(b"\r\n");
+        }
+
+        has_content_length_header = headers.iter().any(|(key, _)| key == "content-length");
     }
 
     if let Some(body) = body {
-        response.extend(format!("content-length: {}\r\n\r\n", body.len()).as_bytes());
+        if !has_content_length_header {
+            response.extend(format!("content-length: {}\r\n\r\n", body.len()).as_bytes());
+        }
+
         response.extend(body);
     } else {
         response.extend(b"\r\n");
@@ -167,16 +178,15 @@ fn respond_bytes<S: Display>(mut stream: TcpStream, status: S, headers: Option<&
     let _ = stream.flush();
 }
 
-fn respond_with_mock(stream: TcpStream, mock: &Mock) {
-    let mut headers = String::new();
-    for &(ref key, ref value) in &mock.response.headers {
-        headers.push_str(key);
-        headers.push_str(": ");
-        headers.push_str(value);
-        headers.push_str("\r\n");
-    }
+fn respond_with_mock(stream: TcpStream, mock: &Mock, skip_body: bool) {
+    let body =
+        if skip_body {
+            None
+        } else {
+            Some(&*mock.response.body)
+        };
 
-    respond_bytes(stream, &mock.response.status, Some(&headers), Some(&mock.response.body));
+    respond_bytes(stream, &mock.response.status, Some(&mock.response.headers), body);
 }
 
 fn respond_with_mock_not_found(stream: TcpStream) {


Test patch:
diff --git a/tests/lib.rs b/tests/lib.rs
index 03d0f1d..0630b5a 100644
--- a/tests/lib.rs
+++ b/tests/lib.rs
@@ -19,7 +19,7 @@ fn request_stream(route: &str, headers: &str, body: &str) -> TcpStream {
     stream
 }
 
-fn parse_stream(stream: TcpStream) -> (String, Vec<String>, String) {
+fn parse_stream(stream: TcpStream, skip_body: bool) -> (String, Vec<String>, String) {
     let mut reader = BufReader::new(stream);
 
     let mut status_line = String::new();
@@ -42,18 +42,20 @@ fn parse_stream(stream: TcpStream) -> (String, Vec<String>, String) {
     }
 
     let mut body = String::new();
-    reader.take(content_length).read_to_string(&mut body).unwrap();
+    if !skip_body {
+        reader.take(content_length).read_to_string(&mut body).unwrap();
+    }
 
     (status_line, headers, body)
 }
 
 fn request(route: &str, headers: &str) -> (String, Vec<String>, String) {
-    parse_stream(request_stream(route, headers, ""))
+    parse_stream(request_stream(route, headers, ""), route.starts_with("HEAD"))
 }
 
 fn request_with_body(route: &str, headers: &str, body: &str) -> (String, Vec<String>, String) {
     let headers = format!("{}content-length: {}\r\n", headers, body.len());
-    parse_stream(request_stream(route, &headers, body))
+    parse_stream(request_stream(route, &headers, body), false)
 }
 
 #[test]
@@ -712,3 +714,12 @@ fn test_mock_from_inside_thread_does_not_lock_forever() {
 
     assert_eq!("outside", body);
 }
+
+#[test]
+fn test_head_request_with_overridden_content_length() {
+    let _mock = mock("HEAD", "/").with_header("content-length", "100").create();
+
+    let (_, headers, _) = request("HEAD /", "");
+
+    assert_eq!(vec![String::from("content-length: 100")], headers);
+}