#![forbid(unsafe_code)]
#![deny(rust_2018_idioms)]
#![allow(clippy::match_like_matches_macro)]
#[cfg(any(feature = "ssl-openssl", feature = "ssl-rustls"))]
use zeroize::Zeroizing;
use std::error::Error;
use std::io::Error as IoError;
use std::io::ErrorKind as IoErrorKind;
use std::io::Result as IoResult;
use std::net::{Shutdown, TcpStream, ToSocketAddrs};
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering::Relaxed;
use std::sync::mpsc;
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use client::ClientConnection;
use connection::Connection;
use util::MessagesQueue;
pub use common::{HTTPVersion, Header, HeaderField, Method, StatusCode};
pub use connection::{ConfigListenAddr, ListenAddr, Listener};
pub use request::{ReadWrite, Request};
pub use response::{Response, ResponseBox};
pub use test::TestRequest;
mod client;
mod common;
mod connection;
mod request;
mod response;
mod ssl;
mod test;
mod util;
pub struct Server {
close: Arc<AtomicBool>,
messages: Arc<MessagesQueue<Message>>,
listening_addr: ListenAddr,
}
enum Message {
Error(IoError),
NewRequest(Request),
}
impl From<IoError> for Message {
fn from(e: IoError) -> Message {
Message::Error(e)
}
}
impl From<Request> for Message {
fn from(rq: Request) -> Message {
Message::NewRequest(rq)
}
}
#[doc(hidden)]
trait MustBeShareDummy: Sync + Send {}
#[doc(hidden)]
impl MustBeShareDummy for Server {}
pub struct IncomingRequests<'a> {
server: &'a Server,
}
#[derive(Debug, Clone)]
pub struct ServerConfig {
pub addr: ConfigListenAddr,
pub ssl: Option<SslConfig>,
}
#[derive(Debug, Clone)]
pub struct SslConfig {
pub certificate: Vec<u8>,
pub private_key: Vec<u8>,
}
impl Server {
#[inline]
pub fn http<A>(addr: A) -> Result<Server, Box<dyn Error + Send + Sync + 'static>>
where
A: ToSocketAddrs,
{
Server::new(ServerConfig {
addr: ConfigListenAddr::from_socket_addrs(addr)?,
ssl: None,
})
}
#[cfg(any(feature = "ssl-openssl", feature = "ssl-rustls"))]
#[inline]
pub fn https<A>(
addr: A,
config: SslConfig,
) -> Result<Server, Box<dyn Error + Send + Sync + 'static>>
where
A: ToSocketAddrs,
{
Server::new(ServerConfig {
addr: ConfigListenAddr::from_socket_addrs(addr)?,
ssl: Some(config),
})
}
#[cfg(unix)]
#[inline]
pub fn http_unix(
path: &std::path::Path,
) -> Result<Server, Box<dyn Error + Send + Sync + 'static>> {
Server::new(ServerConfig {
addr: ConfigListenAddr::unix_from_path(path),
ssl: None,
})
}
pub fn new(config: ServerConfig) -> Result<Server, Box<dyn Error + Send + Sync + 'static>> {
let listener = config.addr.bind()?;
Self::from_listener(listener, config.ssl)
}
pub fn from_listener<L: Into<Listener>>(
listener: L,
ssl_config: Option<SslConfig>,
) -> Result<Server, Box<dyn Error + Send + Sync + 'static>> {
let listener = listener.into();
let close_trigger = Arc::new(AtomicBool::new(false));
let (server, local_addr) = {
let local_addr = listener.local_addr()?;
log::debug!("Server listening on {}", local_addr);
(listener, local_addr)
};
#[cfg(all(feature = "ssl-openssl", feature = "ssl-rustls"))]
compile_error!(
"Features 'ssl-openssl' and 'ssl-rustls' must not be enabled at the same time"
);
#[cfg(not(any(feature = "ssl-openssl", feature = "ssl-rustls")))]
type SslContext = ();
#[cfg(any(feature = "ssl-openssl", feature = "ssl-rustls"))]
type SslContext = crate::ssl::SslContextImpl;
let ssl: Option<SslContext> = {
match ssl_config {
#[cfg(any(feature = "ssl-openssl", feature = "ssl-rustls"))]
Some(config) => Some(SslContext::from_pem(
config.certificate,
Zeroizing::new(config.private_key),
)?),
#[cfg(not(any(feature = "ssl-openssl", feature = "ssl-rustls")))]
Some(_) => return Err(
"Building a server with SSL requires enabling the `ssl` feature in tiny-http"
.into(),
),
None => None,
}
};
let messages = MessagesQueue::with_capacity(8);
let inside_close_trigger = close_trigger.clone();
let inside_messages = messages.clone();
thread::spawn(move || {
let tasks_pool = util::TaskPool::new();
log::debug!("Running accept thread");
while !inside_close_trigger.load(Relaxed) {
let new_client = match server.accept() {
Ok((sock, _)) => {
use util::RefinedTcpStream;
let (read_closable, write_closable) = match ssl {
None => RefinedTcpStream::new(sock),
#[cfg(any(feature = "ssl-openssl", feature = "ssl-rustls"))]
Some(ref ssl) => {
let sock = match ssl.accept(sock) {
Ok(s) => s,
Err(_) => continue,
};
RefinedTcpStream::new(sock)
}
#[cfg(not(any(feature = "ssl-openssl", feature = "ssl-rustls")))]
Some(ref _ssl) => unreachable!(),
};
Ok(ClientConnection::new(write_closable, read_closable))
}
Err(e) => Err(e),
};
match new_client {
Ok(client) => {
let messages = inside_messages.clone();
let mut client = Some(client);
tasks_pool.spawn(Box::new(move || {
if let Some(client) = client.take() {
if client.secure() {
let (sender, receiver) = mpsc::channel();
for rq in client {
messages.push(rq.with_notify_sender(sender.clone()).into());
receiver.recv().unwrap();
}
} else {
for rq in client {
messages.push(rq.into());
}
}
}
}));
}
Err(e) => {
log::error!("Error accepting new client: {}", e);
inside_messages.push(e.into());
break;
}
}
}
log::debug!("Terminating accept thread");
});
Ok(Server {
messages,
close: close_trigger,
listening_addr: local_addr,
})
}
#[inline]
pub fn incoming_requests(&self) -> IncomingRequests<'_> {
IncomingRequests { server: self }
}
#[inline]
pub fn server_addr(&self) -> ListenAddr {
self.listening_addr.clone()
}
pub fn num_connections(&self) -> usize {
unimplemented!()
}
pub fn recv(&self) -> IoResult<Request> {
match self.messages.pop() {
Some(Message::Error(err)) => Err(err),
Some(Message::NewRequest(rq)) => Ok(rq),
None => Err(IoError::new(IoErrorKind::Other, "thread unblocked")),
}
}
pub fn recv_timeout(&self, timeout: Duration) -> IoResult<Option<Request>> {
match self.messages.pop_timeout(timeout) {
Some(Message::Error(err)) => Err(err),
Some(Message::NewRequest(rq)) => Ok(Some(rq)),
None => Ok(None),
}
}
pub fn try_recv(&self) -> IoResult<Option<Request>> {
match self.messages.try_pop() {
Some(Message::Error(err)) => Err(err),
Some(Message::NewRequest(rq)) => Ok(Some(rq)),
None => Ok(None),
}
}
pub fn unblock(&self) {
self.messages.unblock();
}
}
impl Iterator for IncomingRequests<'_> {
type Item = Request;
fn next(&mut self) -> Option<Request> {
self.server.recv().ok()
}
}
impl Drop for Server {
fn drop(&mut self) {
self.close.store(true, Relaxed);
let maybe_stream = match &self.listening_addr {
ListenAddr::IP(addr) => TcpStream::connect(addr).map(Connection::from),
#[cfg(unix)]
ListenAddr::Unix(addr) => {
let path = addr.as_pathname().unwrap();
std::os::unix::net::UnixStream::connect(path).map(Connection::from)
}
};
if let Ok(stream) = maybe_stream {
let _ = stream.shutdown(Shutdown::Both);
}
#[cfg(unix)]
if let ListenAddr::Unix(addr) = &self.listening_addr {
if let Some(path) = addr.as_pathname() {
let _ = std::fs::remove_file(path);
}
}
}
}