summaryrefslogtreecommitdiff
path: root/src/client.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/client.rs')
-rw-r--r--src/client.rs88
1 files changed, 88 insertions, 0 deletions
diff --git a/src/client.rs b/src/client.rs
new file mode 100644
index 0000000..c5c8e18
--- /dev/null
+++ b/src/client.rs
@@ -0,0 +1,88 @@
+use httparse::EMPTY_HEADER;
+
+use tokio::io::{self, AsyncBufRead, AsyncBufReadExt, AsyncWrite, AsyncWriteExt};
+
+use crate::{request::Request, response::Response};
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+ #[error("unsupported version")]
+ Version,
+
+ #[error("io error: {0}")]
+ Io(#[from] io::Error),
+
+ #[error("invalid method: {0}")]
+ Method(#[from] http::method::InvalidMethod),
+
+ #[error("http error: {0}")]
+ Http(#[from] http::Error),
+
+ #[error("http parse error: {0}")]
+ Parse(#[from] httparse::Error),
+}
+
+pub struct Client<R, W> {
+ reader: R,
+ writer: W,
+}
+
+impl<R, W> Client<R, W>
+where
+ R: AsyncBufRead + Unpin,
+ W: AsyncWrite + Unpin,
+{
+ pub fn new(reader: R, writer: W) -> Self {
+ Self { reader, writer }
+ }
+
+ pub async fn send_response(&mut self, response: Response) -> io::Result<()> {
+ response.to_wire(&mut self.writer).await?;
+
+ self.writer.flush().await?;
+
+ Ok(())
+ }
+
+ pub async fn read_request(&mut self) -> Result<Option<Request<Vec<u8>>>, Error> {
+ let mut buf = Vec::new();
+ let mut line = Vec::new();
+
+ loop {
+ line.clear();
+
+ if self.reader.read_until(b'\n', &mut line).await? == 0 {
+ return Ok(None);
+ }
+
+ if line == b"\r\n" || line.is_empty() {
+ break;
+ }
+
+ buf.extend_from_slice(&line);
+ buf.extend_from_slice(b"\r\n");
+ }
+
+ let mut headers = [EMPTY_HEADER; 64];
+ let mut parsed = httparse::Request::new(&mut headers);
+
+ parsed.parse(&buf)?;
+
+ let mut builder = http::Request::builder();
+
+ builder = builder.method(http::Method::from_bytes(parsed.method.unwrap().as_bytes())?);
+ builder = builder.uri(parsed.path.unwrap());
+ builder = builder.version(match parsed.version.unwrap() {
+ 1 => http::Version::HTTP_11,
+ _ => return Err(Error::Version),
+ });
+
+ for header in parsed.headers {
+ builder = builder.header(header.name, header.value);
+ }
+
+ let body: Vec<u8> = Vec::new();
+
+ Ok(Some(Request::new(builder.body(body)?)))
+ }
+}