mirror of
https://github.com/fafhrd91/actix-net
synced 2024-11-23 22:51:07 +01:00
add custom framed dispatcher service
This commit is contained in:
parent
07708c5e9a
commit
205cac82ce
@ -24,6 +24,7 @@ members = [
|
|||||||
"actix-test-server",
|
"actix-test-server",
|
||||||
"actix-threadpool",
|
"actix-threadpool",
|
||||||
"actix-tower",
|
"actix-tower",
|
||||||
|
"actix-ioframe",
|
||||||
"actix-utils",
|
"actix-utils",
|
||||||
"router",
|
"router",
|
||||||
]
|
]
|
||||||
|
5
actix-ioframe/CHANGES.md
Normal file
5
actix-ioframe/CHANGES.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# Changes
|
||||||
|
|
||||||
|
## [0.1.0] - 2019-xx-xx
|
||||||
|
|
||||||
|
* Initial release
|
30
actix-ioframe/Cargo.toml
Normal file
30
actix-ioframe/Cargo.toml
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
[package]
|
||||||
|
name = "actix-ioframe"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
|
description = "Actix framed service"
|
||||||
|
keywords = ["network", "framework", "async", "futures"]
|
||||||
|
homepage = "https://actix.rs"
|
||||||
|
repository = "https://github.com/actix/actix-net.git"
|
||||||
|
documentation = "https://docs.rs/actix-ioframed/"
|
||||||
|
categories = ["network-programming", "asynchronous"]
|
||||||
|
license = "MIT/Apache-2.0"
|
||||||
|
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
|
||||||
|
edition = "2018"
|
||||||
|
workspace = ".."
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "actix_ioframe"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
actix-service = "0.4.0"
|
||||||
|
actix-codec = "0.1.1"
|
||||||
|
bytes = "0.4"
|
||||||
|
either = "1.5.2"
|
||||||
|
futures = "0.1.25"
|
||||||
|
tokio-current-thread = "0.1.4"
|
||||||
|
log = "0.4"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
actix-rt = "0.2.2"
|
1
actix-ioframe/LICENSE-APACHE
Symbolic link
1
actix-ioframe/LICENSE-APACHE
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../LICENSE-APACHE
|
1
actix-ioframe/LICENSE-MIT
Symbolic link
1
actix-ioframe/LICENSE-MIT
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../LICENSE-MIT
|
39
actix-ioframe/src/cell.rs
Normal file
39
actix-ioframe/src/cell.rs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
//! Custom cell impl
|
||||||
|
|
||||||
|
use std::cell::UnsafeCell;
|
||||||
|
use std::fmt;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
pub(crate) struct Cell<T> {
|
||||||
|
inner: Rc<UnsafeCell<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Clone for Cell<T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: self.inner.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: fmt::Debug> fmt::Debug for Cell<T> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
self.inner.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Cell<T> {
|
||||||
|
pub fn new(inner: T) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Rc::new(UnsafeCell::new(inner)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_ref(&self) -> &T {
|
||||||
|
unsafe { &*self.inner.as_ref().get() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_mut(&mut self) -> &mut T {
|
||||||
|
unsafe { &mut *self.inner.as_ref().get() }
|
||||||
|
}
|
||||||
|
}
|
122
actix-ioframe/src/connect.rs
Normal file
122
actix-ioframe/src/connect.rs
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed};
|
||||||
|
use futures::unsync::mpsc;
|
||||||
|
|
||||||
|
use crate::cell::Cell;
|
||||||
|
use crate::dispatcher::FramedMessage;
|
||||||
|
use crate::sink::Sink;
|
||||||
|
|
||||||
|
pub struct Connect<Io, St = (), Codec = ()> {
|
||||||
|
io: Io,
|
||||||
|
codec: Codec,
|
||||||
|
state: St,
|
||||||
|
// rx: mpsc::UnboundedReceiver<FramedMessage<<Codec as Encoder>::Item>>,
|
||||||
|
// sink: Sink<<Codec as Encoder>::Item>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io> Connect<Io> {
|
||||||
|
pub(crate) fn new(io: Io) -> Self {
|
||||||
|
Self {
|
||||||
|
io,
|
||||||
|
codec: (),
|
||||||
|
state: (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io, S, C> Connect<Io, S, C> {
|
||||||
|
pub fn codec<Codec>(self, codec: Codec) -> Connect<Io, S, Codec> {
|
||||||
|
Connect {
|
||||||
|
codec,
|
||||||
|
io: self.io,
|
||||||
|
state: self.state,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn state<St>(self, state: St) -> Connect<Io, St, C> {
|
||||||
|
Connect {
|
||||||
|
state,
|
||||||
|
io: self.io,
|
||||||
|
codec: self.codec,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn state_fn<St, F>(self, f: F) -> Connect<Io, St, C>
|
||||||
|
where
|
||||||
|
F: FnOnce(&Connect<Io, S, C>) -> St,
|
||||||
|
{
|
||||||
|
Connect {
|
||||||
|
state: f(&self),
|
||||||
|
io: self.io,
|
||||||
|
codec: self.codec,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io, S, C> Connect<Io, S, C>
|
||||||
|
where
|
||||||
|
C: Encoder + Decoder,
|
||||||
|
Io: AsyncRead + AsyncWrite,
|
||||||
|
{
|
||||||
|
pub fn into_result(self) -> ConnectResult<Io, S, C> {
|
||||||
|
let (tx, rx) = mpsc::unbounded();
|
||||||
|
let sink = Sink::new(tx);
|
||||||
|
|
||||||
|
ConnectResult {
|
||||||
|
state: Cell::new(self.state),
|
||||||
|
framed: Framed::new(self.io, self.codec),
|
||||||
|
rx,
|
||||||
|
sink,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ConnectResult<Io, St, Codec: Encoder + Decoder> {
|
||||||
|
pub(crate) state: Cell<St>,
|
||||||
|
pub(crate) framed: Framed<Io, Codec>,
|
||||||
|
pub(crate) rx: mpsc::UnboundedReceiver<FramedMessage<<Codec as Encoder>::Item>>,
|
||||||
|
pub(crate) sink: Sink<<Codec as Encoder>::Item>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io, St, Codec: Encoder + Decoder> ConnectResult<Io, St, Codec> {
|
||||||
|
#[inline]
|
||||||
|
pub fn sink(&self) -> &Sink<<Codec as Encoder>::Item> {
|
||||||
|
&self.sink
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io, St, Codec> futures::Stream for ConnectResult<Io, St, Codec>
|
||||||
|
where
|
||||||
|
Io: AsyncRead + AsyncWrite,
|
||||||
|
Codec: Encoder + Decoder,
|
||||||
|
{
|
||||||
|
type Item = <Codec as Decoder>::Item;
|
||||||
|
type Error = <Codec as Decoder>::Error;
|
||||||
|
|
||||||
|
fn poll(&mut self) -> futures::Poll<Option<Self::Item>, Self::Error> {
|
||||||
|
self.framed.poll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io, St, Codec> futures::Sink for ConnectResult<Io, St, Codec>
|
||||||
|
where
|
||||||
|
Io: AsyncRead + AsyncWrite,
|
||||||
|
Codec: Encoder + Decoder,
|
||||||
|
{
|
||||||
|
type SinkItem = <Codec as Encoder>::Item;
|
||||||
|
type SinkError = <Codec as Encoder>::Error;
|
||||||
|
|
||||||
|
fn start_send(
|
||||||
|
&mut self,
|
||||||
|
item: Self::SinkItem,
|
||||||
|
) -> futures::StartSend<Self::SinkItem, Self::SinkError> {
|
||||||
|
self.framed.start_send(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_complete(&mut self) -> futures::Poll<(), Self::SinkError> {
|
||||||
|
self.framed.poll_complete()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn close(&mut self) -> futures::Poll<(), Self::SinkError> {
|
||||||
|
self.framed.close()
|
||||||
|
}
|
||||||
|
}
|
302
actix-ioframe/src/dispatcher.rs
Normal file
302
actix-ioframe/src/dispatcher.rs
Normal file
@ -0,0 +1,302 @@
|
|||||||
|
//! Framed dispatcher service and related utilities
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
|
use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed};
|
||||||
|
use actix_service::{IntoService, Service};
|
||||||
|
use futures::task::AtomicTask;
|
||||||
|
use futures::unsync::{mpsc, oneshot};
|
||||||
|
use futures::{Async, Future, Poll, Sink as FutureSink, Stream};
|
||||||
|
use log::debug;
|
||||||
|
|
||||||
|
use crate::cell::Cell;
|
||||||
|
use crate::error::ServiceError;
|
||||||
|
use crate::item::Item;
|
||||||
|
use crate::sink::Sink;
|
||||||
|
|
||||||
|
type Request<S, U> = Item<S, U>;
|
||||||
|
type Response<U> = <U as Encoder>::Item;
|
||||||
|
|
||||||
|
pub(crate) enum FramedMessage<T> {
|
||||||
|
Message(T),
|
||||||
|
Close,
|
||||||
|
WaitClose(oneshot::Sender<()>),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// FramedTransport - is a future that reads frames from Framed object
|
||||||
|
/// and pass then to the service.
|
||||||
|
pub(crate) struct FramedDispatcher<St, S, T, U>
|
||||||
|
where
|
||||||
|
S: Service<Request = Request<St, U>, Response = Option<Response<U>>>,
|
||||||
|
S::Error: 'static,
|
||||||
|
S::Future: 'static,
|
||||||
|
T: AsyncRead + AsyncWrite,
|
||||||
|
U: Encoder + Decoder,
|
||||||
|
<U as Encoder>::Item: 'static,
|
||||||
|
<U as Encoder>::Error: std::fmt::Debug,
|
||||||
|
{
|
||||||
|
service: S,
|
||||||
|
sink: Sink<<U as Encoder>::Item>,
|
||||||
|
state: Cell<St>,
|
||||||
|
dispatch_state: State<S, U>,
|
||||||
|
framed: Framed<T, U>,
|
||||||
|
rx: Option<mpsc::UnboundedReceiver<FramedMessage<<U as Encoder>::Item>>>,
|
||||||
|
inner: Cell<FramedDispatcherInner<<U as Encoder>::Item, S::Error>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<St, S, T, U> FramedDispatcher<St, S, T, U>
|
||||||
|
where
|
||||||
|
S: Service<Request = Request<St, U>, Response = Option<Response<U>>>,
|
||||||
|
S::Error: 'static,
|
||||||
|
S::Future: 'static,
|
||||||
|
T: AsyncRead + AsyncWrite,
|
||||||
|
U: Decoder + Encoder,
|
||||||
|
<U as Encoder>::Item: 'static,
|
||||||
|
<U as Encoder>::Error: std::fmt::Debug,
|
||||||
|
{
|
||||||
|
pub(crate) fn new<F: IntoService<S>>(
|
||||||
|
framed: Framed<T, U>,
|
||||||
|
state: Cell<St>,
|
||||||
|
service: F,
|
||||||
|
rx: mpsc::UnboundedReceiver<FramedMessage<<U as Encoder>::Item>>,
|
||||||
|
sink: Sink<<U as Encoder>::Item>,
|
||||||
|
) -> Self {
|
||||||
|
FramedDispatcher {
|
||||||
|
framed,
|
||||||
|
state,
|
||||||
|
sink,
|
||||||
|
rx: Some(rx),
|
||||||
|
service: service.into_service(),
|
||||||
|
dispatch_state: State::Processing,
|
||||||
|
inner: Cell::new(FramedDispatcherInner {
|
||||||
|
buf: VecDeque::new(),
|
||||||
|
task: AtomicTask::new(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum State<S: Service, U: Encoder + Decoder> {
|
||||||
|
Processing,
|
||||||
|
Error(ServiceError<S::Error, U>),
|
||||||
|
FramedError(ServiceError<S::Error, U>),
|
||||||
|
FlushAndStop(Vec<oneshot::Sender<()>>),
|
||||||
|
Stopping,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: Service, U: Encoder + Decoder> State<S, U> {
|
||||||
|
fn stop(&mut self, tx: Option<oneshot::Sender<()>>) {
|
||||||
|
match self {
|
||||||
|
State::FlushAndStop(ref mut vec) => {
|
||||||
|
if let Some(tx) = tx {
|
||||||
|
vec.push(tx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
State::Processing => {
|
||||||
|
*self = State::FlushAndStop(if let Some(tx) = tx {
|
||||||
|
vec![tx]
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
State::Error(_) | State::FramedError(_) | State::Stopping => {
|
||||||
|
if let Some(tx) = tx {
|
||||||
|
let _ = tx.send(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FramedDispatcherInner<I, E> {
|
||||||
|
buf: VecDeque<Result<I, E>>,
|
||||||
|
task: AtomicTask,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<St, S, T, U> FramedDispatcher<St, S, T, U>
|
||||||
|
where
|
||||||
|
S: Service<Request = Request<St, U>, Response = Option<Response<U>>>,
|
||||||
|
S::Error: 'static,
|
||||||
|
S::Future: 'static,
|
||||||
|
T: AsyncRead + AsyncWrite,
|
||||||
|
U: Decoder + Encoder,
|
||||||
|
<U as Encoder>::Item: 'static,
|
||||||
|
<U as Encoder>::Error: std::fmt::Debug,
|
||||||
|
{
|
||||||
|
fn poll_read(&mut self) -> bool {
|
||||||
|
loop {
|
||||||
|
match self.service.poll_ready() {
|
||||||
|
Ok(Async::Ready(_)) => {
|
||||||
|
let item = match self.framed.poll() {
|
||||||
|
Ok(Async::Ready(Some(el))) => el,
|
||||||
|
Err(err) => {
|
||||||
|
self.dispatch_state =
|
||||||
|
State::FramedError(ServiceError::Decoder(err));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Ok(Async::NotReady) => return false,
|
||||||
|
Ok(Async::Ready(None)) => {
|
||||||
|
self.dispatch_state = State::Stopping;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut cell = self.inner.clone();
|
||||||
|
cell.get_mut().task.register();
|
||||||
|
tokio_current_thread::spawn(
|
||||||
|
self.service
|
||||||
|
.call(Item::new(self.state.clone(), self.sink.clone(), item))
|
||||||
|
.then(move |item| {
|
||||||
|
let item = match item {
|
||||||
|
Ok(Some(item)) => Ok(item),
|
||||||
|
Ok(None) => return Ok(()),
|
||||||
|
Err(err) => Err(err),
|
||||||
|
};
|
||||||
|
let inner = cell.get_mut();
|
||||||
|
inner.buf.push_back(item);
|
||||||
|
inner.task.notify();
|
||||||
|
Ok(())
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(Async::NotReady) => return false,
|
||||||
|
Err(err) => {
|
||||||
|
self.dispatch_state = State::Error(ServiceError::Service(err));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// write to framed object
|
||||||
|
fn poll_write(&mut self) -> bool {
|
||||||
|
let inner = self.inner.get_mut();
|
||||||
|
let mut rx_done = self.rx.is_none();
|
||||||
|
let mut buf_empty = inner.buf.is_empty();
|
||||||
|
loop {
|
||||||
|
while !self.framed.is_write_buf_full() {
|
||||||
|
if !buf_empty {
|
||||||
|
match inner.buf.pop_front().unwrap() {
|
||||||
|
Ok(msg) => {
|
||||||
|
if let Err(err) = self.framed.force_send(msg) {
|
||||||
|
self.dispatch_state =
|
||||||
|
State::FramedError(ServiceError::Encoder(err));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
buf_empty = inner.buf.is_empty();
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
self.dispatch_state = State::Error(ServiceError::Service(err));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !rx_done && self.rx.is_some() {
|
||||||
|
match self.rx.as_mut().unwrap().poll() {
|
||||||
|
Ok(Async::Ready(Some(FramedMessage::Message(msg)))) => {
|
||||||
|
if let Err(err) = self.framed.force_send(msg) {
|
||||||
|
self.dispatch_state =
|
||||||
|
State::FramedError(ServiceError::Encoder(err));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Async::Ready(Some(FramedMessage::Close))) => {
|
||||||
|
self.dispatch_state.stop(None);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Ok(Async::Ready(Some(FramedMessage::WaitClose(tx)))) => {
|
||||||
|
self.dispatch_state.stop(Some(tx));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Ok(Async::Ready(None)) => {
|
||||||
|
rx_done = true;
|
||||||
|
let _ = self.rx.take();
|
||||||
|
}
|
||||||
|
Ok(Async::NotReady) => rx_done = true,
|
||||||
|
Err(_e) => {
|
||||||
|
rx_done = true;
|
||||||
|
let _ = self.rx.take();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rx_done && buf_empty {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.framed.is_write_buf_empty() {
|
||||||
|
match self.framed.poll_complete() {
|
||||||
|
Ok(Async::NotReady) => break,
|
||||||
|
Err(err) => {
|
||||||
|
debug!("Error sending data: {:?}", err);
|
||||||
|
self.dispatch_state = State::FramedError(ServiceError::Encoder(err));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Ok(Async::Ready(_)) => (),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<St, S, T, U> Future for FramedDispatcher<St, S, T, U>
|
||||||
|
where
|
||||||
|
S: Service<Request = Request<St, U>, Response = Option<Response<U>>>,
|
||||||
|
S::Error: 'static,
|
||||||
|
S::Future: 'static,
|
||||||
|
T: AsyncRead + AsyncWrite,
|
||||||
|
U: Decoder + Encoder,
|
||||||
|
<U as Encoder>::Item: 'static,
|
||||||
|
<U as Encoder>::Error: std::fmt::Debug,
|
||||||
|
{
|
||||||
|
type Item = ();
|
||||||
|
type Error = ServiceError<S::Error, U>;
|
||||||
|
|
||||||
|
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||||
|
match mem::replace(&mut self.dispatch_state, State::Processing) {
|
||||||
|
State::Processing => {
|
||||||
|
if self.poll_read() || self.poll_write() {
|
||||||
|
self.poll()
|
||||||
|
} else {
|
||||||
|
Ok(Async::NotReady)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
State::Error(err) => {
|
||||||
|
if self.framed.is_write_buf_empty()
|
||||||
|
|| (self.poll_write() || self.framed.is_write_buf_empty())
|
||||||
|
{
|
||||||
|
Err(err)
|
||||||
|
} else {
|
||||||
|
self.dispatch_state = State::Error(err);
|
||||||
|
Ok(Async::NotReady)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
State::FlushAndStop(mut vec) => {
|
||||||
|
if !self.framed.is_write_buf_empty() {
|
||||||
|
match self.framed.poll_complete() {
|
||||||
|
Err(err) => {
|
||||||
|
debug!("Error sending data: {:?}", err);
|
||||||
|
}
|
||||||
|
Ok(Async::NotReady) => {
|
||||||
|
self.dispatch_state = State::FlushAndStop(vec);
|
||||||
|
return Ok(Async::NotReady);
|
||||||
|
}
|
||||||
|
Ok(Async::Ready(_)) => (),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for tx in vec.drain(..) {
|
||||||
|
let _ = tx.send(());
|
||||||
|
}
|
||||||
|
Ok(Async::Ready(()))
|
||||||
|
}
|
||||||
|
State::FramedError(err) => Err(err),
|
||||||
|
State::Stopping => Ok(Async::Ready(())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
49
actix-ioframe/src/error.rs
Normal file
49
actix-ioframe/src/error.rs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use actix_codec::{Decoder, Encoder};
|
||||||
|
|
||||||
|
/// Framed service errors
|
||||||
|
pub enum ServiceError<E, U: Encoder + Decoder> {
|
||||||
|
/// Inner service error
|
||||||
|
Service(E),
|
||||||
|
/// Encoder parse error
|
||||||
|
Encoder(<U as Encoder>::Error),
|
||||||
|
/// Decoder parse error
|
||||||
|
Decoder(<U as Decoder>::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E, U: Encoder + Decoder> From<E> for ServiceError<E, U> {
|
||||||
|
fn from(err: E) -> Self {
|
||||||
|
ServiceError::Service(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E, U: Encoder + Decoder> fmt::Debug for ServiceError<E, U>
|
||||||
|
where
|
||||||
|
E: fmt::Debug,
|
||||||
|
<U as Encoder>::Error: fmt::Debug,
|
||||||
|
<U as Decoder>::Error: fmt::Debug,
|
||||||
|
{
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match *self {
|
||||||
|
ServiceError::Service(ref e) => write!(fmt, "ServiceError::Service({:?})", e),
|
||||||
|
ServiceError::Encoder(ref e) => write!(fmt, "ServiceError::Encoder({:?})", e),
|
||||||
|
ServiceError::Decoder(ref e) => write!(fmt, "ServiceError::Encoder({:?})", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E, U: Encoder + Decoder> fmt::Display for ServiceError<E, U>
|
||||||
|
where
|
||||||
|
E: fmt::Display,
|
||||||
|
<U as Encoder>::Error: fmt::Debug,
|
||||||
|
<U as Decoder>::Error: fmt::Debug,
|
||||||
|
{
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match *self {
|
||||||
|
ServiceError::Service(ref e) => write!(fmt, "{}", e),
|
||||||
|
ServiceError::Encoder(ref e) => write!(fmt, "{:?}", e),
|
||||||
|
ServiceError::Decoder(ref e) => write!(fmt, "{:?}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
78
actix-ioframe/src/item.rs
Normal file
78
actix-ioframe/src/item.rs
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
use std::fmt;
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
|
use actix_codec::{Decoder, Encoder};
|
||||||
|
|
||||||
|
use crate::cell::Cell;
|
||||||
|
use crate::sink::Sink;
|
||||||
|
|
||||||
|
pub struct Item<S, Codec: Encoder + Decoder> {
|
||||||
|
state: Cell<S>,
|
||||||
|
sink: Sink<<Codec as Encoder>::Item>,
|
||||||
|
item: <Codec as Decoder>::Item,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<St, Codec> Item<St, Codec>
|
||||||
|
where
|
||||||
|
Codec: Encoder + Decoder,
|
||||||
|
{
|
||||||
|
pub(crate) fn new(
|
||||||
|
state: Cell<St>,
|
||||||
|
sink: Sink<<Codec as Encoder>::Item>,
|
||||||
|
item: <Codec as Decoder>::Item,
|
||||||
|
) -> Self {
|
||||||
|
Item { state, sink, item }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn state(&self) -> &St {
|
||||||
|
self.state.get_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn state_mut(&mut self) -> &mut St {
|
||||||
|
self.state.get_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn sink(&self) -> &Sink<<Codec as Encoder>::Item> {
|
||||||
|
&self.sink
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn into_inner(self) -> <Codec as Decoder>::Item {
|
||||||
|
self.item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<St, Codec> Deref for Item<St, Codec>
|
||||||
|
where
|
||||||
|
Codec: Encoder + Decoder,
|
||||||
|
{
|
||||||
|
type Target = <Codec as Decoder>::Item;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn deref(&self) -> &<Codec as Decoder>::Item {
|
||||||
|
&self.item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<St, Codec> DerefMut for Item<St, Codec>
|
||||||
|
where
|
||||||
|
Codec: Encoder + Decoder,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn deref_mut(&mut self) -> &mut <Codec as Decoder>::Item {
|
||||||
|
&mut self.item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<St, Codec> fmt::Debug for Item<St, Codec>
|
||||||
|
where
|
||||||
|
Codec: Encoder + Decoder,
|
||||||
|
<Codec as Decoder>::Item: fmt::Debug,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.debug_tuple("FramedItem").field(&self.item).finish()
|
||||||
|
}
|
||||||
|
}
|
13
actix-ioframe/src/lib.rs
Normal file
13
actix-ioframe/src/lib.rs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
mod cell;
|
||||||
|
mod connect;
|
||||||
|
mod dispatcher;
|
||||||
|
mod error;
|
||||||
|
mod item;
|
||||||
|
mod service;
|
||||||
|
mod sink;
|
||||||
|
|
||||||
|
pub use self::connect::{Connect, ConnectResult};
|
||||||
|
pub use self::error::ServiceError;
|
||||||
|
pub use self::item::Item;
|
||||||
|
pub use self::service::{Builder, NewServiceBuilder, ServiceBuilder};
|
||||||
|
pub use self::sink::Sink;
|
323
actix-ioframe/src/service.rs
Normal file
323
actix-ioframe/src/service.rs
Normal file
@ -0,0 +1,323 @@
|
|||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder};
|
||||||
|
use actix_service::{IntoNewService, IntoService, NewService, Service};
|
||||||
|
use futures::{Async, Future, Poll};
|
||||||
|
|
||||||
|
use crate::connect::{Connect, ConnectResult};
|
||||||
|
use crate::dispatcher::FramedDispatcher;
|
||||||
|
use crate::error::ServiceError;
|
||||||
|
use crate::item::Item;
|
||||||
|
|
||||||
|
type RequestItem<S, U> = Item<S, U>;
|
||||||
|
type ResponseItem<U> = Option<<U as Encoder>::Item>;
|
||||||
|
|
||||||
|
/// Service builder - structure that follows the builder pattern
|
||||||
|
/// for building instances for framed services.
|
||||||
|
pub struct Builder<St, Codec>(PhantomData<(St, Codec)>);
|
||||||
|
|
||||||
|
impl<St, Codec> Builder<St, Codec> {
|
||||||
|
pub fn new() -> Builder<St, Codec> {
|
||||||
|
Builder(PhantomData)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct framed handler service with specified connect service
|
||||||
|
pub fn service<Io, C, F>(self, connect: F) -> ServiceBuilder<St, C, Io, Codec>
|
||||||
|
where
|
||||||
|
F: IntoService<C>,
|
||||||
|
Io: AsyncRead + AsyncWrite,
|
||||||
|
C: Service<Request = Connect<Io>, Response = ConnectResult<Io, St, Codec>>,
|
||||||
|
Codec: Decoder + Encoder,
|
||||||
|
{
|
||||||
|
ServiceBuilder {
|
||||||
|
connect: connect.into_service(),
|
||||||
|
_t: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct framed handler new service with specified connect service
|
||||||
|
pub fn factory<Io, C, F>(self, connect: F) -> NewServiceBuilder<St, C, Io, Codec>
|
||||||
|
where
|
||||||
|
F: IntoNewService<C>,
|
||||||
|
Io: AsyncRead + AsyncWrite,
|
||||||
|
C: NewService<
|
||||||
|
Config = (),
|
||||||
|
Request = Connect<Io>,
|
||||||
|
Response = ConnectResult<Io, St, Codec>,
|
||||||
|
>,
|
||||||
|
C::Error: 'static,
|
||||||
|
C::Future: 'static,
|
||||||
|
Codec: Decoder + Encoder,
|
||||||
|
{
|
||||||
|
NewServiceBuilder {
|
||||||
|
connect: connect.into_new_service(),
|
||||||
|
_t: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ServiceBuilder<St, C, Io, Codec> {
|
||||||
|
connect: C,
|
||||||
|
_t: PhantomData<(St, Io, Codec)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<St, C, Io, Codec> ServiceBuilder<St, C, Io, Codec>
|
||||||
|
where
|
||||||
|
St: 'static,
|
||||||
|
Io: AsyncRead + AsyncWrite,
|
||||||
|
C: Service<Request = Connect<Io>, Response = ConnectResult<Io, St, Codec>>,
|
||||||
|
C::Error: 'static,
|
||||||
|
Codec: Decoder + Encoder,
|
||||||
|
<Codec as Encoder>::Item: 'static,
|
||||||
|
<Codec as Encoder>::Error: std::fmt::Debug,
|
||||||
|
{
|
||||||
|
pub fn finish<F, T>(
|
||||||
|
self,
|
||||||
|
service: F,
|
||||||
|
) -> impl Service<Request = Io, Response = (), Error = ServiceError<C::Error, Codec>>
|
||||||
|
where
|
||||||
|
F: IntoNewService<T>,
|
||||||
|
T: NewService<
|
||||||
|
Config = St,
|
||||||
|
Request = RequestItem<St, Codec>,
|
||||||
|
Response = ResponseItem<Codec>,
|
||||||
|
Error = C::Error,
|
||||||
|
InitError = C::Error,
|
||||||
|
> + 'static,
|
||||||
|
{
|
||||||
|
FramedServiceImpl {
|
||||||
|
connect: self.connect,
|
||||||
|
handler: Rc::new(service.into_new_service()),
|
||||||
|
_t: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct NewServiceBuilder<St, C, Io, Codec> {
|
||||||
|
connect: C,
|
||||||
|
_t: PhantomData<(St, Io, Codec)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<St, C, Io, Codec> NewServiceBuilder<St, C, Io, Codec>
|
||||||
|
where
|
||||||
|
St: 'static,
|
||||||
|
Io: AsyncRead + AsyncWrite,
|
||||||
|
C: NewService<Config = (), Request = Connect<Io>, Response = ConnectResult<Io, St, Codec>>,
|
||||||
|
C::Error: 'static,
|
||||||
|
C::Future: 'static,
|
||||||
|
Codec: Decoder + Encoder,
|
||||||
|
<Codec as Encoder>::Item: 'static,
|
||||||
|
<Codec as Encoder>::Error: std::fmt::Debug,
|
||||||
|
{
|
||||||
|
pub fn finish<F, T>(
|
||||||
|
self,
|
||||||
|
service: F,
|
||||||
|
) -> impl NewService<
|
||||||
|
Config = (),
|
||||||
|
Request = Io,
|
||||||
|
Response = (),
|
||||||
|
Error = ServiceError<C::Error, Codec>,
|
||||||
|
>
|
||||||
|
where
|
||||||
|
F: IntoNewService<T>,
|
||||||
|
T: NewService<
|
||||||
|
Config = St,
|
||||||
|
Request = RequestItem<St, Codec>,
|
||||||
|
Response = ResponseItem<Codec>,
|
||||||
|
Error = C::Error,
|
||||||
|
InitError = C::Error,
|
||||||
|
> + 'static,
|
||||||
|
{
|
||||||
|
FramedService {
|
||||||
|
connect: self.connect,
|
||||||
|
handler: Rc::new(service.into_new_service()),
|
||||||
|
_t: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct FramedService<St, C, T, Io, Codec> {
|
||||||
|
connect: C,
|
||||||
|
handler: Rc<T>,
|
||||||
|
_t: PhantomData<(St, Io, Codec)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<St, C, T, Io, Codec> NewService for FramedService<St, C, T, Io, Codec>
|
||||||
|
where
|
||||||
|
St: 'static,
|
||||||
|
Io: AsyncRead + AsyncWrite,
|
||||||
|
C: NewService<Config = (), Request = Connect<Io>, Response = ConnectResult<Io, St, Codec>>,
|
||||||
|
C::Error: 'static,
|
||||||
|
C::Future: 'static,
|
||||||
|
T: NewService<
|
||||||
|
Config = St,
|
||||||
|
Request = RequestItem<St, Codec>,
|
||||||
|
Response = ResponseItem<Codec>,
|
||||||
|
Error = C::Error,
|
||||||
|
InitError = C::Error,
|
||||||
|
> + 'static,
|
||||||
|
Codec: Decoder + Encoder,
|
||||||
|
<Codec as Encoder>::Item: 'static,
|
||||||
|
<Codec as Encoder>::Error: std::fmt::Debug,
|
||||||
|
{
|
||||||
|
type Config = ();
|
||||||
|
type Request = Io;
|
||||||
|
type Response = ();
|
||||||
|
type Error = ServiceError<C::Error, Codec>;
|
||||||
|
type InitError = C::InitError;
|
||||||
|
type Service = FramedServiceImpl<St, C::Service, T, Io, Codec>;
|
||||||
|
type Future = Box<Future<Item = Self::Service, Error = Self::InitError>>;
|
||||||
|
|
||||||
|
fn new_service(&self, _: &()) -> Self::Future {
|
||||||
|
let handler = self.handler.clone();
|
||||||
|
|
||||||
|
// create connect service and then create service impl
|
||||||
|
Box::new(
|
||||||
|
self.connect
|
||||||
|
.new_service(&())
|
||||||
|
.map(move |connect| FramedServiceImpl {
|
||||||
|
connect,
|
||||||
|
handler,
|
||||||
|
_t: PhantomData,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FramedServiceImpl<St, C, T, Io, Codec> {
|
||||||
|
connect: C,
|
||||||
|
handler: Rc<T>,
|
||||||
|
_t: PhantomData<(St, Io, Codec)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<St, C, T, Io, Codec> Service for FramedServiceImpl<St, C, T, Io, Codec>
|
||||||
|
where
|
||||||
|
// St: 'static,
|
||||||
|
Io: AsyncRead + AsyncWrite,
|
||||||
|
C: Service<Request = Connect<Io>, Response = ConnectResult<Io, St, Codec>>,
|
||||||
|
C::Error: 'static,
|
||||||
|
T: NewService<
|
||||||
|
Config = St,
|
||||||
|
Request = RequestItem<St, Codec>,
|
||||||
|
Response = ResponseItem<Codec>,
|
||||||
|
Error = C::Error,
|
||||||
|
InitError = C::Error,
|
||||||
|
>,
|
||||||
|
<<T as NewService>::Service as Service>::Future: 'static,
|
||||||
|
Codec: Decoder + Encoder,
|
||||||
|
<Codec as Encoder>::Item: 'static,
|
||||||
|
<Codec as Encoder>::Error: std::fmt::Debug,
|
||||||
|
{
|
||||||
|
type Request = Io;
|
||||||
|
type Response = ();
|
||||||
|
type Error = ServiceError<C::Error, Codec>;
|
||||||
|
type Future = FramedServiceImplResponse<St, Io, Codec, C, T>;
|
||||||
|
|
||||||
|
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||||
|
self.connect.poll_ready().map_err(|e| e.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, req: Io) -> Self::Future {
|
||||||
|
FramedServiceImplResponse {
|
||||||
|
inner: FramedServiceImplResponseInner::Connect(
|
||||||
|
self.connect.call(Connect::new(req)),
|
||||||
|
self.handler.clone(),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FramedServiceImplResponse<St, Io, Codec, C, T>
|
||||||
|
where
|
||||||
|
C: Service<Request = Connect<Io>, Response = ConnectResult<Io, St, Codec>>,
|
||||||
|
C::Error: 'static,
|
||||||
|
T: NewService<
|
||||||
|
Config = St,
|
||||||
|
Request = RequestItem<St, Codec>,
|
||||||
|
Response = ResponseItem<Codec>,
|
||||||
|
Error = C::Error,
|
||||||
|
InitError = C::Error,
|
||||||
|
>,
|
||||||
|
<<T as NewService>::Service as Service>::Future: 'static,
|
||||||
|
Io: AsyncRead + AsyncWrite,
|
||||||
|
Codec: Encoder + Decoder,
|
||||||
|
<Codec as Encoder>::Item: 'static,
|
||||||
|
<Codec as Encoder>::Error: std::fmt::Debug,
|
||||||
|
{
|
||||||
|
inner: FramedServiceImplResponseInner<St, Io, Codec, C, T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum FramedServiceImplResponseInner<St, Io, Codec, C, T>
|
||||||
|
where
|
||||||
|
C: Service<Request = Connect<Io>, Response = ConnectResult<Io, St, Codec>>,
|
||||||
|
C::Error: 'static,
|
||||||
|
T: NewService<
|
||||||
|
Config = St,
|
||||||
|
Request = RequestItem<St, Codec>,
|
||||||
|
Response = ResponseItem<Codec>,
|
||||||
|
Error = C::Error,
|
||||||
|
InitError = C::Error,
|
||||||
|
>,
|
||||||
|
<<T as NewService>::Service as Service>::Future: 'static,
|
||||||
|
Io: AsyncRead + AsyncWrite,
|
||||||
|
Codec: Encoder + Decoder,
|
||||||
|
<Codec as Encoder>::Item: 'static,
|
||||||
|
<Codec as Encoder>::Error: std::fmt::Debug,
|
||||||
|
{
|
||||||
|
Connect(C::Future, Rc<T>),
|
||||||
|
Handler(T::Future, Option<ConnectResult<Io, St, Codec>>),
|
||||||
|
Dispatcher(FramedDispatcher<St, T::Service, Io, Codec>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<St, Io, Codec, C, T> Future for FramedServiceImplResponse<St, Io, Codec, C, T>
|
||||||
|
where
|
||||||
|
C: Service<Request = Connect<Io>, Response = ConnectResult<Io, St, Codec>>,
|
||||||
|
C::Error: 'static,
|
||||||
|
T: NewService<
|
||||||
|
Config = St,
|
||||||
|
Request = RequestItem<St, Codec>,
|
||||||
|
Response = ResponseItem<Codec>,
|
||||||
|
Error = C::Error,
|
||||||
|
InitError = C::Error,
|
||||||
|
>,
|
||||||
|
<<T as NewService>::Service as Service>::Future: 'static,
|
||||||
|
Io: AsyncRead + AsyncWrite,
|
||||||
|
Codec: Encoder + Decoder,
|
||||||
|
<Codec as Encoder>::Item: 'static,
|
||||||
|
<Codec as Encoder>::Error: std::fmt::Debug,
|
||||||
|
{
|
||||||
|
type Item = ();
|
||||||
|
type Error = ServiceError<C::Error, Codec>;
|
||||||
|
|
||||||
|
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||||
|
match self.inner {
|
||||||
|
FramedServiceImplResponseInner::Connect(ref mut fut, ref handler) => {
|
||||||
|
match fut.poll()? {
|
||||||
|
Async::Ready(res) => {
|
||||||
|
self.inner = FramedServiceImplResponseInner::Handler(
|
||||||
|
handler.new_service(res.state.get_ref()),
|
||||||
|
Some(res),
|
||||||
|
);
|
||||||
|
self.poll()
|
||||||
|
}
|
||||||
|
Async::NotReady => Ok(Async::NotReady),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FramedServiceImplResponseInner::Handler(ref mut fut, ref mut res) => {
|
||||||
|
match fut.poll()? {
|
||||||
|
Async::Ready(handler) => {
|
||||||
|
let res = res.take().unwrap();
|
||||||
|
self.inner =
|
||||||
|
FramedServiceImplResponseInner::Dispatcher(FramedDispatcher::new(
|
||||||
|
res.framed, res.state, handler, res.rx, res.sink,
|
||||||
|
));
|
||||||
|
self.poll()
|
||||||
|
}
|
||||||
|
Async::NotReady => Ok(Async::NotReady),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FramedServiceImplResponseInner::Dispatcher(ref mut fut) => fut.poll(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
actix-ioframe/src/sink.rs
Normal file
44
actix-ioframe/src/sink.rs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use futures::unsync::{mpsc, oneshot};
|
||||||
|
use futures::Future;
|
||||||
|
|
||||||
|
use crate::dispatcher::FramedMessage;
|
||||||
|
|
||||||
|
pub struct Sink<T>(mpsc::UnboundedSender<FramedMessage<T>>);
|
||||||
|
|
||||||
|
impl<T> Clone for Sink<T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Sink(self.0.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Sink<T> {
|
||||||
|
pub(crate) fn new(tx: mpsc::UnboundedSender<FramedMessage<T>>) -> Self {
|
||||||
|
Sink(tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Close connection
|
||||||
|
pub fn close(&self) {
|
||||||
|
let _ = self.0.unbounded_send(FramedMessage::Close);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Close connection
|
||||||
|
pub fn wait_close(&self) -> impl Future<Item = (), Error = ()> {
|
||||||
|
let (tx, rx) = oneshot::channel();
|
||||||
|
let _ = self.0.unbounded_send(FramedMessage::WaitClose(tx));
|
||||||
|
|
||||||
|
rx.map_err(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send item
|
||||||
|
pub fn send(&self, item: T) {
|
||||||
|
let _ = self.0.unbounded_send(FramedMessage::Message(item));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> fmt::Debug for Sink<T> {
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
fmt.debug_struct("Sink").finish()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user