#![forbid(unsafe_code)]
#![deny(rust_2018_idioms)]
#![allow(clippy::match_like_matches_macro)]
#[cfg(any(
feature = "ssl-openssl",
feature = "ssl-rustls",
feature = "ssl-native-tls"
))]
use zeroize::Zeroizing;
use std::error::Error;
use std::io;
use std::io::Error as IoError;
use std::io::ErrorKind as IoErrorKind;
use std::io::Result as IoResult;
use std::net::{Shutdown, TcpStream, ToSocketAddrs};
#[cfg(unix)]
use std::path::Path;
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;
use crate::util::TaskPool;
mod client;
mod common;
mod connection;
mod log;
mod request;
mod response;
mod ssl;
mod test;
pub mod util;
#[cfg(not(any(
feature = "ssl-openssl",
feature = "ssl-rustls",
feature = "ssl-native-tls"
)))]
type SslContext = ();
#[cfg(any(
feature = "ssl-openssl",
feature = "ssl-rustls",
feature = "ssl-native-tls"
))]
type SslContext = ssl::SslContextImpl;
#[derive(Debug)]
pub struct SslContextWrap(Option<SslContext>);
impl SslContextWrap
{
pub
fn build_ssl_capabilities(ssl_config: Option<SslConfig>)
-> Result<Self, Box<dyn Error + Send + Sync>>
{
let ssl: Option<SslContext> =
{
match ssl_config
{
#[cfg(any(
feature = "ssl-openssl",
feature = "ssl-rustls",
feature = "ssl-native-tls"
))]
Some(config) =>
Some(
SslContext::from_pem(config.certificate,
Zeroizing::new(config.private_key))?
),
#[cfg(not(any(
feature = "ssl-openssl",
feature = "ssl-rustls",
feature = "ssl-native-tls"
)))]
Some(_) =>
return Err(
"Building a server with SSL requires enabling the `ssl` feature in tiny-http"
.into(),
),
None =>
None,
}
};
return Ok(Self(ssl));
}
}
#[derive(Debug)]
pub struct Server
{
close: Arc<AtomicBool>,
messages: Arc<MessagesQueue<Message>>,
listening_addr: ListenAddr,
}
#[derive(Debug)]
pub 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 {}
#[derive(Debug)]
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",
feature = "ssl-native-tls"
))]
#[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<P: AsRef<Path>>(path: P) -> Result<Server, Box<dyn Error + Send + Sync + 'static>>
{
Server::new(
ServerConfig
{
addr: ConfigListenAddr::unix_from_path(path.as_ref()),
ssl: None,
}
)
}
pub
fn new(config: ServerConfig) -> Result<Server, Box<dyn Error + Send + Sync + 'static>>
{
let (listener, la) = config.addr.bind()?;
Self::from_listener(listener, la, config.ssl)
}
pub
fn accept_connection(server: &Listener, ssl: &SslContextWrap) -> Result<Option<ClientConnection>, IoError>
{
let (sock, _) = server.accept()?;
use util::RefinedTcpStream;
let (read_closable, write_closable) =
match ssl.0
{
None =>
RefinedTcpStream::new(sock),
#[cfg(any(
feature = "ssl-openssl",
feature = "ssl-rustls",
feature = "ssl-native-tls"
))]
Some(ref ssl) =>
{
let Ok(sock) = ssl.accept(sock)
else
{
return Ok(None);
};
RefinedTcpStream::new(sock)
}
#[cfg(not(any(
feature = "ssl-openssl",
feature = "ssl-rustls",
feature = "ssl-native-tls"
)))]
Some(ref _ssl) =>
unreachable!(),
};
return
Ok( Some(ClientConnection::new(write_closable, read_closable)) );
}
pub
fn new_client(
new_client: Result<Option<ClientConnection>, IoError>,
inside_messages: &Arc<MessagesQueue<Message>>,
tasks_pool: &TaskPool
) -> io::Result<()>
{
match new_client
{
Ok(Some(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() == true
{
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());
}
}
}
}
)
);
return Ok(());
},
Ok(None) =>
return Ok(()),
Err(ref e) if e.kind() == IoErrorKind::ConnectionAborted =>
{
log::debug!("Error accepting new client: {}", e);
return Ok(());
},
Err(e) =>
{
let kind = e.kind();
log::error!("Error accepting new client: {}", e);
inside_messages.push(e.into());
return Err(IoError::new(kind, format!("new_client() accept() error")));
}
}
}
pub
fn from_listener<L: Into<Listener>>(listener: L, local_addr: ListenAddr, ssl_config: Option<SslConfig>)
-> Result<Server, Box<dyn Error + Send + Sync + 'static>>
{
let server = listener.into();
let close_trigger = Arc::new(AtomicBool::new(false));
log::debug!("Server listening on {}", local_addr);
let ssl= SslContextWrap::build_ssl_capabilities(ssl_config)?;
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) == false
{
let new_client =
Self::accept_connection(&server, &ssl);
if let Err(_) = Self::new_client(new_client, &inside_messages, &tasks_pool)
{
break;
}
}
log::debug!("Terminating accept thread");
}
);
return 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);
}
}
}
}