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}