-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Remi Bernotavicius
committed
Mar 13, 2019
0 parents
commit 5a8be80
Showing
9 changed files
with
1,130 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
/target | ||
**/*.rs.bk | ||
Cargo.lock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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))) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"), | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
pub mod client; | ||
pub mod server; | ||
|
||
mod error; | ||
mod protocol; |
Oops, something went wrong.