Move whole rmcp crate to offcial rust sdk (#44)

* get rid of async-trait

* move rmcp to official-rust-sdk

* replace master with main

* update readme, change license

* fix typo

* Update README.md

Co-authored-by: Jefry Dewangga <jefrydco@gmail.com>

* fix typos

---------

Co-authored-by: = <=>
Co-authored-by: Jefry Dewangga <jefrydco@gmail.com>
This commit is contained in:
4t145 2025-03-28 05:33:41 +08:00 • committed by GitHub
commit 0be6067326
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 8676 additions and 4466 deletions

View file

@ -0,0 +1,91 @@
use futures::{Sink, Stream};
use tokio::{
io::AsyncRead,
process::{ChildStdin, ChildStdout},
};
use crate::service::{RxJsonRpcMessage, ServiceRole, TxJsonRpcMessage};
use super::IntoTransport;
pub(crate) fn child_process(
mut child: tokio::process::Child,
) -> std::io::Result<(tokio::process::Child, (ChildStdout, ChildStdin))> {
if child.stdin.is_none() {
return Err(std::io::Error::other("std in was taken"));
}
if child.stdout.is_none() {
return Err(std::io::Error::other("std out was taken"));
}
let child_stdin = child.stdin.take().expect("already checked");
let child_stdout = child.stdout.take().expect("already checked");
Ok((child, (child_stdout, child_stdin)))
}
pub struct TokioChildProcess {
child: tokio::process::Child,
child_stdin: ChildStdin,
child_stdout: ChildStdout,
}
// we hold the child process with stdout, for it's easier to implement AsyncRead
pin_project_lite::pin_project! {
pub struct TokioChildProcessOut {
child: tokio::process::Child,
#[pin]
child_stdout: ChildStdout,
}
}
impl AsyncRead for TokioChildProcessOut {
fn poll_read(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &mut tokio::io::ReadBuf<'_>,
) -> std::task::Poll<std::io::Result<()>> {
self.project().child_stdout.poll_read(cx, buf)
}
}
impl TokioChildProcess {
pub fn new(child: &mut tokio::process::Command) -> std::io::Result<Self> {
child
.kill_on_drop(true)
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped());
let (child, (child_stdout, child_stdin)) = child_process(child.spawn()?)?;
Ok(Self {
child,
child_stdin,
child_stdout,
})
}
pub fn split(self) -> (TokioChildProcessOut, ChildStdin) {
let TokioChildProcess {
child,
child_stdin,
child_stdout,
} = self;
(
TokioChildProcessOut {
child,
child_stdout,
},
child_stdin,
)
}
}
impl<R: ServiceRole> IntoTransport<R, std::io::Error, ()> for TokioChildProcess {
fn into_transport(
self,
) -> (
impl Sink<TxJsonRpcMessage<R>, Error = std::io::Error> + Send + 'static,
impl Stream<Item = RxJsonRpcMessage<R>> + Send + 'static,
) {
IntoTransport::<R, std::io::Error, super::io::TransportAdapterAsyncRW>::into_transport(
self.split(),
)
}
}

View file

@ -0,0 +1,316 @@
use std::marker::PhantomData;
// use crate::schema::*;
use futures::{Sink, SinkExt, Stream, StreamExt};
use serde::{Serialize, de::DeserializeOwned};
use thiserror::Error;
use tokio::io::{AsyncRead, AsyncWrite};
use tokio_util::{
bytes::{Buf, BufMut, BytesMut},
codec::{Decoder, Encoder, FramedRead, FramedWrite},
};
use crate::service::{RxJsonRpcMessage, ServiceRole, TxJsonRpcMessage};
use super::IntoTransport;
#[cfg(feature = "transport-io")]
/// # StdIO Transport
///
/// Create a pair of [`tokio::io::Stdin`] and [`tokio::io::Stdout`].
pub fn stdio() -> (tokio::io::Stdin, tokio::io::Stdout) {
(tokio::io::stdin(), tokio::io::stdout())
}
pub enum TransportAdapterAsyncRW {}
impl<Role, R, W> IntoTransport<Role, std::io::Error, TransportAdapterAsyncRW> for (R, W)
where
Role: ServiceRole,
R: AsyncRead + Send + 'static,
W: AsyncWrite + Send + 'static,
{
fn into_transport(
self,
) -> (
impl Sink<TxJsonRpcMessage<Role>, Error = std::io::Error> + Send + 'static,
impl Stream<Item = RxJsonRpcMessage<Role>> + Send + 'static,
) {
(from_async_write(self.1), from_async_read(self.0))
}
}
pub enum TransportAdapterAsyncCombinedRW {}
impl<Role, S> IntoTransport<Role, std::io::Error, TransportAdapterAsyncCombinedRW> for S
where
Role: ServiceRole,
S: AsyncRead + AsyncWrite + Send + 'static,
{
fn into_transport(
self,
) -> (
impl Sink<TxJsonRpcMessage<Role>, Error = std::io::Error> + Send + 'static,
impl Stream<Item = RxJsonRpcMessage<Role>> + Send + 'static,
) {
IntoTransport::<Role, std::io::Error, TransportAdapterAsyncRW>::into_transport(
tokio::io::split(self),
)
}
}
pub fn from_async_read<T: DeserializeOwned, R: AsyncRead>(reader: R) -> impl Stream<Item = T> {
FramedRead::new(reader, JsonRpcMessageCodec::<T>::default()).filter_map(|result| {
if let Err(e) = &result {
tracing::error!("Error reading from stream: {}", e);
}
futures::future::ready(result.ok())
})
}
pub fn from_async_write<T: Serialize, W: AsyncWrite>(
writer: W,
) -> impl Sink<T, Error = std::io::Error> {
FramedWrite::new(writer, JsonRpcMessageCodec::<T>::default()).sink_map_err(Into::into)
}
#[derive(Debug, Clone)]
pub struct JsonRpcMessageCodec<T> {
_marker: PhantomData<fn() -> T>,
next_index: usize,
max_length: usize,
is_discarding: bool,
}
impl<T> Default for JsonRpcMessageCodec<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> JsonRpcMessageCodec<T> {
pub fn new() -> Self {
Self {
_marker: PhantomData,
next_index: 0,
max_length: usize::MAX,
is_discarding: false,
}
}
pub fn new_with_max_length(max_length: usize) -> Self {
Self {
max_length,
..Self::new()
}
}
pub fn max_length(&self) -> usize {
self.max_length
}
}
fn without_carriage_return(s: &[u8]) -> &[u8] {
if let Some(&b'\r') = s.last() {
&s[..s.len() - 1]
} else {
s
}
}
#[derive(Debug, Error)]
pub enum JsonRpcMessageCodecError {
#[error("max line length exceeded")]
MaxLineLengthExceeded,
#[error("serde error {0}")]
Serde(#[from] serde_json::Error),
#[error("io error {0}")]
Io(#[from] std::io::Error),
}
impl From<JsonRpcMessageCodecError> for std::io::Error {
fn from(value: JsonRpcMessageCodecError) -> Self {
match value {
JsonRpcMessageCodecError::MaxLineLengthExceeded => {
std::io::Error::new(std::io::ErrorKind::InvalidData, value)
}
JsonRpcMessageCodecError::Serde(e) => e.into(),
JsonRpcMessageCodecError::Io(e) => e,
}
}
}
impl<T: DeserializeOwned> Decoder for JsonRpcMessageCodec<T> {
type Item = T;
type Error = JsonRpcMessageCodecError;
fn decode(
&mut self,
buf: &mut BytesMut,
) -> Result<Option<Self::Item>, JsonRpcMessageCodecError> {
loop {
// Determine how far into the buffer we'll search for a newline. If
// there's no max_length set, we'll read to the end of the buffer.
let read_to = std::cmp::min(self.max_length.saturating_add(1), buf.len());
let newline_offset = buf[self.next_index..read_to]
.iter()
.position(|b| *b == b'\n');
match (self.is_discarding, newline_offset) {
(true, Some(offset)) => {
// If we found a newline, discard up to that offset and
// then stop discarding. On the next iteration, we'll try
// to read a line normally.
buf.advance(offset + self.next_index + 1);
self.is_discarding = false;
self.next_index = 0;
}
(true, None) => {
// Otherwise, we didn't find a newline, so we'll discard
// everything we read. On the next iteration, we'll continue
// discarding up to max_len bytes unless we find a newline.
buf.advance(read_to);
self.next_index = 0;
if buf.is_empty() {
return Ok(None);
}
}
(false, Some(offset)) => {
// Found a line!
let newline_index = offset + self.next_index;
self.next_index = 0;
let line = buf.split_to(newline_index + 1);
let line = &line[..line.len() - 1];
let line = without_carriage_return(line);
let item =
serde_json::from_slice(line).map_err(JsonRpcMessageCodecError::Serde)?;
return Ok(Some(item));
}
(false, None) if buf.len() > self.max_length => {
// Reached the maximum length without finding a
// newline, return an error and start discarding on the
// next call.
self.is_discarding = true;
return Err(JsonRpcMessageCodecError::MaxLineLengthExceeded);
}
(false, None) => {
// We didn't find a line or reach the length limit, so the next
// call will resume searching at the current offset.
self.next_index = read_to;
return Ok(None);
}
}
}
}
fn decode_eof(&mut self, buf: &mut BytesMut) -> Result<Option<T>, JsonRpcMessageCodecError> {
Ok(match self.decode(buf)? {
Some(frame) => Some(frame),
None => {
self.next_index = 0;
// No terminating newline - return remaining data, if any
if buf.is_empty() || buf == &b"\r"[..] {
None
} else {
let line = buf.split_to(buf.len());
let line = without_carriage_return(&line);
let item =
serde_json::from_slice(line).map_err(JsonRpcMessageCodecError::Serde)?;
Some(item)
}
}
})
}
}
impl<T: Serialize> Encoder<T> for JsonRpcMessageCodec<T> {
type Error = JsonRpcMessageCodecError;
fn encode(&mut self, item: T, buf: &mut BytesMut) -> Result<(), JsonRpcMessageCodecError> {
serde_json::to_writer(buf.writer(), &item)?;
buf.put_u8(b'\n');
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
#[tokio::test]
async fn test_decode() {
use futures::StreamExt;
use tokio::io::BufReader;
let data = r#"{"jsonrpc":"2.0","method":"subtract","params":[42,23],"id":1}
{"jsonrpc":"2.0","method":"subtract","params":[23,42],"id":2}
{"jsonrpc":"2.0","method":"subtract","params":[42,23],"id":3}
{"jsonrpc":"2.0","method":"subtract","params":[23,42],"id":4}
{"jsonrpc":"2.0","method":"subtract","params":[42,23],"id":5}
{"jsonrpc":"2.0","method":"subtract","params":[23,42],"id":6}
{"jsonrpc":"2.0","method":"subtract","params":[42,23],"id":7}
{"jsonrpc":"2.0","method":"subtract","params":[23,42],"id":8}
{"jsonrpc":"2.0","method":"subtract","params":[42,23],"id":9}
{"jsonrpc":"2.0","method":"subtract","params":[23,42],"id":10}
"#;
let mut cursor = BufReader::new(data.as_bytes());
let mut stream = from_async_read::<serde_json::Value, _>(&mut cursor);
for i in 1..=10 {
let item = stream.next().await.unwrap();
assert_eq!(
item,
serde_json::json!({
"jsonrpc": "2.0",
"method": "subtract",
"params": if i % 2 != 0 { [42, 23] } else { [23, 42] },
"id": i,
})
);
}
}
#[tokio::test]
async fn test_encode() {
let test_messages = vec![
serde_json::json!({
"jsonrpc": "2.0",
"method": "subtract",
"params": [42, 23],
"id": 1,
}),
serde_json::json!({
"jsonrpc": "2.0",
"method": "subtract",
"params": [23, 42],
"id": 2,
}),
];
// Create a buffer to write to
let mut buffer = Vec::new();
let mut writer = from_async_write(&mut buffer);
// Write the test messages
for message in test_messages.iter() {
writer.send(message.clone()).await.unwrap();
}
writer.close().await.unwrap();
drop(writer);
// Parse the buffer back into lines and check each one
let output = String::from_utf8_lossy(&buffer);
let mut lines = output.lines();
for expected_message in test_messages {
let line = lines.next().unwrap();
let parsed_message: serde_json::Value = serde_json::from_str(line).unwrap();
assert_eq!(parsed_message, expected_message);
}
// Make sure there are no extra lines
assert!(lines.next().is_none());
}
}

View file

@ -0,0 +1,325 @@
//! reference: https://html.spec.whatwg.org/multipage/server-sent-events.html
use crate::model::{ClientJsonRpcMessage, ServerJsonRpcMessage};
use futures::{FutureExt, Sink, Stream, StreamExt, future::BoxFuture, stream::BoxStream};
use reqwest::{
Client as HttpClient, IntoUrl, Url,
header::{ACCEPT, HeaderValue},
};
use sse_stream::{Error as SseError, Sse, SseStream};
use std::{collections::VecDeque, sync::Arc, time::Duration};
use thiserror::Error;
const MIME_TYPE: &str = "text/event-stream";
const HEADER_LAST_EVENT_ID: &str = "Last-Event-ID";
#[derive(Error, Debug)]
pub enum SseTransportError {
#[error("SSE error: {0}")]
Sse(#[from] SseError),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Reqwest error: {0}")]
Reqwest(#[from] reqwest::Error),
#[error("unexpected end of stream")]
UnexpectedEndOfStream,
#[error("Url error: {0}")]
Url(#[from] url::ParseError),
#[error("Unexpected content type: {0:?}")]
UnexpectedContentType(Option<HeaderValue>),
}
enum SseTransportState {
Connected(BoxStream<'static, Result<Sse, SseError>>),
Retrying {
times: usize,
fut: BoxFuture<
'static,
Result<BoxStream<'static, Result<Sse, SseError>>, SseTransportError>,
>,
},
Fatal {
reason: String,
},
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub struct SseTransportRetryConfig {
pub max_times: Option<usize>,
pub min_duration: Duration,
}
impl SseTransportRetryConfig {
pub const DEFAULT_MIN_DURATION: Duration = Duration::from_millis(1000);
}
impl Default for SseTransportRetryConfig {
fn default() -> Self {
Self {
max_times: None,
min_duration: Self::DEFAULT_MIN_DURATION,
}
}
}
/// # Transport for client sse
///
/// Call [`SseTransport::start`] to create a new transport from url.
///
/// Call [`SseTransport::start_with_client`] to create a new transport with a customized reqwest client.
pub struct SseTransport {
http_client: HttpClient,
state: SseTransportState,
post_url: Arc<Url>,
sse_url: Arc<Url>,
last_event_id: Option<String>,
recommended_retry_duration_ms: Option<u64>,
#[allow(clippy::type_complexity)]
request_queue: VecDeque<tokio::sync::oneshot::Receiver<Result<(), SseTransportError>>>,
pub retry_config: SseTransportRetryConfig,
}
impl SseTransport {
pub async fn start_with_timeout<U>(url: U, timeout: Duration) -> Result<Self, SseTransportError>
where
U: IntoUrl,
{
let mut client = HttpClient::builder();
client = client.timeout(timeout);
let client = client.build()?;
Self::start_with_client(url, client).await
}
pub async fn start<U>(url: U) -> Result<Self, SseTransportError>
where
U: IntoUrl,
{
Self::start_with_client(url, HttpClient::default()).await
}
/// Start with a reqwest client, this would be helpful when you want to customize the client behavior like default headers or tls stuff.
pub async fn start_with_client<U>(url: U, client: HttpClient) -> Result<Self, SseTransportError>
where
U: IntoUrl,
{
let url = url.into_url()?;
let response = client
.get(url.clone())
.header(ACCEPT, MIME_TYPE)
.send()
.await?;
let response = response.error_for_status()?;
match response.headers().get(reqwest::header::CONTENT_TYPE) {
Some(ct) => {
if !ct.as_bytes().starts_with(MIME_TYPE.as_bytes()) {
return Err(SseTransportError::UnexpectedContentType(Some(ct.clone())));
}
}
None => {
return Err(SseTransportError::UnexpectedContentType(None));
}
}
let mut event_stream = SseStream::from_byte_stream(response.bytes_stream()).boxed();
let mut last_event_id = None;
let mut retry = None;
let post_url = loop {
let next_event = event_stream
.next()
.await
.ok_or(SseTransportError::UnexpectedEndOfStream)??;
if let Some(id) = next_event.id {
last_event_id = Some(id);
}
if let Some(retry_ms) = next_event.retry {
retry = Some(retry_ms);
}
if let Some("endpoint") = next_event.event.as_deref() {
break next_event.data.unwrap_or_default();
}
};
tracing::info!("will post event on {post_url}");
let post_url = url.join(&post_url)?;
Ok(SseTransport {
http_client: client,
state: SseTransportState::Connected(Box::pin(event_stream)),
post_url: Arc::from(post_url),
last_event_id,
recommended_retry_duration_ms: retry,
sse_url: Arc::from(url),
request_queue: Default::default(),
retry_config: Default::default(),
})
}
fn retry_connection(
&self,
) -> BoxFuture<'static, Result<BoxStream<'static, Result<Sse, SseError>>, SseTransportError>>
{
let retry_duration = {
let recommended_retry_duration = self
.recommended_retry_duration_ms
.map(Duration::from_millis);
let config_retry_duration = self.retry_config.min_duration;
recommended_retry_duration
.map(|d| d.max(config_retry_duration))
.unwrap_or(config_retry_duration)
};
let client = self.http_client.clone();
let sse_url = self.sse_url.as_ref().clone();
let last_event_id = self.last_event_id.clone();
let fut = async move {
tokio::time::sleep(retry_duration).await;
let mut request_builder = client.get(sse_url).header(ACCEPT, MIME_TYPE);
if let Some(last_event_id) = last_event_id {
request_builder = request_builder.header(HEADER_LAST_EVENT_ID, last_event_id);
}
let response = request_builder.send().await?;
let response = response.error_for_status()?;
match response.headers().get(reqwest::header::CONTENT_TYPE) {
Some(ct) => {
if ct.as_bytes() != MIME_TYPE.as_bytes() {
return Err(SseTransportError::UnexpectedContentType(Some(ct.clone())));
}
}
None => {
return Err(SseTransportError::UnexpectedContentType(None));
}
}
let event_stream = SseStream::from_byte_stream(response.bytes_stream()).boxed();
Ok(event_stream)
};
fut.boxed()
}
}
impl Stream for SseTransport {
type Item = ServerJsonRpcMessage;
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
let retry_config = self.retry_config;
let state = &mut self.state;
match state {
SseTransportState::Connected(event_stream) => {
let event = std::task::ready!(event_stream.poll_next_unpin(cx));
match event {
Some(Ok(event)) => {
if let Some(retry) = event.retry {
self.recommended_retry_duration_ms = Some(retry);
}
if let Some(id) = event.id {
self.last_event_id = Some(id);
}
if let Some(data) = event.data {
match serde_json::from_str(&data) {
Ok(message) => std::task::Poll::Ready(Some(message)),
Err(e) => {
tracing::error!(error = %e, "failed to parse json rpc request");
self.poll_next(cx)
}
}
} else {
self.poll_next(cx)
}
}
Some(Err(e)) => {
tracing::error!(error = %e, "sse event stream encounter an error");
let fut = self.retry_connection();
self.state = SseTransportState::Retrying { times: 1, fut };
self.poll_next(cx)
}
None => std::task::Poll::Ready(None),
}
}
SseTransportState::Retrying { fut, times } => {
let retry_result = std::task::ready!(fut.poll_unpin(cx));
match retry_result {
Ok(stream) => {
self.state = SseTransportState::Connected(stream);
self.poll_next(cx)
}
Err(e) => {
tracing::warn!(error = %e, "retrying failed");
if let Some(max_retry_times) = retry_config.max_times {
if *times >= max_retry_times {
self.state = SseTransportState::Fatal {
reason: format!("retrying failed after {} times: {}", times, e),
};
return self.poll_next(cx);
}
}
let times = *times + 1;
let fut = self.retry_connection();
self.state = SseTransportState::Retrying { times, fut };
self.poll_next(cx)
}
}
}
SseTransportState::Fatal { reason } => {
tracing::error!("sse transport fatal error: {}", reason);
std::task::Poll::Ready(None)
}
}
}
}
impl Sink<ClientJsonRpcMessage> for SseTransport {
type Error = SseTransportError;
fn poll_ready(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> {
const QUEUE_SIZE: usize = 16;
if self.request_queue.len() >= QUEUE_SIZE {
std::task::ready!(
self.request_queue
.front_mut()
.expect("queue is not empty")
.poll_unpin(cx)
)
.expect("sender shall not drop")?;
}
std::task::Poll::Ready(Ok(()))
}
fn start_send(
mut self: std::pin::Pin<&mut Self>,
item: ClientJsonRpcMessage,
) -> Result<(), Self::Error> {
let client = self.http_client.clone();
let uri = self.post_url.clone();
let (tx, rx) = tokio::sync::oneshot::channel();
let request_builder = client.post(uri.as_ref().clone()).json(&item);
tokio::spawn(async move {
let result = request_builder
.send()
.await
.and_then(|resp| resp.error_for_status())
.map_err(SseTransportError::from)
.map(drop);
let _ = tx.send(result);
});
self.as_mut().request_queue.push_back(rx);
Ok(())
}
fn poll_flush(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> {
let queue = &mut self.as_mut().request_queue;
while let Some(fut) = queue.front_mut() {
std::task::ready!(fut.poll_unpin(cx)).expect("sender shall not drop")?;
queue.pop_front();
}
std::task::Poll::Ready(Ok(()))
}
fn poll_close(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> {
self.poll_flush(cx)
}
}

View file

@ -0,0 +1,285 @@
use crate::{
RoleServer, Service,
model::ClientJsonRpcMessage,
service::{RxJsonRpcMessage, TxJsonRpcMessage},
};
use axum::{
Json, Router,
extract::{Query, State},
http::StatusCode,
response::{
Response,
sse::{Event, Sse},
},
routing::{get, post},
};
use futures::{Sink, SinkExt, Stream, StreamExt};
use std::{collections::HashMap, net::SocketAddr};
use tokio_stream::wrappers::ReceiverStream;
use tokio_util::sync::{CancellationToken, PollSender};
use tracing::Instrument;
use std::sync::Arc;
use tokio::io;
type SessionId = Arc<str>;
type TxStore =
Arc<tokio::sync::RwLock<HashMap<SessionId, tokio::sync::mpsc::Sender<ClientJsonRpcMessage>>>>;
pub type TransportReceiver = ReceiverStream<RxJsonRpcMessage<RoleServer>>;
#[derive(Clone)]
struct App {
txs: TxStore,
transport_tx: tokio::sync::mpsc::UnboundedSender<SseServerTransport>,
post_path: Arc<str>,
}
impl App {
pub fn new(
post_path: String,
) -> (
Self,
tokio::sync::mpsc::UnboundedReceiver<SseServerTransport>,
) {
let (transport_tx, tranport_rx) = tokio::sync::mpsc::unbounded_channel();
(
Self {
txs: Default::default(),
transport_tx,
post_path: post_path.into(),
},
tranport_rx,
)
}
}
fn session_id() -> SessionId {
let id = format!("{:016x}", rand::random::<u128>());
Arc::from(id)
}
#[derive(Debug, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PostEventQuery {
pub session_id: String,
}
async fn post_event_handler(
State(app): State<App>,
Query(PostEventQuery { session_id }): Query<PostEventQuery>,
Json(message): Json<ClientJsonRpcMessage>,
) -> Result<StatusCode, StatusCode> {
tracing::debug!(session_id, ?message, "new client message");
let tx = {
let rg = app.txs.read().await;
rg.get(session_id.as_str())
.ok_or(StatusCode::NOT_FOUND)?
.clone()
};
if tx.send(message).await.is_err() {
tracing::error!("send message error");
return Err(StatusCode::GONE);
}
Ok(StatusCode::ACCEPTED)
}
async fn sse_handler(
State(app): State<App>,
) -> Result<Sse<impl Stream<Item = Result<Event, io::Error>>>, Response<String>> {
let session = session_id();
tracing::info!(%session, "sse connection");
use tokio_stream::wrappers::ReceiverStream;
use tokio_util::sync::PollSender;
let (from_client_tx, from_client_rx) = tokio::sync::mpsc::channel(64);
let (to_client_tx, to_client_rx) = tokio::sync::mpsc::channel(64);
app.txs
.write()
.await
.insert(session.clone(), from_client_tx);
let session = session.clone();
let stream = ReceiverStream::new(from_client_rx);
let sink = PollSender::new(to_client_tx);
let transport = SseServerTransport {
stream,
sink,
session_id: session.clone(),
tx_store: app.txs.clone(),
};
let transport_send_result = app.transport_tx.send(transport);
if transport_send_result.is_err() {
tracing::warn!("send transport out error");
let mut response =
Response::new("fail to send out trasnport, it seems server is closed".to_string());
*response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
return Err(response);
}
let post_path = app.post_path.as_ref();
let stream = futures::stream::once(futures::future::ok(
Event::default()
.event("endpoint")
.data(format!("{post_path}?sessionId={session}")),
))
.chain(ReceiverStream::new(to_client_rx).map(|message| {
match serde_json::to_string(&message) {
Ok(bytes) => Ok(Event::default().event("message").data(&bytes)),
Err(e) => Err(io::Error::new(io::ErrorKind::InvalidData, e)),
}
}));
Ok(Sse::new(stream))
}
pub struct SseServerTransport {
stream: ReceiverStream<RxJsonRpcMessage<RoleServer>>,
sink: PollSender<TxJsonRpcMessage<RoleServer>>,
session_id: SessionId,
tx_store: TxStore,
}
impl Sink<TxJsonRpcMessage<RoleServer>> for SseServerTransport {
type Error = io::Error;
fn poll_ready(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> {
self.sink
.poll_ready_unpin(cx)
.map_err(std::io::Error::other)
}
fn start_send(
mut self: std::pin::Pin<&mut Self>,
item: TxJsonRpcMessage<RoleServer>,
) -> Result<(), Self::Error> {
self.sink
.start_send_unpin(item)
.map_err(std::io::Error::other)
}
fn poll_flush(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> {
self.sink
.poll_flush_unpin(cx)
.map_err(std::io::Error::other)
}
fn poll_close(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> {
let inner_close_result = self
.sink
.poll_close_unpin(cx)
.map_err(std::io::Error::other);
if inner_close_result.is_ready() {
let session_id = self.session_id.clone();
let tx_store = self.tx_store.clone();
tokio::spawn(async move {
tx_store.write().await.remove(&session_id);
});
}
inner_close_result
}
}
impl Stream for SseServerTransport {
type Item = RxJsonRpcMessage<RoleServer>;
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
self.stream.poll_next_unpin(cx)
}
}
#[derive(Debug, Clone)]
pub struct SseServerConfig {
pub bind: SocketAddr,
pub sse_path: String,
pub post_path: String,
pub ct: CancellationToken,
}
#[derive(Debug)]
pub struct SseServer {
transport_rx: tokio::sync::mpsc::UnboundedReceiver<SseServerTransport>,
pub config: SseServerConfig,
}
impl SseServer {
pub async fn serve(bind: SocketAddr) -> io::Result<Self> {
Self::serve_with_config(SseServerConfig {
bind,
sse_path: "/sse".to_string(),
post_path: "/message".to_string(),
ct: CancellationToken::new(),
})
.await
}
pub async fn serve_with_config(config: SseServerConfig) -> io::Result<Self> {
let (app, transport_rx) = App::new(config.post_path.clone());
let listener = tokio::net::TcpListener::bind(config.bind).await?;
let service = Router::new()
.route(&config.sse_path, get(sse_handler))
.route(&config.post_path, post(post_event_handler))
.with_state(app);
let ct = config.ct.child_token();
let server = axum::serve(listener, service).with_graceful_shutdown(async move {
ct.cancelled().await;
tracing::info!("sse server cancelled");
});
tokio::spawn(
async move {
if let Err(e) = server.await {
tracing::error!(error = %e, "sse server shutdown with error");
}
}
.instrument(tracing::info_span!("sse-server", bind_address = %config.bind)),
);
Ok(Self {
transport_rx,
config,
})
}
pub fn with_service<S, F>(mut self, service_provider: F) -> CancellationToken
where
S: Service<RoleServer>,
F: Fn() -> S + Send + 'static,
{
use crate::service::ServiceExt;
let ct = self.config.ct.clone();
tokio::spawn(async move {
while let Some(transport) = self.next_transport().await {
let service = service_provider();
let ct = self.config.ct.child_token();
tokio::spawn(async move {
let server = service.serve_with_ct(transport, ct).await?;
server.waiting().await?;
tokio::io::Result::Ok(())
});
}
});
ct
}
pub fn cancel(&self) {
self.config.ct.cancel();
}
pub async fn next_transport(&mut self) -> Option<SseServerTransport> {
self.transport_rx.recv().await
}
}
impl Stream for SseServer {
type Item = SseServerTransport;
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
self.transport_rx.poll_recv(cx)
}
}

View file

@ -0,0 +1,174 @@
//! 写这一段原本是为了兼容官方sdk的tower-service这一套,但是想想还是算了,如果仅仅是请求-响应,都完成不了基本任务。要一个完成不了基本的任务的传输层干嘛?
use std::{collections::VecDeque, task::ready};
use crate::service::{RxJsonRpcMessage, ServiceRole, TxJsonRpcMessage};
use super::IntoTransport;
use futures::{Sink, Stream};
use tower_service::Service as TowerService;
pub enum TransportAdapterTower {}
impl<R, E, S> IntoTransport<R, E, TransportAdapterTower> for S
where
S: TowerService<TxJsonRpcMessage<R>, Response = RxJsonRpcMessage<R>, Error = E>
+ Unpin
+ Send
+ 'static,
R: ServiceRole,
E: std::error::Error + Send + 'static,
S::Future: Send + 'static,
S::Response: Send + 'static,
{
fn into_transport(
self,
) -> (
impl Sink<TxJsonRpcMessage<R>, Error = E> + Send + 'static,
impl Stream<Item = RxJsonRpcMessage<R>> + Send + 'static,
) {
let sink = TowerServiceSink {
service: self,
state: TowerServiceSinkState::Ready::<R, S>,
rx_wakers: VecDeque::new(),
tx_wakers: VecDeque::new(),
};
IntoTransport::<R, _, ()>::into_transport(sink)
}
}
pin_project_lite::pin_project! {
pub struct TowerServiceSink<R: ServiceRole, S: TowerService<TxJsonRpcMessage<R>>> {
#[pin]
service: S,
#[pin]
state: TowerServiceSinkState<R, S>,
rx_wakers: VecDeque<std::task::Waker>,
tx_wakers: VecDeque<std::task::Waker>,
}
}
pin_project_lite::pin_project! {
#[project = TowerServiceSinkStateProj]
pub enum TowerServiceSinkState<R: ServiceRole, S: TowerService<TxJsonRpcMessage<R>>> {
Ready,
Sending {
#[pin]
fut: S::Future,
},
Yield {
output: Option<S::Response>,
}
}
}
impl<R, S> Sink<TxJsonRpcMessage<R>> for TowerServiceSink<R, S>
where
R: ServiceRole,
S: TowerService<TxJsonRpcMessage<R>> + Unpin,
S::Future: Send + 'static,
S::Response: Send + 'static,
S::Error: Send + 'static,
{
type Error = S::Error;
fn poll_ready(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> {
let mut this = self.as_mut().project();
let service = this.service;
ready!(service.get_mut().poll_ready(cx)?);
match this.state.as_mut().project() {
TowerServiceSinkStateProj::Ready => std::task::Poll::Ready(Ok(())),
TowerServiceSinkStateProj::Sending { fut } => {
ready!(fut.poll(cx))?;
this.state.set(TowerServiceSinkState::Ready);
std::task::Poll::Ready(Ok(()))
}
TowerServiceSinkStateProj::Yield { .. } => {
this.state.set(TowerServiceSinkState::Ready);
this.rx_wakers.drain(..).for_each(|waker| waker.wake());
this.tx_wakers.push_back(cx.waker().clone());
std::task::Poll::Pending
}
}
}
fn start_send(
mut self: std::pin::Pin<&mut Self>,
item: TxJsonRpcMessage<R>,
) -> Result<(), Self::Error> {
let mut this = self.as_mut().project();
let service = this.service;
if let TxJsonRpcMessage::<R>::Request(req) = &item {
let fut = service.get_mut().call(item);
this.state.set(TowerServiceSinkState::Sending { fut });
} else {
tracing::debug!(message = ?item, "omit notification due to the transport layer limit");
this.state.set(TowerServiceSinkState::Ready);
}
Ok(())
}
fn poll_flush(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> {
let mut this = self.as_mut().project();
let service = this.service;
ready!(service.get_mut().poll_ready(cx)?);
loop {
match this.state.as_mut().project() {
TowerServiceSinkStateProj::Ready => return std::task::Poll::Ready(Ok(())),
TowerServiceSinkStateProj::Sending { fut } => {
let output = ready!(fut.poll(cx))?;
this.state.set(TowerServiceSinkState::Yield {
output: Some(output),
});
}
TowerServiceSinkStateProj::Yield { .. } => {
this.state.set(TowerServiceSinkState::Ready);
this.rx_wakers.drain(..).for_each(|waker| waker.wake());
return std::task::Poll::Ready(Ok(()));
}
}
}
}
fn poll_close(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> {
self.poll_flush(cx)
}
}
impl<R, S> Stream for TowerServiceSink<R, S>
where
R: ServiceRole,
S: TowerService<TxJsonRpcMessage<R>, Response = RxJsonRpcMessage<R>>,
{
type Item = S::Response;
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
let mut this = self.as_mut().project();
match this.state.as_mut().project() {
TowerServiceSinkStateProj::Ready => {
this.rx_wakers.push_back(cx.waker().clone());
std::task::Poll::Pending
}
TowerServiceSinkStateProj::Sending { .. } => {
this.rx_wakers.push_back(cx.waker().clone());
std::task::Poll::Pending
}
TowerServiceSinkStateProj::Yield { output } => {
this.tx_wakers.drain(..).for_each(|waker| waker.wake());
let output = output.take().expect("should have an output");
this.state.set(TowerServiceSinkState::Ready);
std::task::Poll::Ready(Some(output))
}
}
}
}

View file

@ -0,0 +1 @@
// Maybe we don't really need a ws implementation?