Receive multiple file descriptors with a Request
Extending the existing code to support receiving more than one file descriptor per request. The micro-http crate might be used in a context where multiple file descriptors are associated with one request, hence the need to update the micro-http crate. A concrete example from Cloud Hypervisor is to be able to pass multiple TAP file descriptors at once when adding a new network interface. This way it can hotplug a multiqueue device. Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
This commit is contained in:
committed by
georgepisaltu
parent
0a58eb1ece
commit
a730d86940
6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -1,5 +1,7 @@
|
|||||||
# This file is automatically @generated by Cargo.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
@@ -22,9 +24,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vmm-sys-util"
|
name = "vmm-sys-util"
|
||||||
version = "0.8.0"
|
version = "0.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "01cf11afbc4ebc0d5c7a7748a77d19e2042677fc15faa2f4ccccb27c18a60605"
|
checksum = "733537bded03aaa93543f785ae997727b30d1d9f4a03b7861d23290474242e11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"libc",
|
"libc",
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"coverage_score": 93.1, "exclude_path": "", "crate_features": ""}
|
{"coverage_score": 93.2, "exclude_path": "", "crate_features": ""}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
|
use std::os::unix::io::FromRawFd;
|
||||||
|
|
||||||
use crate::common::ascii::{CR, CRLF_LEN, LF};
|
use crate::common::ascii::{CR, CRLF_LEN, LF};
|
||||||
use crate::common::Body;
|
use crate::common::Body;
|
||||||
@@ -15,6 +16,7 @@ use crate::server::MAX_PAYLOAD_SIZE;
|
|||||||
use vmm_sys_util::sock_ctrl_msg::ScmSocket;
|
use vmm_sys_util::sock_ctrl_msg::ScmSocket;
|
||||||
|
|
||||||
const BUFFER_SIZE: usize = 1024;
|
const BUFFER_SIZE: usize = 1024;
|
||||||
|
const SCM_MAX_FD: usize = 253;
|
||||||
|
|
||||||
/// Describes the state machine of an HTTP connection.
|
/// Describes the state machine of an HTTP connection.
|
||||||
enum ConnectionState {
|
enum ConnectionState {
|
||||||
@@ -52,9 +54,9 @@ pub struct HttpConnection<T> {
|
|||||||
/// A buffer containing the bytes of a response that is currently
|
/// A buffer containing the bytes of a response that is currently
|
||||||
/// being sent.
|
/// being sent.
|
||||||
response_buffer: Option<Vec<u8>>,
|
response_buffer: Option<Vec<u8>>,
|
||||||
/// The latest file that has been received and which must be associated
|
/// The list of files that has been received and which must be associated
|
||||||
/// with the pending request.
|
/// with the pending request.
|
||||||
file: Option<File>,
|
files: Vec<File>,
|
||||||
/// Optional payload max size.
|
/// Optional payload max size.
|
||||||
payload_max_size: usize,
|
payload_max_size: usize,
|
||||||
}
|
}
|
||||||
@@ -73,7 +75,7 @@ impl<T: Read + Write + ScmSocket> HttpConnection<T> {
|
|||||||
parsed_requests: VecDeque::new(),
|
parsed_requests: VecDeque::new(),
|
||||||
response_queue: VecDeque::new(),
|
response_queue: VecDeque::new(),
|
||||||
response_buffer: None,
|
response_buffer: None,
|
||||||
file: None,
|
files: Vec::new(),
|
||||||
payload_max_size: MAX_PAYLOAD_SIZE,
|
payload_max_size: MAX_PAYLOAD_SIZE,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,7 +125,7 @@ impl<T: Read + Write + ScmSocket> HttpConnection<T> {
|
|||||||
self.state = ConnectionState::WaitingForRequestLine;
|
self.state = ConnectionState::WaitingForRequestLine;
|
||||||
self.body_bytes_to_be_read = 0;
|
self.body_bytes_to_be_read = 0;
|
||||||
let mut pending_request = self.pending_request.take().unwrap();
|
let mut pending_request = self.pending_request.take().unwrap();
|
||||||
pending_request.file = self.file.take();
|
pending_request.files = self.files.drain(..).collect();
|
||||||
self.parsed_requests.push_back(pending_request);
|
self.parsed_requests.push_back(pending_request);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -143,15 +145,11 @@ impl<T: Read + Write + ScmSocket> HttpConnection<T> {
|
|||||||
}
|
}
|
||||||
// Append new bytes to what we already have in the buffer.
|
// Append new bytes to what we already have in the buffer.
|
||||||
// The slice access is safe, the index is checked above.
|
// The slice access is safe, the index is checked above.
|
||||||
let (bytes_read, file) = self
|
let (bytes_read, new_files) = self.recv_with_fds()?;
|
||||||
.stream
|
|
||||||
.recv_with_fd(&mut self.buffer[self.read_cursor..])
|
|
||||||
.map_err(ConnectionError::StreamReadError)?;
|
|
||||||
|
|
||||||
// Update the internal file that must be associated with the request.
|
// Update the internal list of files that must be associated with the
|
||||||
if file.is_some() {
|
// request.
|
||||||
self.file = file;
|
self.files.extend(new_files);
|
||||||
}
|
|
||||||
|
|
||||||
// If the read returned 0 then the client has closed the connection.
|
// If the read returned 0 then the client has closed the connection.
|
||||||
if bytes_read == 0 {
|
if bytes_read == 0 {
|
||||||
@@ -162,6 +160,43 @@ impl<T: Read + Write + ScmSocket> HttpConnection<T> {
|
|||||||
.ok_or(ConnectionError::ParseError(RequestError::Overflow))
|
.ok_or(ConnectionError::ParseError(RequestError::Overflow))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Receive data along with optional files descriptors.
|
||||||
|
/// It is a wrapper around the same function from vmm-sys-util.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// `StreamError` is returned if any error occurred while reading the stream.
|
||||||
|
fn recv_with_fds(&mut self) -> Result<(usize, Vec<File>), ConnectionError> {
|
||||||
|
let buf = &mut self.buffer[self.read_cursor..];
|
||||||
|
// We must allocate the maximum number of receivable file descriptors
|
||||||
|
// if don't want to miss any of them. Allocating a too small number
|
||||||
|
// would lead to the incapacity of receiving the file descriptors.
|
||||||
|
let mut fds = [0; SCM_MAX_FD];
|
||||||
|
let mut iovecs = [libc::iovec {
|
||||||
|
iov_base: buf.as_mut_ptr() as *mut libc::c_void,
|
||||||
|
iov_len: buf.len(),
|
||||||
|
}];
|
||||||
|
|
||||||
|
// Safe because we have mutably borrowed buf and it's safe to write
|
||||||
|
// arbitrary data to a slice.
|
||||||
|
let (read_count, fd_count) = unsafe {
|
||||||
|
self.stream
|
||||||
|
.recv_with_fds(&mut iovecs, &mut fds)
|
||||||
|
.map_err(ConnectionError::StreamReadError)?
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
read_count,
|
||||||
|
fds.iter()
|
||||||
|
.take(fd_count)
|
||||||
|
.map(|fd| {
|
||||||
|
// Safe because all fds are owned by us after they have been
|
||||||
|
// received through the socket.
|
||||||
|
unsafe { File::from_raw_fd(*fd) }
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
/// Parses bytes in `buffer` for a valid request line.
|
/// Parses bytes in `buffer` for a valid request line.
|
||||||
/// Returns `false` if there are no more bytes to be parsed in the buffer.
|
/// Returns `false` if there are no more bytes to be parsed in the buffer.
|
||||||
///
|
///
|
||||||
@@ -197,7 +232,7 @@ impl<T: Read + Write + ScmSocket> HttpConnection<T> {
|
|||||||
.map_err(ConnectionError::ParseError)?,
|
.map_err(ConnectionError::ParseError)?,
|
||||||
headers: Headers::default(),
|
headers: Headers::default(),
|
||||||
body: None,
|
body: None,
|
||||||
file: None,
|
files: Vec::new(),
|
||||||
});
|
});
|
||||||
self.state = ConnectionState::WaitingForHeaders;
|
self.state = ConnectionState::WaitingForHeaders;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
@@ -517,13 +552,17 @@ impl<T: Read + Write + ScmSocket> HttpConnection<T> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use std::io::{Seek, SeekFrom};
|
||||||
use std::net::Shutdown;
|
use std::net::Shutdown;
|
||||||
|
use std::os::unix::io::IntoRawFd;
|
||||||
use std::os::unix::net::UnixStream;
|
use std::os::unix::net::UnixStream;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::common::{Method, Version};
|
use crate::common::{Method, Version};
|
||||||
use crate::server::MAX_PAYLOAD_SIZE;
|
use crate::server::MAX_PAYLOAD_SIZE;
|
||||||
|
|
||||||
|
use vmm_sys_util::tempfile::TempFile;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_try_read_expect() {
|
fn test_try_read_expect() {
|
||||||
// Test request with `Expect` header.
|
// Test request with `Expect` header.
|
||||||
@@ -548,7 +587,7 @@ mod tests {
|
|||||||
request_line: RequestLine::new(Method::Patch, "http://localhost/home", Version::Http11),
|
request_line: RequestLine::new(Method::Patch, "http://localhost/home", Version::Http11),
|
||||||
headers: Headers::new(26, true, true),
|
headers: Headers::new(26, true, true),
|
||||||
body: Some(Body::new(b"this is not\n\r\na json \nbody".to_vec())),
|
body: Some(Body::new(b"this is not\n\r\na json \nbody".to_vec())),
|
||||||
file: None,
|
files: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(request, expected_request);
|
assert_eq!(request, expected_request);
|
||||||
@@ -585,7 +624,7 @@ mod tests {
|
|||||||
request_line: RequestLine::new(Method::Patch, "http://localhost/home", Version::Http11),
|
request_line: RequestLine::new(Method::Patch, "http://localhost/home", Version::Http11),
|
||||||
headers: Headers::new(26, true, true),
|
headers: Headers::new(26, true, true),
|
||||||
body: Some(Body::new(b"this is not\n\r\na json \nbody".to_vec())),
|
body: Some(Body::new(b"this is not\n\r\na json \nbody".to_vec())),
|
||||||
file: None,
|
files: Vec::new(),
|
||||||
};
|
};
|
||||||
assert_eq!(request, expected_request);
|
assert_eq!(request, expected_request);
|
||||||
}
|
}
|
||||||
@@ -619,7 +658,7 @@ mod tests {
|
|||||||
request_line: RequestLine::new(Method::Patch, "http://localhost/home", Version::Http11),
|
request_line: RequestLine::new(Method::Patch, "http://localhost/home", Version::Http11),
|
||||||
headers: Headers::new(26, true, true),
|
headers: Headers::new(26, true, true),
|
||||||
body: Some(Body::new(b"this is not\n\r\na json \nbody".to_vec())),
|
body: Some(Body::new(b"this is not\n\r\na json \nbody".to_vec())),
|
||||||
file: None,
|
files: Vec::new(),
|
||||||
};
|
};
|
||||||
assert_eq!(request, expected_request);
|
assert_eq!(request, expected_request);
|
||||||
}
|
}
|
||||||
@@ -684,7 +723,7 @@ mod tests {
|
|||||||
request_line: RequestLine::new(Method::Patch, "http://localhost/home", Version::Http11),
|
request_line: RequestLine::new(Method::Patch, "http://localhost/home", Version::Http11),
|
||||||
headers: Headers::new(1400, true, true),
|
headers: Headers::new(1400, true, true),
|
||||||
body: Some(Body::new(request_body)),
|
body: Some(Body::new(request_body)),
|
||||||
file: None,
|
files: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(request, expected_request);
|
assert_eq!(request, expected_request);
|
||||||
@@ -755,7 +794,7 @@ mod tests {
|
|||||||
request_line: RequestLine::new(Method::Patch, "http://localhost/home", Version::Http11),
|
request_line: RequestLine::new(Method::Patch, "http://localhost/home", Version::Http11),
|
||||||
headers: Headers::new(0, true, true),
|
headers: Headers::new(0, true, true),
|
||||||
body: None,
|
body: None,
|
||||||
file: None,
|
files: Vec::new(),
|
||||||
};
|
};
|
||||||
assert_eq!(request, expected_request);
|
assert_eq!(request, expected_request);
|
||||||
}
|
}
|
||||||
@@ -777,7 +816,7 @@ mod tests {
|
|||||||
request_line: RequestLine::new(Method::Patch, "http://localhost/home", Version::Http11),
|
request_line: RequestLine::new(Method::Patch, "http://localhost/home", Version::Http11),
|
||||||
headers: Headers::new(0, false, false),
|
headers: Headers::new(0, false, false),
|
||||||
body: None,
|
body: None,
|
||||||
file: None,
|
files: Vec::new(),
|
||||||
};
|
};
|
||||||
assert_eq!(request, expected_request);
|
assert_eq!(request, expected_request);
|
||||||
}
|
}
|
||||||
@@ -806,7 +845,7 @@ mod tests {
|
|||||||
request_line: RequestLine::new(Method::Patch, "http://localhost/home", Version::Http11),
|
request_line: RequestLine::new(Method::Patch, "http://localhost/home", Version::Http11),
|
||||||
headers: Headers::new(0, false, false),
|
headers: Headers::new(0, false, false),
|
||||||
body: None,
|
body: None,
|
||||||
file: None,
|
files: Vec::new(),
|
||||||
};
|
};
|
||||||
assert_eq!(request, expected_request);
|
assert_eq!(request, expected_request);
|
||||||
|
|
||||||
@@ -825,7 +864,7 @@ mod tests {
|
|||||||
),
|
),
|
||||||
headers: Headers::new(0, false, false),
|
headers: Headers::new(0, false, false),
|
||||||
body: None,
|
body: None,
|
||||||
file: None,
|
files: Vec::new(),
|
||||||
};
|
};
|
||||||
assert_eq!(request, expected_request);
|
assert_eq!(request, expected_request);
|
||||||
}
|
}
|
||||||
@@ -853,7 +892,7 @@ mod tests {
|
|||||||
request_line: RequestLine::new(Method::Patch, "http://localhost/home", Version::Http11),
|
request_line: RequestLine::new(Method::Patch, "http://localhost/home", Version::Http11),
|
||||||
headers: Headers::new(26, false, true),
|
headers: Headers::new(26, false, true),
|
||||||
body: Some(Body::new(b"this is not\n\r\na json \nbody".to_vec())),
|
body: Some(Body::new(b"this is not\n\r\na json \nbody".to_vec())),
|
||||||
file: None,
|
files: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
conn.try_read().unwrap();
|
conn.try_read().unwrap();
|
||||||
@@ -864,7 +903,7 @@ mod tests {
|
|||||||
request_line: RequestLine::new(Method::Put, "http://farhost/away", Version::Http11),
|
request_line: RequestLine::new(Method::Put, "http://farhost/away", Version::Http11),
|
||||||
headers: Headers::new(23, false, false),
|
headers: Headers::new(23, false, false),
|
||||||
body: Some(Body::new(b"this is another request".to_vec())),
|
body: Some(Body::new(b"this is another request".to_vec())),
|
||||||
file: None,
|
files: Vec::new(),
|
||||||
};
|
};
|
||||||
assert_eq!(request_first, expected_request_first);
|
assert_eq!(request_first, expected_request_first);
|
||||||
assert_eq!(request_second, expected_request_second);
|
assert_eq!(request_second, expected_request_second);
|
||||||
@@ -999,6 +1038,77 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_read_bytes_with_files() {
|
||||||
|
let (sender, receiver) = UnixStream::pair().unwrap();
|
||||||
|
receiver.set_nonblocking(true).expect("Can't modify socket");
|
||||||
|
let mut conn = HttpConnection::new(receiver);
|
||||||
|
|
||||||
|
// Create 3 files, edit the content and rewind back to the start.
|
||||||
|
let mut file1 = TempFile::new().unwrap().into_file();
|
||||||
|
let mut file2 = TempFile::new().unwrap().into_file();
|
||||||
|
let mut file3 = TempFile::new().unwrap().into_file();
|
||||||
|
file1.write(b"foo").unwrap();
|
||||||
|
file1.seek(SeekFrom::Start(0)).unwrap();
|
||||||
|
file2.write(b"bar").unwrap();
|
||||||
|
file2.seek(SeekFrom::Start(0)).unwrap();
|
||||||
|
file3.write(b"foobar").unwrap();
|
||||||
|
file3.seek(SeekFrom::Start(0)).unwrap();
|
||||||
|
|
||||||
|
// Send 2 file descriptors along with 3 bytes of data.
|
||||||
|
assert_eq!(
|
||||||
|
sender.send_with_fds(
|
||||||
|
&[[1, 2, 3].as_ref()],
|
||||||
|
&[file1.into_raw_fd(), file2.into_raw_fd()]
|
||||||
|
),
|
||||||
|
Ok(3)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check we receive the right amount of data along with the right
|
||||||
|
// amount of file descriptors.
|
||||||
|
assert_eq!(conn.read_bytes(), Ok(3));
|
||||||
|
assert_eq!(conn.files.len(), 2);
|
||||||
|
|
||||||
|
// Check the content of the data received
|
||||||
|
assert_eq!(conn.buffer[0], 1);
|
||||||
|
assert_eq!(conn.buffer[1], 2);
|
||||||
|
assert_eq!(conn.buffer[2], 3);
|
||||||
|
|
||||||
|
// Check the file descriptors are usable by checking the content that
|
||||||
|
// can be read.
|
||||||
|
let mut buf = [0; 10];
|
||||||
|
assert_eq!(conn.files[0].read(&mut buf).unwrap(), 3);
|
||||||
|
assert_eq!(&buf[..3], b"foo");
|
||||||
|
assert_eq!(conn.files[1].read(&mut buf).unwrap(), 3);
|
||||||
|
assert_eq!(&buf[..3], b"bar");
|
||||||
|
|
||||||
|
// Send the 3rd file descriptor along with 1 byte of data.
|
||||||
|
assert_eq!(
|
||||||
|
sender.send_with_fds(&[[10].as_ref()], &[file3.into_raw_fd()]),
|
||||||
|
Ok(1)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check the amount of data along with the amount of file descriptors
|
||||||
|
// are updated.
|
||||||
|
assert_eq!(conn.read_bytes(), Ok(1));
|
||||||
|
assert_eq!(conn.files.len(), 3);
|
||||||
|
|
||||||
|
// Check the content of the new data received
|
||||||
|
assert_eq!(conn.buffer[0], 10);
|
||||||
|
|
||||||
|
// Check the latest file descriptor is usable by checking the content
|
||||||
|
// that can be read.
|
||||||
|
let mut buf = [0; 10];
|
||||||
|
assert_eq!(conn.files[2].read(&mut buf).unwrap(), 6);
|
||||||
|
assert_eq!(&buf[..6], b"foobar");
|
||||||
|
|
||||||
|
sender.shutdown(Shutdown::Write).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
conn.read_bytes().unwrap_err(),
|
||||||
|
ConnectionError::ConnectionClosed
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_shift_buffer_left() {
|
fn test_shift_buffer_left() {
|
||||||
let (_, receiver) = UnixStream::pair().unwrap();
|
let (_, receiver) = UnixStream::pair().unwrap();
|
||||||
@@ -1095,7 +1205,7 @@ mod tests {
|
|||||||
request_line: RequestLine::new(Method::Get, "http://foo/bar", Version::Http11),
|
request_line: RequestLine::new(Method::Get, "http://foo/bar", Version::Http11),
|
||||||
headers: Headers::new(0, true, true),
|
headers: Headers::new(0, true, true),
|
||||||
body: None,
|
body: None,
|
||||||
file: None,
|
files: Vec::new(),
|
||||||
});
|
});
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
conn.parse_headers(&mut 0, BUFFER_SIZE).unwrap_err(),
|
conn.parse_headers(&mut 0, BUFFER_SIZE).unwrap_err(),
|
||||||
@@ -1153,7 +1263,7 @@ mod tests {
|
|||||||
request_line: RequestLine::new(Method::Get, "http://foo/bar", Version::Http11),
|
request_line: RequestLine::new(Method::Get, "http://foo/bar", Version::Http11),
|
||||||
headers: Headers::new(0, true, true),
|
headers: Headers::new(0, true, true),
|
||||||
body: None,
|
body: None,
|
||||||
file: None,
|
files: Vec::new(),
|
||||||
});
|
});
|
||||||
conn.body_vec = vec![0xde, 0xad, 0xbe, 0xef];
|
conn.body_vec = vec![0xde, 0xad, 0xbe, 0xef];
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
@@ -159,8 +159,8 @@ pub struct Request {
|
|||||||
pub headers: Headers,
|
pub headers: Headers,
|
||||||
/// The body of the request.
|
/// The body of the request.
|
||||||
pub body: Option<Body>,
|
pub body: Option<Body>,
|
||||||
/// The optional file associated with the request.
|
/// The optional files associated with the request.
|
||||||
pub file: Option<File>,
|
pub files: Vec<File>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Request {
|
impl Request {
|
||||||
@@ -220,7 +220,7 @@ impl Request {
|
|||||||
request_line,
|
request_line,
|
||||||
headers: Headers::default(),
|
headers: Headers::default(),
|
||||||
body: None,
|
body: None,
|
||||||
file: None,
|
files: Vec::new(),
|
||||||
}),
|
}),
|
||||||
Some(headers_end) => {
|
Some(headers_end) => {
|
||||||
// Parse the request headers.
|
// Parse the request headers.
|
||||||
@@ -280,7 +280,7 @@ impl Request {
|
|||||||
request_line,
|
request_line,
|
||||||
headers,
|
headers,
|
||||||
body,
|
body,
|
||||||
file: None,
|
files: Vec::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// If we can't find a CR LF CR LF even though the request should have headers
|
// If we can't find a CR LF CR LF even though the request should have headers
|
||||||
@@ -449,7 +449,7 @@ mod tests {
|
|||||||
uri: Uri::new("http://localhost/home"),
|
uri: Uri::new("http://localhost/home"),
|
||||||
},
|
},
|
||||||
body: None,
|
body: None,
|
||||||
file: None,
|
files: Vec::new(),
|
||||||
headers: Headers::default(),
|
headers: Headers::default(),
|
||||||
};
|
};
|
||||||
let request_bytes = b"GET http://localhost/home HTTP/1.0\r\n\
|
let request_bytes = b"GET http://localhost/home HTTP/1.0\r\n\
|
||||||
|
|||||||
Reference in New Issue
Block a user