tiny_http/
lib.rs

1//! # Simple usage
2//!
3//! ## Creating the server
4//!
5//! The easiest way to create a server is to call `Server::http()`.
6//!
7//! The `http()` function returns an `IoResult<Server>` which will return an error
8//! in the case where the server creation fails (for example if the listening port is already
9//! occupied).
10//!
11//! ```no_run
12//! let server = tiny_http::Server::http("0.0.0.0:0").unwrap();
13//! ```
14//!
15//! A newly-created `Server` will immediately start listening for incoming connections and HTTP
16//! requests.
17//!
18//! ## Receiving requests
19//!
20//! Calling `server.recv()` will block until the next request is available.
21//! This function returns an `IoResult<Request>`, so you need to handle the possible errors.
22//!
23//! ```no_run
24//! # let server = tiny_http::Server::http("0.0.0.0:0").unwrap();
25//!
26//! loop {
27//!     // blocks until the next request is received
28//!     let request = match server.recv() {
29//!         Ok(rq) => rq,
30//!         Err(e) => { println!("error: {}", e); break }
31//!     };
32//!
33//!     // do something with the request
34//!     // ...
35//! }
36//! ```
37//!
38//! In a real-case scenario, you will probably want to spawn multiple worker tasks and call
39//! `server.recv()` on all of them. Like this:
40//!
41//! ```no_run
42//! # use std::sync::Arc;
43//! # use std::thread;
44//! # let server = tiny_http::Server::http("0.0.0.0:0").unwrap();
45//! let server = Arc::new(server);
46//! let mut guards = Vec::with_capacity(4);
47//!
48//! for _ in (0 .. 4) {
49//!     let server = server.clone();
50//!
51//!     let guard = thread::spawn(move || {
52//!         loop {
53//!             let rq = server.recv().unwrap();
54//!
55//!             // ...
56//!         }
57//!     });
58//!
59//!     guards.push(guard);
60//! }
61//! ```
62//!
63//! If you don't want to block, you can call `server.try_recv()` instead.
64//!
65//! ## Handling requests
66//!
67//! The `Request` object returned by `server.recv()` contains informations about the client's request.
68//! The most useful methods are probably `request.method()` and `request.url()` which return
69//! the requested method (`GET`, `POST`, etc.) and url.
70//!
71//! To handle a request, you need to create a `Response` object. See the docs of this object for
72//! more infos. Here is an example of creating a `Response` from a file:
73//!
74//! ```no_run
75//! # use std::fs::File;
76//! # use std::path::Path;
77//! let response = tiny_http::Response::from_file(File::open(&Path::new("image.png")).unwrap());
78//! ```
79//!
80//! All that remains to do is call `request.respond()`:
81//!
82//! ```no_run
83//! # use std::fs::File;
84//! # use std::path::Path;
85//! # let server = tiny_http::Server::http("0.0.0.0:0").unwrap();
86//! # let request = server.recv().unwrap();
87//! # let response = tiny_http::Response::from_file(File::open(&Path::new("image.png")).unwrap());
88//! let _ = request.respond(response);
89//! ```
90#![forbid(unsafe_code)]
91#![deny(rust_2018_idioms)]
92#![allow(clippy::match_like_matches_macro)]
93
94#[cfg(any(feature = "ssl-openssl", feature = "ssl-rustls"))]
95use zeroize::Zeroizing;
96
97use std::error::Error;
98use std::io::Error as IoError;
99use std::io::ErrorKind as IoErrorKind;
100use std::io::Result as IoResult;
101use std::net::{Shutdown, TcpStream, ToSocketAddrs};
102use std::sync::atomic::AtomicBool;
103use std::sync::atomic::Ordering::Relaxed;
104use std::sync::mpsc;
105use std::sync::Arc;
106use std::thread;
107use std::time::Duration;
108
109use client::ClientConnection;
110use connection::Connection;
111use util::MessagesQueue;
112
113pub use common::{HTTPVersion, Header, HeaderField, Method, StatusCode};
114pub use connection::{ConfigListenAddr, ListenAddr, Listener};
115pub use request::{ReadWrite, Request};
116pub use response::{Response, ResponseBox};
117pub use test::TestRequest;
118
119mod client;
120mod common;
121mod connection;
122mod request;
123mod response;
124mod ssl;
125mod test;
126mod util;
127
128/// The main class of this library.
129///
130/// Destroying this object will immediately close the listening socket and the reading
131///  part of all the client's connections. Requests that have already been returned by
132///  the `recv()` function will not close and the responses will be transferred to the client.
133pub struct Server {
134    // should be false as long as the server exists
135    // when set to true, all the subtasks will close within a few hundreds ms
136    close: Arc<AtomicBool>,
137
138    // queue for messages received by child threads
139    messages: Arc<MessagesQueue<Message>>,
140
141    // result of TcpListener::local_addr()
142    listening_addr: ListenAddr,
143}
144
145enum Message {
146    Error(IoError),
147    NewRequest(Request),
148}
149
150impl From<IoError> for Message {
151    fn from(e: IoError) -> Message {
152        Message::Error(e)
153    }
154}
155
156impl From<Request> for Message {
157    fn from(rq: Request) -> Message {
158        Message::NewRequest(rq)
159    }
160}
161
162// this trait is to make sure that Server implements Share and Send
163#[doc(hidden)]
164trait MustBeShareDummy: Sync + Send {}
165#[doc(hidden)]
166impl MustBeShareDummy for Server {}
167
168pub struct IncomingRequests<'a> {
169    server: &'a Server,
170}
171
172/// Represents the parameters required to create a server.
173#[derive(Debug, Clone)]
174pub struct ServerConfig {
175    /// The addresses to try to listen to.
176    pub addr: ConfigListenAddr,
177
178    /// If `Some`, then the server will use SSL to encode the communications.
179    pub ssl: Option<SslConfig>,
180}
181
182/// Configuration of the server for SSL.
183#[derive(Debug, Clone)]
184pub struct SslConfig {
185    /// Contains the public certificate to send to clients.
186    pub certificate: Vec<u8>,
187    /// Contains the ultra-secret private key used to decode communications.
188    pub private_key: Vec<u8>,
189}
190
191impl Server {
192    /// Shortcut for a simple server on a specific address.
193    #[inline]
194    pub fn http<A>(addr: A) -> Result<Server, Box<dyn Error + Send + Sync + 'static>>
195    where
196        A: ToSocketAddrs,
197    {
198        Server::new(ServerConfig {
199            addr: ConfigListenAddr::from_socket_addrs(addr)?,
200            ssl: None,
201        })
202    }
203
204    /// Shortcut for an HTTPS server on a specific address.
205    #[cfg(any(feature = "ssl-openssl", feature = "ssl-rustls"))]
206    #[inline]
207    pub fn https<A>(
208        addr: A,
209        config: SslConfig,
210    ) -> Result<Server, Box<dyn Error + Send + Sync + 'static>>
211    where
212        A: ToSocketAddrs,
213    {
214        Server::new(ServerConfig {
215            addr: ConfigListenAddr::from_socket_addrs(addr)?,
216            ssl: Some(config),
217        })
218    }
219
220    #[cfg(unix)]
221    #[inline]
222    /// Shortcut for a UNIX socket server at a specific path
223    pub fn http_unix(
224        path: &std::path::Path,
225    ) -> Result<Server, Box<dyn Error + Send + Sync + 'static>> {
226        Server::new(ServerConfig {
227            addr: ConfigListenAddr::unix_from_path(path),
228            ssl: None,
229        })
230    }
231
232    /// Builds a new server that listens on the specified address.
233    pub fn new(config: ServerConfig) -> Result<Server, Box<dyn Error + Send + Sync + 'static>> {
234        let listener = config.addr.bind()?;
235        Self::from_listener(listener, config.ssl)
236    }
237
238    /// Builds a new server using the specified TCP listener.
239    ///
240    /// This is useful if you've constructed TcpListener using some less usual method
241    /// such as from systemd. For other cases, you probably want the `new()` function.
242    pub fn from_listener<L: Into<Listener>>(
243        listener: L,
244        ssl_config: Option<SslConfig>,
245    ) -> Result<Server, Box<dyn Error + Send + Sync + 'static>> {
246        let listener = listener.into();
247        // building the "close" variable
248        let close_trigger = Arc::new(AtomicBool::new(false));
249
250        // building the TcpListener
251        let (server, local_addr) = {
252            let local_addr = listener.local_addr()?;
253            log::debug!("Server listening on {}", local_addr);
254            (listener, local_addr)
255        };
256
257        // building the SSL capabilities
258        #[cfg(all(feature = "ssl-openssl", feature = "ssl-rustls"))]
259        compile_error!(
260            "Features 'ssl-openssl' and 'ssl-rustls' must not be enabled at the same time"
261        );
262        #[cfg(not(any(feature = "ssl-openssl", feature = "ssl-rustls")))]
263        type SslContext = ();
264        #[cfg(any(feature = "ssl-openssl", feature = "ssl-rustls"))]
265        type SslContext = crate::ssl::SslContextImpl;
266        let ssl: Option<SslContext> = {
267            match ssl_config {
268                #[cfg(any(feature = "ssl-openssl", feature = "ssl-rustls"))]
269                Some(config) => Some(SslContext::from_pem(
270                    config.certificate,
271                    Zeroizing::new(config.private_key),
272                )?),
273                #[cfg(not(any(feature = "ssl-openssl", feature = "ssl-rustls")))]
274                Some(_) => return Err(
275                    "Building a server with SSL requires enabling the `ssl` feature in tiny-http"
276                        .into(),
277                ),
278                None => None,
279            }
280        };
281
282        // creating a task where server.accept() is continuously called
283        // and ClientConnection objects are pushed in the messages queue
284        let messages = MessagesQueue::with_capacity(8);
285
286        let inside_close_trigger = close_trigger.clone();
287        let inside_messages = messages.clone();
288        thread::spawn(move || {
289            // a tasks pool is used to dispatch the connections into threads
290            let tasks_pool = util::TaskPool::new();
291
292            log::debug!("Running accept thread");
293            while !inside_close_trigger.load(Relaxed) {
294                let new_client = match server.accept() {
295                    Ok((sock, _)) => {
296                        use util::RefinedTcpStream;
297                        let (read_closable, write_closable) = match ssl {
298                            None => RefinedTcpStream::new(sock),
299                            #[cfg(any(feature = "ssl-openssl", feature = "ssl-rustls"))]
300                            Some(ref ssl) => {
301                                // trying to apply SSL over the connection
302                                // if an error occurs, we just close the socket and resume listening
303                                let sock = match ssl.accept(sock) {
304                                    Ok(s) => s,
305                                    Err(_) => continue,
306                                };
307
308                                RefinedTcpStream::new(sock)
309                            }
310                            #[cfg(not(any(feature = "ssl-openssl", feature = "ssl-rustls")))]
311                            Some(ref _ssl) => unreachable!(),
312                        };
313
314                        Ok(ClientConnection::new(write_closable, read_closable))
315                    }
316                    Err(e) => Err(e),
317                };
318
319                match new_client {
320                    Ok(client) => {
321                        let messages = inside_messages.clone();
322                        let mut client = Some(client);
323                        tasks_pool.spawn(Box::new(move || {
324                            if let Some(client) = client.take() {
325                                // Synchronization is needed for HTTPS requests to avoid a deadlock
326                                if client.secure() {
327                                    let (sender, receiver) = mpsc::channel();
328                                    for rq in client {
329                                        messages.push(rq.with_notify_sender(sender.clone()).into());
330                                        receiver.recv().unwrap();
331                                    }
332                                } else {
333                                    for rq in client {
334                                        messages.push(rq.into());
335                                    }
336                                }
337                            }
338                        }));
339                    }
340
341                    Err(e) => {
342                        log::error!("Error accepting new client: {}", e);
343                        inside_messages.push(e.into());
344                        break;
345                    }
346                }
347            }
348            log::debug!("Terminating accept thread");
349        });
350
351        // result
352        Ok(Server {
353            messages,
354            close: close_trigger,
355            listening_addr: local_addr,
356        })
357    }
358
359    /// Returns an iterator for all the incoming requests.
360    ///
361    /// The iterator will return `None` if the server socket is shutdown.
362    #[inline]
363    pub fn incoming_requests(&self) -> IncomingRequests<'_> {
364        IncomingRequests { server: self }
365    }
366
367    /// Returns the address the server is listening to.
368    #[inline]
369    pub fn server_addr(&self) -> ListenAddr {
370        self.listening_addr.clone()
371    }
372
373    /// Returns the number of clients currently connected to the server.
374    pub fn num_connections(&self) -> usize {
375        unimplemented!()
376        //self.requests_receiver.lock().len()
377    }
378
379    /// Blocks until an HTTP request has been submitted and returns it.
380    pub fn recv(&self) -> IoResult<Request> {
381        match self.messages.pop() {
382            Some(Message::Error(err)) => Err(err),
383            Some(Message::NewRequest(rq)) => Ok(rq),
384            None => Err(IoError::new(IoErrorKind::Other, "thread unblocked")),
385        }
386    }
387
388    /// Same as `recv()` but doesn't block longer than timeout
389    pub fn recv_timeout(&self, timeout: Duration) -> IoResult<Option<Request>> {
390        match self.messages.pop_timeout(timeout) {
391            Some(Message::Error(err)) => Err(err),
392            Some(Message::NewRequest(rq)) => Ok(Some(rq)),
393            None => Ok(None),
394        }
395    }
396
397    /// Same as `recv()` but doesn't block.
398    pub fn try_recv(&self) -> IoResult<Option<Request>> {
399        match self.messages.try_pop() {
400            Some(Message::Error(err)) => Err(err),
401            Some(Message::NewRequest(rq)) => Ok(Some(rq)),
402            None => Ok(None),
403        }
404    }
405
406    /// Unblock thread stuck in recv() or incoming_requests().
407    /// If there are several such threads, only one is unblocked.
408    /// This method allows graceful shutdown of server.
409    pub fn unblock(&self) {
410        self.messages.unblock();
411    }
412}
413
414impl Iterator for IncomingRequests<'_> {
415    type Item = Request;
416    fn next(&mut self) -> Option<Request> {
417        self.server.recv().ok()
418    }
419}
420
421impl Drop for Server {
422    fn drop(&mut self) {
423        self.close.store(true, Relaxed);
424        // Connect briefly to ourselves to unblock the accept thread
425        let maybe_stream = match &self.listening_addr {
426            ListenAddr::IP(addr) => TcpStream::connect(addr).map(Connection::from),
427            #[cfg(unix)]
428            ListenAddr::Unix(addr) => {
429                // TODO: use connect_addr when its stabilized.
430                let path = addr.as_pathname().unwrap();
431                std::os::unix::net::UnixStream::connect(path).map(Connection::from)
432            }
433        };
434        if let Ok(stream) = maybe_stream {
435            let _ = stream.shutdown(Shutdown::Both);
436        }
437
438        #[cfg(unix)]
439        if let ListenAddr::Unix(addr) = &self.listening_addr {
440            if let Some(path) = addr.as_pathname() {
441                let _ = std::fs::remove_file(path);
442            }
443        }
444    }
445}