-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: start on a built-in API client: xs cat, xs append, xs serve (#13)
- Loading branch information
Showing
7 changed files
with
262 additions
and
14 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
/target | ||
store |
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,151 @@ | ||
use serde_json::Value; | ||
|
||
use futures::StreamExt; | ||
|
||
use tokio::io::AsyncRead; | ||
use tokio::net::{TcpStream, UnixStream}; | ||
use tokio::sync::mpsc::Receiver; | ||
|
||
use http_body_util::{combinators::BoxBody, BodyExt, Empty}; | ||
use hyper::body::Bytes; | ||
use hyper::client::conn::http1; | ||
use hyper::{Method, Request, StatusCode}; | ||
use hyper_util::rt::TokioIo; | ||
|
||
use crate::listener::AsyncReadWriteBox; | ||
|
||
type BoxError = Box<dyn std::error::Error + Send + Sync>; | ||
|
||
async fn connect(addr: &str) -> Result<AsyncReadWriteBox, BoxError> { | ||
if addr.starts_with('/') || addr.starts_with('.') { | ||
let path = std::path::Path::new(addr); | ||
let addr = if path.is_dir() { | ||
path.join("sock") | ||
} else { | ||
path.to_path_buf() | ||
}; | ||
let stream = UnixStream::connect(addr).await?; | ||
Ok(Box::new(stream)) | ||
} else { | ||
let addr = if addr.starts_with(':') { | ||
format!("127.0.0.1{}", addr) | ||
} else { | ||
addr.to_string() | ||
}; | ||
let stream = TcpStream::connect(addr).await?; | ||
Ok(Box::new(stream)) | ||
} | ||
} | ||
|
||
fn empty() -> BoxBody<Bytes, BoxError> { | ||
Empty::<Bytes>::new() | ||
.map_err(|never| match never {}) | ||
.boxed() | ||
} | ||
|
||
pub async fn cat(addr: &str, follow: bool) -> Result<Receiver<Bytes>, BoxError> { | ||
let stream = connect(addr).await?; | ||
let io = TokioIo::new(stream); | ||
|
||
let (mut sender, conn) = http1::handshake(io).await?; | ||
|
||
tokio::spawn(async move { | ||
if let Err(e) = conn.await { | ||
eprintln!("Connection error: {}", e); | ||
} | ||
}); | ||
|
||
let uri = if follow { | ||
"http://localhost/?follow=true" | ||
} else { | ||
"http://localhost/" | ||
}; | ||
|
||
let req = Request::builder() | ||
.method(Method::GET) | ||
.uri(uri) | ||
.body(empty())?; | ||
|
||
let res = sender.send_request(req).await?; | ||
|
||
if res.status() != StatusCode::OK { | ||
return Err(format!("HTTP error: {}", res.status()).into()); | ||
} | ||
|
||
let (_parts, mut body) = res.into_parts(); | ||
|
||
let (tx, rx) = tokio::sync::mpsc::channel(100); | ||
|
||
tokio::spawn(async move { | ||
while let Some(frame_result) = body.frame().await { | ||
match frame_result { | ||
Ok(frame) => { | ||
if let Ok(bytes) = frame.into_data() { | ||
if tx.send(bytes).await.is_err() { | ||
break; | ||
} | ||
} | ||
// Ignore non-data frames | ||
} | ||
Err(e) => { | ||
eprintln!("Error reading body: {}", e); | ||
break; | ||
} | ||
} | ||
} | ||
}); | ||
|
||
Ok(rx) | ||
} | ||
|
||
use http_body_util::StreamBody; | ||
use tokio_util::io::ReaderStream; | ||
|
||
pub async fn append<R>( | ||
addr: &str, | ||
topic: &str, | ||
data: R, | ||
meta: Option<&Value>, | ||
) -> Result<Bytes, BoxError> | ||
where | ||
R: AsyncRead + Unpin + Send + 'static, | ||
{ | ||
let stream = connect(addr).await?; | ||
let io = TokioIo::new(stream); | ||
let (mut sender, conn) = http1::handshake(io).await?; | ||
tokio::spawn(async move { | ||
if let Err(e) = conn.await { | ||
eprintln!("Connection error: {}", e); | ||
} | ||
}); | ||
|
||
let uri = format!("http://localhost/{}", topic); | ||
let mut req = Request::builder().method(Method::POST).uri(uri); | ||
|
||
if let Some(meta_value) = meta { | ||
req = req.header("xs-meta", serde_json::to_string(meta_value)?); | ||
} | ||
|
||
// Create a stream from the AsyncRead | ||
let reader_stream = ReaderStream::new(data); | ||
|
||
// Map the stream to convert io::Error to BoxError | ||
let mapped_stream = reader_stream.map(|result| { | ||
result | ||
.map(hyper::body::Frame::data) | ||
.map_err(|e| Box::new(e) as BoxError) | ||
}); | ||
|
||
// Create a StreamBody | ||
let body = StreamBody::new(mapped_stream); | ||
|
||
let req = req.body(body)?; | ||
let res = sender.send_request(req).await?; | ||
|
||
if res.status() != StatusCode::OK { | ||
return Err(format!("HTTP error: {}", res.status()).into()); | ||
} | ||
|
||
let body = res.collect().await?.to_bytes(); | ||
Ok(body) | ||
} |
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 |
---|---|---|
@@ -1,4 +1,5 @@ | ||
pub mod api; | ||
pub mod client; | ||
pub mod error; | ||
pub mod handlers; | ||
pub mod http; | ||
|
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