-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
implement unix domain sockets as transport layer
- Loading branch information
Showing
9 changed files
with
195 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
use crate::net::clients::GenClient; | ||
use crate::net::protocol::{parse_response_str, Request, Response}; | ||
use async_trait::async_trait; | ||
use std::path::PathBuf; | ||
use tokio::io::{AsyncBufReadExt, AsyncWrite, AsyncWriteExt, BufReader, BufWriter}; | ||
use tokio::net::unix::{OwnedReadHalf, OwnedWriteHalf}; | ||
use tokio::net::UnixStream; | ||
|
||
/// A pixelflut client that connects to a unix domain socket and uses buffered read/write for communication with a pixelflut server | ||
#[derive(Debug)] | ||
pub struct UnixSocketClient { | ||
reader: BufReader<OwnedReadHalf>, | ||
writer: BufWriter<OwnedWriteHalf>, | ||
} | ||
|
||
impl UnixSocketClient { | ||
/// Flush the write buffer to immediately send all enqueued requests to the server. | ||
async fn flush(&mut self) -> std::io::Result<()> { | ||
self.writer.flush().await | ||
} | ||
|
||
/// Get the raw writer that is connected to the pixelflut server. | ||
pub fn get_writer(&mut self) -> &mut BufWriter<impl AsyncWrite> { | ||
&mut self.writer | ||
} | ||
} | ||
|
||
#[async_trait] | ||
impl GenClient for UnixSocketClient { | ||
type ConnectionParam = PathBuf; | ||
|
||
async fn connect(addr: Self::ConnectionParam) -> std::io::Result<Self> { | ||
let (reader, writer) = UnixStream::connect(addr).await?.into_split(); | ||
Ok(Self { | ||
reader: BufReader::new(reader), | ||
writer: BufWriter::new(writer), | ||
}) | ||
} | ||
|
||
async fn send_request(&mut self, request: Request) -> std::io::Result<()> { | ||
request.write_async(&mut self.writer).await | ||
} | ||
|
||
async fn await_response(&mut self) -> anyhow::Result<Response> { | ||
let mut buf = String::with_capacity(32); | ||
self.reader.read_line(&mut buf).await?; | ||
let response = parse_response_str(&buf)?; | ||
Ok(response) | ||
} | ||
|
||
async fn exchange(&mut self, request: Request) -> anyhow::Result<Response> { | ||
self.send_request(request).await?; | ||
self.flush().await?; | ||
let response = self.await_response().await?; | ||
Ok(response) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
use crate::net::servers::GenServer; | ||
use crate::pixmap::SharedPixmap; | ||
use crate::DaemonResult; | ||
use anyhow::anyhow; | ||
use async_trait::async_trait; | ||
use bytes::{BufMut, BytesMut}; | ||
use std::io::Write; | ||
use std::path::PathBuf; | ||
use tokio::io::{AsyncReadExt, AsyncWriteExt}; | ||
use tokio::net::{UnixListener, UnixStream}; | ||
use tokio::task::{AbortHandle, JoinSet}; | ||
|
||
/// Options with which the `UnixSocketServer` is configured | ||
#[derive(Debug, Clone, Eq, PartialEq)] | ||
pub struct UnixSocketOptions { | ||
/// The path at which a socket should be created | ||
pub path: PathBuf, | ||
} | ||
|
||
/// A server implementation using unix domain sockets to transport pixelflut messages. | ||
#[derive(Debug, Clone, Eq, PartialEq)] | ||
pub struct UnixSocketServer { | ||
options: UnixSocketOptions, | ||
} | ||
|
||
impl UnixSocketServer { | ||
#[tracing::instrument(skip_all)] | ||
async fn handle_listener(listener: UnixListener, pixmap: SharedPixmap) -> anyhow::Result<!> { | ||
loop { | ||
let (stream, _) = listener.accept().await?; | ||
let pixmap = pixmap.clone(); | ||
tokio::spawn(async move { | ||
if let Err(e) = UnixSocketServer::handle_connection(stream, pixmap).await { | ||
tracing::warn!("Got error while handling unix socket stream: {e}"); | ||
} | ||
}); | ||
} | ||
} | ||
|
||
#[tracing::instrument(skip_all)] | ||
async fn handle_connection(mut stream: UnixStream, pixmap: SharedPixmap) -> anyhow::Result<()> { | ||
const MAX_LINE_LEN: usize = 32; | ||
tracing::debug!("Client connected"); | ||
|
||
let mut req_buf = BytesMut::with_capacity(16 * 1024); | ||
let mut resp_buf = BytesMut::with_capacity(2 * 1024).writer(); | ||
loop { | ||
// fill the line buffer from the socket | ||
let n = stream.read_buf(&mut req_buf).await?; | ||
if n == 0 { | ||
return Err(anyhow!("client stream exhausted")); | ||
} | ||
tracing::trace!("Received {}KiB stream data: {:?}", n / 1024, req_buf); | ||
|
||
// handle all lines contained in the buffer | ||
while let Some((i, _)) = req_buf.iter().enumerate().find(|(_, &b)| b == b'\n') { | ||
let line = req_buf.split_to(i + 1); | ||
let result = super::handle_request(&line, &pixmap); | ||
match result { | ||
Err(e) => { | ||
resp_buf.write_fmt(format_args!("{}\n", e)).unwrap(); | ||
} | ||
Ok(Some(response)) => response.write(&mut resp_buf).unwrap(), | ||
Ok(None) => {} | ||
} | ||
} | ||
|
||
// clear the buffer if someone is deliberately not sending a newline | ||
if req_buf.len() > MAX_LINE_LEN { | ||
tracing::warn!( | ||
"Request buffer has {}B but no lines left in it. Client is probably misbehaving.", | ||
req_buf.len() | ||
); | ||
req_buf.clear(); | ||
resp_buf.write_all("line too long\n".as_bytes()).unwrap(); | ||
} | ||
|
||
// write accumulated responses back to the sender | ||
if !resp_buf.get_ref().is_empty() { | ||
tracing::trace!( | ||
"Sending back {}KiB response: {:?}", | ||
resp_buf.get_ref().len() / 1024, | ||
resp_buf.get_ref() | ||
); | ||
stream.write_all_buf(resp_buf.get_mut()).await?; | ||
} | ||
} | ||
} | ||
} | ||
|
||
#[async_trait] | ||
impl GenServer for UnixSocketServer { | ||
type Options = UnixSocketOptions; | ||
|
||
fn new(options: Self::Options) -> Self { | ||
Self { options } | ||
} | ||
|
||
async fn start( | ||
self, | ||
pixmap: SharedPixmap, | ||
join_set: &mut JoinSet<DaemonResult>, | ||
) -> anyhow::Result<AbortHandle> { | ||
let listener = UnixListener::bind(&self.options.path)?; | ||
tracing::info!("Started unix listener on {}", self.options.path.display()); | ||
|
||
let handle = join_set | ||
.build_task() | ||
.name("unix_listener") | ||
.spawn(async move { UnixSocketServer::handle_listener(listener, pixmap).await })?; | ||
Ok(handle) | ||
} | ||
} |