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:
parent
31493cfbcc
commit
0be6067326
6 changed files with 8676 additions and 4466 deletions
91
crates/rmcp/src/transport/child_process.rs
Normal file
91
crates/rmcp/src/transport/child_process.rs
Normal 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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
316
crates/rmcp/src/transport/io.rs
Normal file
316
crates/rmcp/src/transport/io.rs
Normal 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());
|
||||
}
|
||||
}
|
||||
325
crates/rmcp/src/transport/sse.rs
Normal file
325
crates/rmcp/src/transport/sse.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
285
crates/rmcp/src/transport/sse_server.rs
Normal file
285
crates/rmcp/src/transport/sse_server.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
174
crates/rmcp/src/transport/tower.rs
Normal file
174
crates/rmcp/src/transport/tower.rs
Normal 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1
crates/rmcp/src/transport/ws.rs
Normal file
1
crates/rmcp/src/transport/ws.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
// Maybe we don't really need a ws implementation?
|
||||
Loading…
Reference in a new issue