Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Use mio instead of tiny-http #144

Open
wants to merge 51 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
8760e39
Add new dependencies
tomaka Aug 7, 2017
f0a4be9
Move Server to its own module
tomaka Aug 7, 2017
d111935
First draft for using mio
tomaka Aug 7, 2017
beae922
Parse request headers
tomaka Aug 7, 2017
36588ba
Remove old code
tomaka Aug 7, 2017
77dd08f
Correctly send back response body
tomaka Aug 7, 2017
ec63056
Fix when multiple requests in a row
tomaka Aug 7, 2017
0848440
Make socket_handler extensible for later
tomaka Aug 8, 2017
bb9b783
Correctly print status code and headers
tomaka Aug 8, 2017
4ba773a
Remove the debug sleep
tomaka Aug 8, 2017
fa5d31e
Minor fixes
tomaka Aug 8, 2017
ab9075a
Correct value for Request::https
tomaka Aug 8, 2017
66baf16
Remove debugging printlns
tomaka Aug 8, 2017
f442649
Fix panic when stream errors
tomaka Aug 8, 2017
66bfe2a
Fix panic when slab doesn't contain entry
tomaka Aug 8, 2017
2ab9ef2
Add a tasks pool
tomaka Aug 8, 2017
761f048
Modernize task pool
tomaka Aug 8, 2017
685af11
Fix pool being dropped after a connection is closed
tomaka Aug 8, 2017
e83f1d5
Put the method in an ArrayString
tomaka Aug 8, 2017
1056a5f
Preallocate memory for Update
tomaka Aug 8, 2017
4a77d84
Put new_data_start in http1
tomaka Aug 8, 2017
d0167d2
Avoid allocating after receiving status line and headers
tomaka Aug 8, 2017
b6d2c83
Some doc for Update
tomaka Aug 8, 2017
b1bf27a
Minor todo
tomaka Aug 8, 2017
92bf81f
Put update in a trait
tomaka Aug 8, 2017
d7398c8
Draft for Rustls support
tomaka Aug 8, 2017
9ada898
Rework Update API
tomaka Aug 8, 2017
3f69583
Better handler dispatching
tomaka Aug 8, 2017
467259f
Minor code style
tomaka Aug 8, 2017
f17f50f
Streaming data send back
tomaka Aug 8, 2017
814b053
More documentation
tomaka Aug 8, 2017
c8a6123
Draft for handling Connection: close
tomaka Aug 8, 2017
a77c316
Drop the Mutex earlier
tomaka Aug 8, 2017
6809a6f
Minor optimization in data reception
tomaka Aug 8, 2017
bf7ac3a
Remove TODO
tomaka Aug 8, 2017
71532de
Optimize writing
tomaka Aug 8, 2017
5b12ee2
Don't flush when writing
tomaka Aug 8, 2017
ae862ca
Add write_flush_suggested
tomaka Aug 8, 2017
4126b48
Basic client input data handling
tomaka Aug 9, 2017
ad51efe
More work on HTTPS
tomaka Aug 9, 2017
190adfa
Rustls now working properly
tomaka Aug 9, 2017
1c032aa
Better Rustls error handling
tomaka Aug 9, 2017
e9059c8
Add support for chunked transfer encoding decoding
tomaka Aug 9, 2017
42c7794
Bugfix and test for chunked decoding
tomaka Aug 9, 2017
025c7cb
Use 17 bytes for the method
tomaka Aug 10, 2017
98f4bac
Handle errors in request line
tomaka Aug 10, 2017
e81bfe3
Handle buffer too large errors
tomaka Aug 10, 2017
a8ad92d
Fix wrong handling of empty requests
tomaka Aug 10, 2017
ab22b0a
Add support for chunked encoding
tomaka Aug 10, 2017
2a705f5
Remove unused EndOfStream
tomaka Aug 10, 2017
e958d66
Merge remote-tracking branch 'origin/master' into async
tomaka Oct 31, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,24 @@ brotli = ["brotli2"]
gzip = ["flate2"]

[dependencies]
arrayvec = "0.3.23"
atoi = "0.2.2"
base64 = "0.7.0"
brotli2 = { version = "0.2.1", optional = true }
chrono = "0.2.0"
crossbeam = "0.3.0"
filetime = "0.1.10"
flate2 = { version = "0.2.14", optional = true }
httparse = "1.2.3"
itoa = "0.3"
mio = "0.6.10"
multipart = { version = "0.5.1", default-features = false, features = ["server"] }
num_cpus = "1.6.2"
rand = "0.3.11"
rustc-serialize = "0.3"
rustls = "0.9.0"
sha1 = "0.2.0"
slab = "0.4.0"
term = "0.2"
time = "0.1.31"
tiny_http = "0.5.6"
Expand Down
193 changes: 21 additions & 172 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,26 @@

#![deny(unsafe_code)]

extern crate arrayvec;
extern crate atoi;
extern crate base64;
#[cfg(feature = "brotli2")]
extern crate brotli2;
extern crate chrono;
extern crate crossbeam;
extern crate filetime;
#[cfg(feature = "flate2")]
extern crate flate2;
extern crate httparse;
extern crate itoa;
extern crate mio;
extern crate multipart;
extern crate num_cpus;
extern crate rand;
extern crate rustc_serialize;
extern crate rustls;
extern crate sha1;
extern crate slab;
extern crate time;
extern crate tiny_http;
extern crate url;
Expand All @@ -71,21 +80,20 @@ pub use assets::extension_to_mime;
pub use assets::match_assets;
pub use log::log;
pub use response::{Response, ResponseBody};
pub use server::Server;
pub use server::SslConfig;
pub use tiny_http::ReadWrite;

use std::error::Error;
use arrayvec::ArrayString;
use std::io::Cursor;
use std::io::Result as IoResult;
use std::io::Read;
use std::marker::PhantomData;
use std::net::SocketAddr;
use std::net::ToSocketAddrs;
use std::panic;
use std::panic::AssertUnwindSafe;
use std::slice::Iter as SliceIter;
use std::sync::Arc;
use std::sync::Mutex;
use std::thread;
use std::ascii::AsciiExt;
use std::fmt;

Expand All @@ -101,6 +109,8 @@ mod find_route;
mod log;
mod response;
mod router;
mod server;
mod socket_handler;
#[doc(hidden)]
pub mod try_or_400;

Expand Down Expand Up @@ -208,169 +218,6 @@ pub fn start_server<A, F>(addr: A, handler: F) -> !
panic!("The server socket closed unexpectedly")
}

/// A listening server.
///
/// This struct is the more manual server creation API of rouille and can be used as an alternative
/// to the `start_server` function.
///
/// The `start_server` function is just a shortcut for `Server::new` followed with `run`. See the
/// documentation of the `start_server` function for more details about the handler.
///
/// # Example
///
/// ```no_run
/// use rouille::Server;
/// use rouille::Response;
///
/// let server = Server::new("localhost:0", |request| {
/// Response::text("hello world")
/// }).unwrap();
/// println!("Listening on {:?}", server.server_addr());
/// server.run();
/// ```
pub struct Server<F> {
server: tiny_http::Server,
handler: Arc<AssertUnwindSafe<F>>,
}

impl<F> Server<F> where F: Send + Sync + 'static + Fn(&Request) -> Response {
/// Builds a new `Server` object.
///
/// After this function returns, the HTTP server is listening.
///
/// Returns an error if there was an error while creating the listening socket, for example if
/// the port is already in use.
pub fn new<A>(addr: A, handler: F) -> Result<Server<F>, Box<Error + Send + Sync>>
where A: ToSocketAddrs
{
let server = try!(tiny_http::Server::http(addr));

Ok(Server {
server: server,
handler: Arc::new(AssertUnwindSafe(handler)), // TODO: using AssertUnwindSafe here is wrong, but unwind safety has some usability problems in Rust in general
})
}

/// Returns the address of the listening socket.
#[inline]
pub fn server_addr(&self) -> SocketAddr {
self.server.server_addr()
}

/// Runs the server forever, or until the listening socket is somehow force-closed by the
/// operating system.
#[inline]
pub fn run(self) {
for request in self.server.incoming_requests() {
self.process(request);
}
}

/// Processes all the client requests waiting to be processed, then returns.
///
/// This function executes very quickly, as each client requests that needs to be processed
/// is processed in a separate thread.
#[inline]
pub fn poll(&self) {
while let Ok(Some(request)) = self.server.try_recv() {
self.process(request);
}
}

// Internal function, called when we got a request from tiny-http that needs to be processed.
fn process(&self, request: tiny_http::Request) {
// We spawn a thread so that requests are processed in parallel.
let handler = self.handler.clone();
thread::spawn(move || {
// Small helper struct that makes it possible to put
// a `tiny_http::Request` inside a `Box<Read>`.
struct RequestRead(Arc<Mutex<Option<tiny_http::Request>>>);
impl Read for RequestRead {
#[inline]
fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> {
self.0.lock().unwrap().as_mut().unwrap().as_reader().read(buf)
}
}

// Building the `Request` object.
let tiny_http_request;
let rouille_request = {
let url = request.url().to_owned();
let method = request.method().as_str().to_owned();
let headers = request.headers().iter().map(|h| (h.field.to_string(), h.value.clone().into())).collect();
let remote_addr = request.remote_addr().clone();

tiny_http_request = Arc::new(Mutex::new(Some(request)));

Request {
url: url,
method: method,
headers: headers,
https: false,
data: Arc::new(Mutex::new(Some(Box::new(RequestRead(tiny_http_request.clone())) as Box<_>))),
remote_addr: remote_addr,
}
};

// Calling the handler ; this most likely takes a lot of time.
// If the handler panics, we build a dummy response.
let mut rouille_response = {
// We don't use the `rouille_request` anymore after the panic, so it's ok to assert
// it's unwind safe.
let rouille_request = AssertUnwindSafe(rouille_request);
let res = panic::catch_unwind(move || {
let rouille_request = rouille_request;
handler(&rouille_request)
});

match res {
Ok(r) => r,
Err(_) => {
Response::html("<h1>Internal Server Error</h1>\
<p>An internal error has occurred on the server.</p>")
.with_status_code(500)
}
}
};

// writing the response
let (res_data, res_len) = rouille_response.data.into_reader_and_size();
let mut response = tiny_http::Response::empty(rouille_response.status_code)
.with_data(res_data, res_len);

let mut upgrade_header = "".into();

for (key, value) in rouille_response.headers {
if key.eq_ignore_ascii_case("Content-Length") {
continue;
}

if key.eq_ignore_ascii_case("Upgrade") {
upgrade_header = value;
continue;
}

if let Ok(header) = tiny_http::Header::from_bytes(key.as_bytes(), value.as_bytes()) {
response.add_header(header);
} else {
// TODO: ?
}
}

if let Some(ref mut upgrade) = rouille_response.upgrade {
let trq = tiny_http_request.lock().unwrap().take().unwrap();
let socket = trq.upgrade(&upgrade_header, response);
upgrade.build(socket);

} else {
// We don't really care if we fail to send the response to the client, as there's
// nothing we can do anyway.
let _ = tiny_http_request.lock().unwrap().take().unwrap().respond(response);
}
});
}
}

/// Trait for objects that can take ownership of a raw connection to the client data.
///
/// The purpose of this trait is to be used with the `Connection: Upgrade` header, hence its name.
Expand All @@ -384,7 +231,9 @@ pub trait Upgrade {
/// This can be either a real request (received by the HTTP server) or a mock object created with
/// one of the `fake_*` constructors.
pub struct Request {
method: String,
// The method (`GET`, `POST`, ..). The longest registered method know to the author is
// `UPDATEREDIRECTREF` and is 17 bytes long.
method: ArrayString<[u8; 17]>,
url: String,
headers: Vec<(String, String)>,
https: bool,
Expand Down Expand Up @@ -414,7 +263,7 @@ impl Request {
{
Request {
url: url.into(),
method: method.into(),
method: ArrayString::from(&method.into()).expect("Method too long"),
https: false,
data: Arc::new(Mutex::new(Some(Box::new(Cursor::new(data)) as Box<_>))),
headers: headers,
Expand All @@ -429,7 +278,7 @@ impl Request {
{
Request {
url: url.into(),
method: method.into(),
method: ArrayString::from(&method.into()).expect("Method too long"),
https: false,
data: Arc::new(Mutex::new(Some(Box::new(Cursor::new(data)) as Box<_>))),
headers: headers,
Expand All @@ -446,7 +295,7 @@ impl Request {
{
Request {
url: url.into(),
method: method.into(),
method: ArrayString::from(&method.into()).expect("Method too long"),
https: true,
data: Arc::new(Mutex::new(Some(Box::new(Cursor::new(data)) as Box<_>))),
headers: headers,
Expand All @@ -461,7 +310,7 @@ impl Request {
{
Request {
url: url.into(),
method: method.into(),
method: ArrayString::from(&method.into()).expect("Method too long"),
https: true,
data: Arc::new(Mutex::new(Some(Box::new(Cursor::new(data)) as Box<_>))),
headers: headers,
Expand Down
Loading