Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Remi Bernotavicius committed Mar 13, 2019
0 parents commit 5a8be80
Show file tree
Hide file tree
Showing 9 changed files with 1,130 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/target
**/*.rs.bk
Cargo.lock
7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "http_io"
version = "0.1.0"
authors = ["Remi Bernotavicius <[email protected]>"]
edition = "2018"

[dependencies]
Empty file added README.md
Empty file.
167 changes: 167 additions & 0 deletions src/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
use crate::error::Result;
use crate::protocol::{CrLfStream, HttpMethod, HttpRequest, HttpResponse};
use std::io;
use std::io::Read;

pub struct HttpClient<S: io::Read + io::Write> {
socket: S,
}

struct HttpBodyChunk<S: io::Read> {
inner: io::Take<HttpReadTilCloseBody<S>>,
}

pub struct HttpChunkedBody<S: io::Read> {
stream: Option<HttpReadTilCloseBody<S>>,
chunk: Option<HttpBodyChunk<S>>,
}

impl<S: io::Read> HttpChunkedBody<S> {
fn new(stream: HttpReadTilCloseBody<S>) -> Self {
HttpChunkedBody {
stream: Some(stream),
chunk: None,
}
}
}

impl<S: io::Read> HttpBodyChunk<S> {
fn new(mut stream: HttpReadTilCloseBody<S>) -> Result<Option<Self>> {
let mut ts = CrLfStream::new(&mut stream);
let size_str = ts.expect_next()?;
drop(ts);
let size = u64::from_str_radix(&size_str, 16)?;
Ok(if size == 0 {
None
} else {
Some(HttpBodyChunk {
inner: stream.take(size),
})
})
}

fn into_inner(self) -> HttpReadTilCloseBody<S> {
self.inner.into_inner()
}
}

impl<S: io::Read> io::Read for HttpBodyChunk<S> {
fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
self.inner.read(buffer)
}
}

impl<S: io::Read> io::Read for HttpChunkedBody<S> {
fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
if let Some(mut chunk) = self.chunk.take() {
let read = chunk.read(buffer)?;
if read == 0 {
let mut stream = chunk.into_inner();
let mut b = [0; 2];
stream.read(&mut b)?;
self.stream = Some(stream);
self.read(buffer)
} else {
self.chunk.replace(chunk);
Ok(read)
}
} else if let Some(stream) = self.stream.take() {
let new_chunk = HttpBodyChunk::new(stream)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))?;
match new_chunk {
Some(chunk) => {
self.chunk = Some(chunk);
self.read(buffer)
}
None => Ok(0),
}
} else {
Ok(0)
}
}
}

#[cfg(test)]
mod chunked_encoding_tests {
use super::HttpChunkedBody;
use crate::error::Result;
use std::io;
use std::io::Read;

fn chunk_test(i: &'static str) -> Result<String> {
let input = io::BufReader::new(io::Cursor::new(i));
let mut body = HttpChunkedBody::new(input);

let mut output = String::new();
body.read_to_string(&mut output)?;
Ok(output)
}

#[test]
fn simple_chunk() {
assert_eq!(
&chunk_test("a\r\n0123456789\r\n0\r\n").unwrap(),
"0123456789"
);
}

#[test]
fn chunk_missing_last_chunk() {
assert!(chunk_test("a\r\n0123456789\r\n").is_err());
}

#[test]
fn chunk_short_read() {
assert!(chunk_test("a\r\n012345678").is_err());
}
}

type HttpReadTilCloseBody<S> = io::BufReader<S>;
type HttpLimitedBody<S> = io::Take<HttpReadTilCloseBody<S>>;

pub enum HttpBody<S: io::Read> {
Chunked(HttpChunkedBody<S>),
Limited(HttpLimitedBody<S>),
ReadTilClose(HttpReadTilCloseBody<S>),
}

impl<S: io::Read> io::Read for HttpBody<S> {
fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
match self {
HttpBody::Chunked(i) => i.read(buffer),
HttpBody::Limited(i) => i.read(buffer),
HttpBody::ReadTilClose(i) => i.read(buffer),
}
}
}

impl<S: io::Read + io::Write> HttpClient<S> {
pub fn new(socket: S) -> HttpClient<S> {
HttpClient { socket }
}

pub fn get(mut self, host: String, uri: String) -> Result<(HttpResponse, HttpBody<S>)> {
let mut request = HttpRequest::new(HttpMethod::Get, uri);
request.add_header("Host", host);
request.add_header("User-Agent", "fuck/bitches");
request.add_header("Accept", "*/*");
write!(self.socket, "{}", request)?;

let mut stream = CrLfStream::new(&mut self.socket);
let response = HttpResponse::deserialize(&mut stream)?;
drop(stream);

let body = io::BufReader::new(self.socket);

let encoding = response.get_header("Transfer-Encoding");
let content_length = response.get_header("Content-Length").map(str::parse);

if encoding == Some("chunked") {
Ok((response, HttpBody::Chunked(HttpChunkedBody::new(body))))
} else if let Some(length) = content_length {
Ok((response, HttpBody::Limited(body.take(length?))))
} else {
Ok((response, HttpBody::ReadTilClose(body)))
}
}
}
53 changes: 53 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use std::convert;
use std::error;
use std::fmt;
use std::io;
use std::num;
use std::str;

#[derive(Debug)]
pub enum Error {
ParseError(String),
ParseIntError(num::ParseIntError),
Utf8Error(str::Utf8Error),
IoError(io::Error),
UnexpectedEof(String),
}

pub type Result<R> = std::result::Result<R, Error>;

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}

impl error::Error for Error {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
Error::ParseError(_) => None,
Error::IoError(e) => Some(e),
Error::Utf8Error(e) => Some(e),
Error::ParseIntError(e) => Some(e),
Error::UnexpectedEof(_) => None,
}
}
}

impl convert::From<str::Utf8Error> for Error {
fn from(e: str::Utf8Error) -> Self {
Error::Utf8Error(e)
}
}

impl convert::From<io::Error> for Error {
fn from(e: io::Error) -> Self {
Error::IoError(e)
}
}

impl convert::From<num::ParseIntError> for Error {
fn from(e: num::ParseIntError) -> Self {
Error::ParseIntError(e)
}
}
43 changes: 43 additions & 0 deletions src/examples/client_server.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use std::convert;
use std::io::{self, Read, Write};
use std::net;

mod client;
mod error;
mod protocol;
mod server;

use self::client::HttpClient;
use self::server::HttpServer;

fn client_main(mut args: std::env::Args) {
let host = args.next().unwrap_or("www.google.com".into());

let s = net::TcpStream::connect((host.as_ref(), 80)).unwrap();
let h = HttpClient::new(s);
let (response, mut body_stream) = h.get(host, "/".into()).unwrap();

let mut body = Vec::new();
body_stream.read_to_end(&mut body).unwrap();

println!("{:#?}", response);
io::stdout().write(&body).unwrap();
}

fn server_main(_args: std::env::Args) {
let socket = net::TcpListener::bind("127.0.0.1:8080").unwrap();
let server = HttpServer::new(socket);
println!("Server started on port 8080");
server.serve_forever();
}

fn main() {
let mut args = std::env::args();
args.next();
let command = args.next();
match command.as_ref().map(convert::AsRef::as_ref) {
Some("client") => client_main(args),
Some("server") => server_main(args),
_ => panic!("Bad arguments"),
}
}
5 changes: 5 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub mod client;
pub mod server;

mod error;
mod protocol;
Loading

0 comments on commit 5a8be80

Please sign in to comment.