#[macro_use]
extern crate log;
extern crate http;
extern crate httparse;
extern crate num_cpus;
extern crate scoped_threadpool;
extern crate time;
pub use http::Request;
pub use http::method::Method;
pub use http::response::Builder as ResponseBuilder;
pub use http::response::{Builder, Parts, Response};
pub use http::status::{InvalidStatusCode, StatusCode};
use scoped_threadpool::Pool;
use std::env;
use std::fmt;
use std::fs::File;
use std::io::prelude::*;
use std::net::{TcpListener, TcpStream};
use std::path::{Path, PathBuf};
use std::time::Duration;
use std::borrow::Borrow;
mod error;
mod parsing;
mod request;
pub use error::Error;
pub type ResponseResult = Result<Response<Vec<u8>>, Error>;
pub type Handler =
Box<Fn(Request<Vec<u8>>, ResponseBuilder) -> ResponseResult + 'static + Send + Sync>;
pub struct Server {
handler: Handler,
timeout: Option<Duration>,
static_directory: Option<PathBuf>,
}
impl fmt::Debug for Server {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"Server {{ timeout: {:?}, static_directory: {:?} }}",
self.timeout, self.static_directory
)
}
}
impl Server {
pub fn new<H>(handler: H) -> Server
where
H: Fn(Request<Vec<u8>>, ResponseBuilder) -> ResponseResult + 'static + Send + Sync,
{
Server {
handler: Box::new(handler),
timeout: None,
static_directory: Some(PathBuf::from("public")),
}
}
pub fn with_timeout<H>(timeout: Duration, handler: H) -> Server
where
H: Fn(Request<Vec<u8>>, ResponseBuilder) -> ResponseResult + 'static + Send + Sync,
{
Server {
handler: Box::new(handler),
timeout: Some(timeout),
static_directory: Some(PathBuf::from("public")),
}
}
pub fn listen(&self, host: &str, port: &str) -> ! {
let listener =
TcpListener::bind(format!("{}:{}", host, port)).expect("Error starting the server.");
info!("Server started at http://{}:{}", host, port);
self.listen_on_socket(listener)
}
pub fn listen_on_socket(&self, listener: TcpListener) -> ! {
const READ_TIMEOUT_MS: u64 = 20;
let num_threads = self.pool_size();
let mut pool = Pool::new(num_threads);
let mut incoming = listener.incoming();
loop {
let stream = incoming.next().unwrap();
let stream = stream.expect("Error handling TCP stream.");
stream
.set_read_timeout(Some(Duration::from_millis(READ_TIMEOUT_MS)))
.expect("FATAL: Couldn't set read timeout on socket");
pool.scoped(|scope| {
scope.execute(|| {
self.handle_connection(stream)
.expect("Error handling connection.");
});
});
}
}
pub fn set_static_directory<P: Into<PathBuf>>(&mut self, path: P) {
self.static_directory = Some(path.into());
}
pub fn dont_serve_static_files(&mut self) {
self.static_directory = None;
}
fn pool_size(&self) -> u32 {
const NUM_THREADS: &str = "SIMPLESERVER_THREADS";
let logical_cores = num_cpus::get() as u32;
match env::var(NUM_THREADS) {
Ok(v) => v.parse::<u32>().unwrap_or(logical_cores),
Err(_) => logical_cores,
}
}
fn handle_connection(&self, mut stream: TcpStream) -> Result<(), Error> {
let request = match request::read(&mut stream, self.timeout) {
Err(Error::ConnectionClosed) | Err(Error::Timeout) | Err(Error::HttpParse(_)) => {
return Ok(())
}
Err(Error::RequestTooLarge) => {
let resp = Response::builder()
.status(StatusCode::PAYLOAD_TOO_LARGE)
.body("<h1>413</h1><p>Request too large!<p>".as_bytes())
.unwrap();
write_response(resp, stream)?;
return Ok(());
}
Err(e) => return Err(e),
Ok(r) => r,
};
let mut response_builder = Response::builder();
if let Some(ref static_directory) = self.static_directory {
let fs_path = request.uri().to_string();
let fs_path = PathBuf::from(&fs_path[1..]);
let traversal_attempt = fs_path.components().any(|component| match component {
std::path::Component::Normal(_) => false,
_ => true,
});
if traversal_attempt {
response_builder.status(StatusCode::NOT_FOUND);
let response = response_builder
.body("<h1>404</h1><p>Not found!<p>".as_bytes())
.unwrap();
write_response(response, stream)?;
return Ok(());
}
let fs_path = static_directory.join(fs_path);
if Path::new(&fs_path).is_file() {
let mut f = File::open(&fs_path)?;
let mut source = Vec::new();
f.read_to_end(&mut source)?;
let response = response_builder.body(source)?;
write_response(response, stream)?;
return Ok(());
}
}
match (self.handler)(request, response_builder) {
Ok(response) => Ok(write_response(response, stream)?),
Err(_) => {
let mut response_builder = Response::builder();
response_builder.status(StatusCode::INTERNAL_SERVER_ERROR);
let response = response_builder
.body("<h1>500</h1><p>Internal Server Error!<p>".as_bytes())
.unwrap();
Ok(write_response(response, stream)?)
}
}
}
}
fn write_response<T: Borrow<[u8]>, S: Write>(
response: Response<T>,
mut stream: S,
) -> Result<(), Error> {
use fmt::Write;
let (parts, body) = response.into_parts();
let body: &[u8] = body.borrow();
let mut text = format!(
"HTTP/1.1 {} {}\r\n",
parts.status.as_str(),
parts
.status
.canonical_reason()
.expect("Unsupported HTTP Status"),
);
if !parts.headers.contains_key(http::header::DATE) {
let date = time::strftime("%a, %d %b %Y %H:%M:%S GMT", &time::now_utc()).unwrap();
write!(text, "date: {}\r\n", date).unwrap();
}
if !parts.headers.contains_key(http::header::CONNECTION) {
write!(text, "connection: close\r\n").unwrap();
}
if !parts.headers.contains_key(http::header::CONTENT_LENGTH) {
write!(text, "content-length: {}\r\n", body.len()).unwrap();
}
for (k, v) in parts.headers.iter() {
write!(text, "{}: {}\r\n", k.as_str(), v.to_str().unwrap()).unwrap();
}
write!(text, "\r\n").unwrap();
stream.write(text.as_bytes())?;
stream.write(body)?;
Ok(stream.flush()?)
}
#[test]
fn test_write_response() {
let mut builder = http::response::Builder::new();
builder.status(http::StatusCode::OK);
builder.header(http::header::DATE, "Thu, 01 Jan 1970 00:00:00 GMT");
builder.header(http::header::CONTENT_TYPE, "text/plain".as_bytes());
let mut output = vec![];
let _ = write_response(builder.body("Hello rust".as_bytes()).unwrap(), &mut output).unwrap();
let expected = b"HTTP/1.1 200 OK\r\n\
connection: close\r\n\
content-length: 10\r\n\
date: Thu, 01 Jan 1970 00:00:00 GMT\r\n\
content-type: text/plain\r\n\
\r\n\
Hello rust";
assert_eq!(&expected[..], &output[..]);
}
#[test]
fn test_write_response_no_headers() {
let mut builder = http::response::Builder::new();
builder.header(http::header::DATE, "Thu, 01 Jan 1970 00:00:00 GMT");
builder.status(http::StatusCode::OK);
let mut output = vec![];
let _ = write_response(builder.body("Hello rust".as_bytes()).unwrap(), &mut output).unwrap();
let expected = b"HTTP/1.1 200 OK\r\n\
connection: close\r\n\
content-length: 10\r\n\
date: Thu, 01 Jan 1970 00:00:00 GMT\r\n\
\r\n\
Hello rust";
assert_eq!(&expected[..], &output[..]);
}