Initial Dever 0.0.1
This commit is contained in:
commit
81233d098c
1479 files changed
+163782
No files matched your search
@@ -0,0 +1,212 @@
|
||||
// 独立 Rust 共享会话参照,不调用 Dever 运行库;覆盖收发、期限、关闭和所有权退出。
|
||||
// TCP/WebSocket 握手在计时外完成;此参照不代表 TLS、HTTP 升级或整个 Scope 系统。
|
||||
mod wire;
|
||||
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
future::Future,
|
||||
sync::{
|
||||
Arc, Mutex,
|
||||
atomic::{AtomicBool, Ordering},
|
||||
},
|
||||
time::Duration,
|
||||
};
|
||||
use tokio::{net::TcpStream, sync::Mutex as Direction};
|
||||
use tokio_tungstenite::{
|
||||
WebSocketStream,
|
||||
tungstenite::{
|
||||
Message,
|
||||
protocol::{CloseFrame, frame::coding::CloseCode},
|
||||
},
|
||||
};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use wire::Wire;
|
||||
|
||||
pub type Error = Cow<'static, str>;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Limits {
|
||||
pub message: usize,
|
||||
pub idle: Duration,
|
||||
pub write: Duration,
|
||||
}
|
||||
|
||||
struct State {
|
||||
socket: Mutex<Option<WebSocketStream<TcpStream>>>,
|
||||
reader: Direction<()>,
|
||||
writer: Direction<()>,
|
||||
closing: CancellationToken,
|
||||
ended: AtomicBool,
|
||||
limits: Limits,
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn abort(&self) {
|
||||
self.closing.cancel();
|
||||
self.socket.lock().unwrap().take();
|
||||
}
|
||||
|
||||
async fn active<Value>(
|
||||
&self,
|
||||
work: impl Future<Output = Result<Value, Error>>,
|
||||
) -> Result<Value, Error> {
|
||||
tokio::select! {
|
||||
biased;
|
||||
() = self.closing.cancelled() => Err("WebSocket is closing".into()),
|
||||
result = work => result,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 所有者退出同步释放协议连接;保留别名不能延长连接生命。
|
||||
pub struct Owner(Arc<State>);
|
||||
impl Drop for Owner {
|
||||
fn drop(&mut self) {
|
||||
self.0.abort();
|
||||
}
|
||||
}
|
||||
|
||||
struct Writing<'socket>(Option<&'socket State>);
|
||||
impl Drop for Writing<'_> {
|
||||
fn drop(&mut self) {
|
||||
if let Some(state) = self.0 {
|
||||
state.abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Socket(Arc<State>);
|
||||
|
||||
impl Socket {
|
||||
pub fn new(socket: WebSocketStream<TcpStream>, limits: Limits) -> (Self, Owner) {
|
||||
let state = Arc::new(State {
|
||||
socket: Mutex::new(Some(socket)),
|
||||
reader: Direction::new(()),
|
||||
writer: Direction::new(()),
|
||||
closing: CancellationToken::new(),
|
||||
ended: AtomicBool::new(false),
|
||||
limits,
|
||||
});
|
||||
(Self(state.clone()), Owner(state))
|
||||
}
|
||||
|
||||
pub async fn send(&self, message: Message) -> Result<(), Error> {
|
||||
let limit = match message {
|
||||
Message::Text(_) | Message::Binary(_) => self.0.limits.message,
|
||||
Message::Ping(_) | Message::Pong(_) => 125,
|
||||
_ => return Err("use close for the closing handshake".into()),
|
||||
};
|
||||
if message.len() > limit {
|
||||
return Err("WebSocket message exceeds byte limit".into());
|
||||
}
|
||||
self.0
|
||||
.active(async {
|
||||
tokio::time::timeout(self.0.limits.write, async {
|
||||
let _writer = self.0.writer.lock().await;
|
||||
let mut writing = Writing(Some(&self.0));
|
||||
Wire(&self.0.socket).send(message).await.map_err(failure)?;
|
||||
writing.0 = None;
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
.map_err(|_| Error::from("WebSocket write timed out"))?
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn receive(&self) -> Result<Option<Message>, Error> {
|
||||
if self.0.ended.load(Ordering::Acquire) {
|
||||
return Ok(None);
|
||||
}
|
||||
let result = self
|
||||
.0
|
||||
.active(async {
|
||||
tokio::time::timeout(self.0.limits.idle, async {
|
||||
let _reader = self.0.reader.lock().await;
|
||||
if self.0.ended.load(Ordering::Acquire) {
|
||||
return Ok(None);
|
||||
}
|
||||
let message = Wire(&self.0.socket)
|
||||
.next()
|
||||
.await
|
||||
.transpose()
|
||||
.map_err(failure)?;
|
||||
match message {
|
||||
Some(Message::Ping(_)) => {
|
||||
self.reply().await?;
|
||||
Ok(message)
|
||||
}
|
||||
Some(Message::Close(_)) | None => {
|
||||
let _closing = Writing(Some(&self.0));
|
||||
self.reply().await?;
|
||||
self.0.ended.store(true, Ordering::Release);
|
||||
Ok(None)
|
||||
}
|
||||
_ => Ok(message),
|
||||
}
|
||||
})
|
||||
.await
|
||||
.map_err(|_| Error::from("WebSocket receive timed out"))?
|
||||
})
|
||||
.await;
|
||||
if result.is_err() && !self.0.closing.is_cancelled() {
|
||||
self.0.abort();
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
async fn reply(&self) -> Result<(), Error> {
|
||||
let _writer = self.0.writer.lock().await;
|
||||
let mut writing = Writing(Some(&self.0));
|
||||
tokio::time::timeout(self.0.limits.write, Wire(&self.0.socket).flush())
|
||||
.await
|
||||
.map_err(|_| Error::from("WebSocket reply timed out"))?
|
||||
.map_err(failure)?;
|
||||
writing.0 = None;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn close(&self, code: u16, reason: &str) -> Result<(), Error> {
|
||||
let code = CloseCode::from(code);
|
||||
if !code.is_allowed() || reason.len() > 123 {
|
||||
return Err("invalid WebSocket close frame".into());
|
||||
}
|
||||
if self.0.closing.is_cancelled() {
|
||||
return Err("WebSocket is closed".into());
|
||||
}
|
||||
self.0.closing.cancel();
|
||||
let _closing = Writing(Some(&self.0));
|
||||
tokio::time::timeout(self.0.limits.write, async {
|
||||
let writer = self.0.writer.lock().await;
|
||||
Wire(&self.0.socket)
|
||||
.send(Message::Close(Some(CloseFrame {
|
||||
code,
|
||||
reason: reason.to_owned().into(),
|
||||
})))
|
||||
.await
|
||||
.map_err(failure)?;
|
||||
drop(writer);
|
||||
let _reader = self.0.reader.lock().await;
|
||||
loop {
|
||||
let message = Wire(&self.0.socket)
|
||||
.next()
|
||||
.await
|
||||
.transpose()
|
||||
.map_err(failure)?;
|
||||
Wire(&self.0.socket).flush().await.map_err(failure)?;
|
||||
if matches!(message, Some(Message::Close(_)) | None) {
|
||||
self.0.ended.store(true, Ordering::Release);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
})
|
||||
.await
|
||||
.map_err(|_| Error::from("WebSocket close timed out"))?
|
||||
}
|
||||
}
|
||||
|
||||
fn failure(error: tokio_tungstenite::tungstenite::Error) -> Error {
|
||||
error.to_string().into()
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
use futures_util::{Sink, Stream};
|
||||
use std::{
|
||||
pin::Pin,
|
||||
sync::Mutex,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_tungstenite::{
|
||||
WebSocketStream,
|
||||
tungstenite::{Error, Message},
|
||||
};
|
||||
|
||||
// 共享引用仅在一次协议调用内借用连接;send/next 的状态机由 futures-util 提供。
|
||||
pub(super) struct Wire<'socket>(pub &'socket Mutex<Option<WebSocketStream<TcpStream>>>);
|
||||
|
||||
impl Wire<'_> {
|
||||
fn with<Value>(
|
||||
&self,
|
||||
call: impl FnOnce(Pin<&mut WebSocketStream<TcpStream>>) -> Value,
|
||||
) -> Result<Value, Error> {
|
||||
let mut socket = self.0.lock().unwrap();
|
||||
socket
|
||||
.as_mut()
|
||||
.map(|socket| call(Pin::new(socket)))
|
||||
.ok_or(Error::ConnectionClosed)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sink<Message> for Wire<'_> {
|
||||
type Error = Error;
|
||||
|
||||
fn poll_ready(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Result<(), Error>> {
|
||||
self.with(|socket| socket.poll_ready(context))?
|
||||
}
|
||||
|
||||
fn start_send(self: Pin<&mut Self>, message: Message) -> Result<(), Error> {
|
||||
self.with(|socket| socket.start_send(message))?
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Result<(), Error>> {
|
||||
self.with(|socket| socket.poll_flush(context))?
|
||||
}
|
||||
|
||||
fn poll_close(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Result<(), Error>> {
|
||||
self.with(|socket| socket.poll_close(context))?
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for Wire<'_> {
|
||||
type Item = Result<Message, Error>;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
match self.with(|socket| socket.poll_next(context)) {
|
||||
Ok(result) => result,
|
||||
Err(error) => Poll::Ready(Some(Err(error))),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user