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}