simple_server/
lib.rs

1//! A simple webserver.
2//!
3//! The `simple-server` crate is designed to give you the tools to to build
4//! an HTTP server, based around the http crate, blocking I/O, and a
5//! threadpool.
6//!
7//! We call it 'simple' want to keep the code small, and easy to
8//! understand. This is why we're only using blocking I/O. Depending on
9//! your needs, you may or may not want to choose another server.
10//! However, just the simple stuff is often enough for many projects.
11//!
12//! # Examples
13//!
14//! At its core, `simple-server` contains a `Server`. The `Server` is
15//! passed a handler upon creation, and the `listen` method is used
16//! to start handling connections.
17//!
18//! The other types are from the `http` crate, and give you the ability
19//! to work with various aspects of HTTP. The `Request`, `Response`, and
20//! `ResponseBuilder` types are used by the handler you give to `Server`,
21//! for example.
22//!
23//! To see examples of this crate in use, please consult the `examples`
24//! directory.
25
26#[macro_use]
27extern crate log;
28
29extern crate http;
30extern crate httparse;
31extern crate num_cpus;
32extern crate scoped_threadpool;
33extern crate time;
34
35pub use http::Request;
36pub use http::method::Method;
37pub use http::response::Builder as ResponseBuilder;
38pub use http::response::{Builder, Parts, Response};
39pub use http::status::{InvalidStatusCode, StatusCode};
40
41use scoped_threadpool::Pool;
42
43use std::env;
44use std::fmt;
45use std::fs::File;
46use std::io::prelude::*;
47use std::net::{TcpListener, TcpStream};
48use std::path::{Path, PathBuf};
49use std::time::Duration;
50
51use std::borrow::Borrow;
52
53mod error;
54mod parsing;
55mod request;
56
57pub use error::Error;
58
59pub type ResponseResult = Result<Response<Vec<u8>>, Error>;
60
61pub type Handler =
62    Box<Fn(Request<Vec<u8>>, ResponseBuilder) -> ResponseResult + 'static + Send + Sync>;
63
64/// A web server.
65///
66/// This is the core type of this crate, and is used to create a new
67/// server and listen for connections.
68pub struct Server {
69    handler: Handler,
70    timeout: Option<Duration>,
71    static_directory: Option<PathBuf>,
72}
73
74impl fmt::Debug for Server {
75    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
76        write!(
77            f,
78            "Server {{ timeout: {:?}, static_directory: {:?} }}",
79            self.timeout, self.static_directory
80        )
81    }
82}
83
84impl Server {
85    /// Constructs a new server with the given handler.
86    ///
87    /// The handler function is called on all requests.
88    ///
89    /// # Errors
90    ///
91    /// The handler function returns a `Result` so that you may use `?` to
92    /// handle errors. If a handler returns an `Err`, a 500 will be shown.
93    ///
94    /// If you'd like behavior other than that, return an `Ok(Response)` with
95    /// the proper error code. In other words, this behavior is to gracefully
96    /// handle errors you don't care about, not for properly handling
97    /// non-`HTTP 200` responses.
98    ///
99    /// # Examples
100    ///
101    /// ```
102    /// extern crate simple_server;
103    ///
104    /// use simple_server::Server;
105    ///
106    /// fn main() {
107    ///     let server = Server::new(|request, mut response| {
108    ///         Ok(response.body("Hello, world!".as_bytes().to_vec())?)
109    ///     });
110    /// }
111    /// ```
112    pub fn new<H>(handler: H) -> Server
113    where
114        H: Fn(Request<Vec<u8>>, ResponseBuilder) -> ResponseResult + 'static + Send + Sync,
115    {
116        Server {
117            handler: Box::new(handler),
118            timeout: None,
119            static_directory: Some(PathBuf::from("public")),
120        }
121    }
122
123    /// Constructs a new server with the given handler and the specified request
124    /// timeout.
125    ///
126    /// The handler function is called on all requests.
127    ///
128    /// # Errors
129    ///
130    /// The handler function returns a `Result` so that you may use `?` to
131    /// handle errors. If a handler returns an `Err`, a 500 will be shown.
132    ///
133    /// If you'd like behavior other than that, return an `Ok(Response)` with
134    /// the proper error code. In other words, this behavior is to gracefully
135    /// handle errors you don't care about, not for properly handling
136    /// non-`HTTP 200` responses.
137    ///
138    /// # Examples
139    ///
140    /// ```
141    /// extern crate simple_server;
142    ///
143    /// use std::time::Duration;
144    /// use simple_server::Server;
145    ///
146    /// fn main() {
147    ///     let server = Server::with_timeout(Duration::from_secs(5), |request, mut response| {
148    ///         Ok(response.body("Hello, world!".as_bytes().to_vec())?)
149    ///     });
150    /// }
151    /// ```
152    pub fn with_timeout<H>(timeout: Duration, handler: H) -> Server
153    where
154        H: Fn(Request<Vec<u8>>, ResponseBuilder) -> ResponseResult + 'static + Send + Sync,
155    {
156        Server {
157            handler: Box::new(handler),
158            timeout: Some(timeout),
159            static_directory: Some(PathBuf::from("public")),
160        }
161    }
162
163    /// Tells the server to listen on a specified host and port.
164    ///
165    /// A threadpool is created, and used to handle connections.
166    /// The pool size is four threads.
167    ///
168    /// This method blocks forever.
169    ///
170    /// The `listen` method will also serve static files. By default, that
171    /// directory is "public" in the same directory as where it's run. If you'd like to change
172    /// this default, please see the `set_static_directory` method.
173    ///
174    /// If someone tries a path directory traversal attack, this will return a
175    /// `404`. Please note that [this is a best effort][best effort] at the
176    /// moment.
177    ///
178    /// [best effort]: https://github.com/steveklabnik/simple-server/issues/54
179    ///
180    /// # Panics
181    ///
182    /// There are several circumstances in which `listen` can currently panic:
183    ///
184    /// * If there's an error [constructing a TcpListener][constructing], generally if the port
185    ///    or host is incorrect. See `TcpListener`'s docs for more.
186    /// * If the connection fails, see [`incoming`'s docs] for more.
187    ///
188    /// Finally, if reading from the stream fails. Timeouts and connection closes
189    /// are handled, other errors may result in a panic. This will only take down
190    /// one of the threads in the threadpool, rather than the whole server.
191    ///
192    /// [constructing]: https://doc.rust-lang.org/std/net/struct.TcpListener.html#method.bind
193    /// [`incoming`'s docs]: https://doc.rust-lang.org/std/net/struct.TcpListener.html#method.incoming
194    ///
195    /// # Examples
196    ///
197    /// ```no_run
198    /// extern crate simple_server;
199    ///
200    /// use simple_server::Server;
201    ///
202    /// fn main() {
203    ///     let server = Server::new(|request, mut response| {
204    ///         Ok(response.body("Hello, world!".as_bytes().to_vec())?)
205    ///     });
206    ///
207    ///     server.listen("127.0.0.1", "7979");
208    /// }
209    /// ```
210    pub fn listen(&self, host: &str, port: &str) -> ! {
211        let listener =
212            TcpListener::bind(format!("{}:{}", host, port)).expect("Error starting the server.");
213
214        info!("Server started at http://{}:{}", host, port);
215
216        self.listen_on_socket(listener)
217    }
218
219    /// Tells the server to listen on a provided `TcpListener`.
220    ///
221    /// A threadpool is created, and used to handle connections.
222    /// The pool size is four threads.
223    ///
224    /// This method blocks forever.
225    ///
226    /// This method will also serve static files out of a `public` directory
227    /// in the same directory as where it's run. If someone tries a path
228    /// directory traversal attack, this will return a `404`.
229    ///
230    /// # Examples
231    ///
232    /// ```no_run
233    /// extern crate simple_server;
234    ///
235    /// use simple_server::Server;
236    /// use std::net::TcpListener;
237    ///
238    /// fn main() {
239    ///     let listener = TcpListener::bind(("127.0.0.1", 7979))
240    ///         .expect("Error starting the server.");
241    ///
242    ///     let server = Server::new(|request, mut response| {
243    ///         Ok(response.body("Hello, world!".as_bytes().to_vec())?)
244    ///     });
245    ///
246    ///     server.listen_on_socket(listener);
247    /// }
248    /// ```
249    pub fn listen_on_socket(&self, listener: TcpListener) -> ! {
250        const READ_TIMEOUT_MS: u64 = 20;
251        let num_threads = self.pool_size();
252        let mut pool = Pool::new(num_threads);
253        let mut incoming = listener.incoming();
254
255        loop {
256            // Incoming is an endless iterator, so it's okay to unwrap on it.
257            let stream = incoming.next().unwrap();
258            let stream = stream.expect("Error handling TCP stream.");
259
260            stream
261                .set_read_timeout(Some(Duration::from_millis(READ_TIMEOUT_MS)))
262                .expect("FATAL: Couldn't set read timeout on socket");
263
264            pool.scoped(|scope| {
265                scope.execute(|| {
266                    self.handle_connection(stream)
267                        .expect("Error handling connection.");
268                });
269            });
270        }
271    }
272
273    /// Sets the proper directory for serving static files.
274    ///
275    /// By default, the server will serve static files inside a `public`
276    /// directory. This method lets you set a path to whatever location
277    /// you'd like.
278    ///
279    /// # Examples
280    ///
281    /// ```no_run
282    /// extern crate simple_server;
283    ///
284    /// use simple_server::Server;
285    ///
286    /// fn main() {
287    ///     let mut server = Server::new(|request, mut response| {
288    ///         Ok(response.body("Hello, world!".as_bytes().to_vec())?)
289    ///     });
290    ///
291    ///     server.set_static_directory("/var/www/");
292    ///
293    ///     server.listen("127.0.0.1", "7979");
294    /// }
295    /// ```
296    pub fn set_static_directory<P: Into<PathBuf>>(&mut self, path: P) {
297        self.static_directory = Some(path.into());
298    }
299
300    /// Disables serving static files.
301    ///
302    /// By default, the server will serve static files inside a `public`
303    /// directory, or the directory set by `set_static_directory`. This
304    /// method lets you disable this.
305    ///
306    /// It can be re-enabled by a subsequent call to `set_static_directory`.
307    ///
308    /// # Examples
309    ///
310    /// ```no_run
311    /// extern crate simple_server;
312    ///
313    /// use simple_server::Server;
314    ///
315    /// fn main() {
316    ///     let mut server = Server::new(|request, mut response| {
317    ///         Ok(response.body("Hello, world!".as_bytes().to_vec())?)
318    ///     });
319    ///
320    ///     server.dont_serve_static_files();
321    ///
322    ///     server.listen("127.0.0.1", "7979");
323    /// }
324    /// ```
325    pub fn dont_serve_static_files(&mut self) {
326        self.static_directory = None;
327    }
328
329    // Try and fetch the environment variable SIMPLESERVER_THREADS and parse it as a u32.
330    // If this fails we fall back to using the num_cpus crate.
331    fn pool_size(&self) -> u32 {
332        const NUM_THREADS: &str = "SIMPLESERVER_THREADS";
333        let logical_cores = num_cpus::get() as u32;
334
335        match env::var(NUM_THREADS) {
336            Ok(v) => v.parse::<u32>().unwrap_or(logical_cores),
337            Err(_) => logical_cores,
338        }
339    }
340
341    fn handle_connection(&self, mut stream: TcpStream) -> Result<(), Error> {
342        let request = match request::read(&mut stream, self.timeout) {
343            Err(Error::ConnectionClosed) | Err(Error::Timeout) | Err(Error::HttpParse(_)) => {
344                return Ok(())
345            }
346
347            Err(Error::RequestTooLarge) => {
348                let resp = Response::builder()
349                    .status(StatusCode::PAYLOAD_TOO_LARGE)
350                    .body("<h1>413</h1><p>Request too large!<p>".as_bytes())
351                    .unwrap();
352                write_response(resp, stream)?;
353                return Ok(());
354            }
355
356            Err(e) => return Err(e),
357
358            Ok(r) => r,
359        };
360
361        let mut response_builder = Response::builder();
362
363        // first, we serve static files
364        if let Some(ref static_directory) = self.static_directory {
365            let fs_path = request.uri().to_string();
366
367            // the uri always includes a leading /, which means that join will over-write the static directory...
368            let fs_path = PathBuf::from(&fs_path[1..]);
369
370            // ... you trying to do something bad?
371            let traversal_attempt = fs_path.components().any(|component| match component {
372                std::path::Component::Normal(_) => false,
373                _ => true,
374            });
375
376            if traversal_attempt {
377                // GET OUT
378                response_builder.status(StatusCode::NOT_FOUND);
379
380                let response = response_builder
381                    .body("<h1>404</h1><p>Not found!<p>".as_bytes())
382                    .unwrap();
383
384                write_response(response, stream)?;
385                return Ok(());
386            }
387
388            let fs_path = static_directory.join(fs_path);
389
390            if Path::new(&fs_path).is_file() {
391                let mut f = File::open(&fs_path)?;
392
393                let mut source = Vec::new();
394
395                f.read_to_end(&mut source)?;
396
397                let response = response_builder.body(source)?;
398
399                write_response(response, stream)?;
400                return Ok(());
401            }
402        }
403
404        match (self.handler)(request, response_builder) {
405            Ok(response) => Ok(write_response(response, stream)?),
406            Err(_) => {
407                let mut response_builder = Response::builder();
408                response_builder.status(StatusCode::INTERNAL_SERVER_ERROR);
409
410                let response = response_builder
411                    .body("<h1>500</h1><p>Internal Server Error!<p>".as_bytes())
412                    .unwrap();
413
414                Ok(write_response(response, stream)?)
415            }
416        }
417    }
418}
419
420fn write_response<T: Borrow<[u8]>, S: Write>(
421    response: Response<T>,
422    mut stream: S,
423) -> Result<(), Error> {
424    use fmt::Write;
425
426    let (parts, body) = response.into_parts();
427    let body: &[u8] = body.borrow();
428
429    let mut text = format!(
430        "HTTP/1.1 {} {}\r\n",
431        parts.status.as_str(),
432        parts
433            .status
434            .canonical_reason()
435            .expect("Unsupported HTTP Status"),
436    );
437
438    if !parts.headers.contains_key(http::header::DATE) {
439        let date = time::strftime("%a, %d %b %Y %H:%M:%S GMT", &time::now_utc()).unwrap();
440        write!(text, "date: {}\r\n", date).unwrap();
441    }
442    if !parts.headers.contains_key(http::header::CONNECTION) {
443        write!(text, "connection: close\r\n").unwrap();
444    }
445    if !parts.headers.contains_key(http::header::CONTENT_LENGTH) {
446        write!(text, "content-length: {}\r\n", body.len()).unwrap();
447    }
448    for (k, v) in parts.headers.iter() {
449        write!(text, "{}: {}\r\n", k.as_str(), v.to_str().unwrap()).unwrap();
450    }
451
452    write!(text, "\r\n").unwrap();
453
454    stream.write(text.as_bytes())?;
455    stream.write(body)?;
456    Ok(stream.flush()?)
457}
458
459#[test]
460fn test_write_response() {
461    let mut builder = http::response::Builder::new();
462    builder.status(http::StatusCode::OK);
463    builder.header(http::header::DATE, "Thu, 01 Jan 1970 00:00:00 GMT");
464    builder.header(http::header::CONTENT_TYPE, "text/plain".as_bytes());
465
466    let mut output = vec![];
467    let _ = write_response(builder.body("Hello rust".as_bytes()).unwrap(), &mut output).unwrap();
468    let expected = b"HTTP/1.1 200 OK\r\n\
469        connection: close\r\n\
470        content-length: 10\r\n\
471        date: Thu, 01 Jan 1970 00:00:00 GMT\r\n\
472        content-type: text/plain\r\n\
473        \r\n\
474        Hello rust";
475    assert_eq!(&expected[..], &output[..]);
476}
477
478#[test]
479fn test_write_response_no_headers() {
480    let mut builder = http::response::Builder::new();
481    // Well, no headers besides the date ;) Otherwise, we wouldn't know
482    // what `expected` should be.
483    builder.header(http::header::DATE, "Thu, 01 Jan 1970 00:00:00 GMT");
484    builder.status(http::StatusCode::OK);
485
486    let mut output = vec![];
487    let _ = write_response(builder.body("Hello rust".as_bytes()).unwrap(), &mut output).unwrap();
488    let expected = b"HTTP/1.1 200 OK\r\n\
489        connection: close\r\n\
490        content-length: 10\r\n\
491        date: Thu, 01 Jan 1970 00:00:00 GMT\r\n\
492        \r\n\
493        Hello rust";
494    assert_eq!(&expected[..], &output[..]);
495}