forked from KokaKiwi/boitalettres
Initial commit.
This commit is contained in:
commit
28ded269ea
16 changed files with 631 additions and 0 deletions
5
.editorconfig
Normal file
5
.editorconfig
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*.rs]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# Rust stuff
|
||||||
|
/target
|
||||||
|
/Cargo.lock
|
||||||
|
|
||||||
|
# Misc stuff
|
||||||
|
.*
|
||||||
|
!.editorconfig
|
||||||
|
!.gitignore
|
25
Cargo.toml
Normal file
25
Cargo.toml
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
[package]
|
||||||
|
name = "boitalettres"
|
||||||
|
version = "0.0.1"
|
||||||
|
license = "BSD-3-Clause"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bytes = "1.1"
|
||||||
|
thiserror = "1.0"
|
||||||
|
tracing = "0.1"
|
||||||
|
|
||||||
|
# IMAP
|
||||||
|
imap-codec = "0.5"
|
||||||
|
|
||||||
|
# Async
|
||||||
|
async-compat = "0.2"
|
||||||
|
futures = "0.3"
|
||||||
|
pin-project = "1.0"
|
||||||
|
tokio = { version = "1.18", features = ["full"] }
|
||||||
|
tokio-tower = "0.6"
|
||||||
|
tower = { version = "0.4", features = ["full"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
eyre = "0.6"
|
||||||
|
tracing-subscriber = "0.3"
|
24
LICENSE
Normal file
24
LICENSE
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
Copyright (c) 2022 KokaKiwi <kokakiwi@deuxfleurs.fr>
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions
|
||||||
|
are met:
|
||||||
|
1. Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
3. Neither the name of the author nor the names of its contributors may
|
||||||
|
be used to endorse or promote products derived from this software
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGE.
|
32
examples/simple.rs
Normal file
32
examples/simple.rs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
use boitalettres::proto::{Request, Response};
|
||||||
|
use boitalettres::server::accept::addr::{AddrIncoming, AddrStream};
|
||||||
|
use boitalettres::server::Server;
|
||||||
|
|
||||||
|
async fn handle_req(req: Request) -> eyre::Result<Response> {
|
||||||
|
use imap_codec::types::response::Status;
|
||||||
|
|
||||||
|
tracing::debug!("Got request: {:#?}", req);
|
||||||
|
|
||||||
|
Ok(Response::Status(
|
||||||
|
Status::ok(Some(req.tag), None, "Ok").map_err(|e| eyre::eyre!(e))?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> eyre::Result<()> {
|
||||||
|
tracing_subscriber::fmt()
|
||||||
|
.with_max_level(tracing::Level::TRACE)
|
||||||
|
.init();
|
||||||
|
|
||||||
|
let incoming = AddrIncoming::new("127.0.0.1:4567").await?;
|
||||||
|
|
||||||
|
let make_service = tower::service_fn(|addr: &AddrStream| {
|
||||||
|
tracing::debug!("Accept: {} -> {}", addr.remote_addr, addr.local_addr);
|
||||||
|
futures::future::ok::<_, std::convert::Infallible>(tower::service_fn(handle_req))
|
||||||
|
});
|
||||||
|
|
||||||
|
let server = Server::new(incoming).serve(make_service);
|
||||||
|
let _ = server.await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
3
rust-toolchain.toml
Normal file
3
rust-toolchain.toml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[toolchain]
|
||||||
|
channel = "stable"
|
||||||
|
components = ["rustc-dev", "rust-src"]
|
7
scripts/test_imap.py
Normal file
7
scripts/test_imap.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
from imaplib import IMAP4
|
||||||
|
|
||||||
|
conn = IMAP4('127.0.0.1', port=4567)
|
||||||
|
conn.noop()
|
||||||
|
|
||||||
|
conn.logout()
|
2
src/lib.rs
Normal file
2
src/lib.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod proto;
|
||||||
|
pub mod server;
|
5
src/proto/mod.rs
Normal file
5
src/proto/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
pub use self::req::Request;
|
||||||
|
pub use self::res::Response;
|
||||||
|
|
||||||
|
pub mod req;
|
||||||
|
pub mod res;
|
3
src/proto/req.rs
Normal file
3
src/proto/req.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
use imap_codec::types::command::Command;
|
||||||
|
|
||||||
|
pub type Request = Command;
|
1
src/proto/res.rs
Normal file
1
src/proto/res.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pub type Response = imap_codec::types::response::Response;
|
83
src/server/accept/addr.rs
Normal file
83
src/server/accept/addr.rs
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::task::{self, Poll};
|
||||||
|
|
||||||
|
use async_compat::Compat as AsyncCompat;
|
||||||
|
use futures::io::{AsyncRead, AsyncWrite};
|
||||||
|
use tokio::net::{TcpListener, TcpStream, ToSocketAddrs};
|
||||||
|
|
||||||
|
use super::Accept;
|
||||||
|
|
||||||
|
#[pin_project::pin_project]
|
||||||
|
pub struct AddrIncoming {
|
||||||
|
pub local_addr: SocketAddr,
|
||||||
|
#[pin]
|
||||||
|
listener: TcpListener,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AddrIncoming {
|
||||||
|
pub async fn new(addr: impl ToSocketAddrs) -> std::io::Result<Self> {
|
||||||
|
let listener = TcpListener::bind(addr).await?;
|
||||||
|
let local_addr = listener.local_addr()?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
local_addr,
|
||||||
|
listener,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Accept for AddrIncoming {
|
||||||
|
type Conn = AddrStream;
|
||||||
|
type Error = std::io::Error;
|
||||||
|
|
||||||
|
fn poll_accept(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut task::Context,
|
||||||
|
) -> task::Poll<Option<Result<Self::Conn, Self::Error>>> {
|
||||||
|
let this = self.project();
|
||||||
|
|
||||||
|
let (stream, remote_addr) = futures::ready!(this.listener.poll_accept(cx))?;
|
||||||
|
Poll::Ready(Some(Ok(AddrStream {
|
||||||
|
local_addr: *this.local_addr,
|
||||||
|
remote_addr,
|
||||||
|
stream: AsyncCompat::new(stream),
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pin_project::pin_project]
|
||||||
|
pub struct AddrStream {
|
||||||
|
pub local_addr: SocketAddr,
|
||||||
|
pub remote_addr: SocketAddr,
|
||||||
|
#[pin]
|
||||||
|
stream: AsyncCompat<TcpStream>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsyncRead for AddrStream {
|
||||||
|
fn poll_read(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
buf: &mut [u8],
|
||||||
|
) -> Poll<std::io::Result<usize>> {
|
||||||
|
self.project().stream.poll_read(cx, buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsyncWrite for AddrStream {
|
||||||
|
fn poll_write(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
buf: &[u8],
|
||||||
|
) -> Poll<std::io::Result<usize>> {
|
||||||
|
self.project().stream.poll_write(cx, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_flush(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<std::io::Result<()>> {
|
||||||
|
self.project().stream.poll_flush(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_close(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<std::io::Result<()>> {
|
||||||
|
self.project().stream.poll_close(cx)
|
||||||
|
}
|
||||||
|
}
|
79
src/server/accept/mod.rs
Normal file
79
src/server/accept/mod.rs
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
use std::error::Error as StdError;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::task;
|
||||||
|
|
||||||
|
use futures::io;
|
||||||
|
use futures::Stream;
|
||||||
|
|
||||||
|
pub mod addr;
|
||||||
|
|
||||||
|
pub trait Accept {
|
||||||
|
type Conn: io::AsyncRead + io::AsyncWrite;
|
||||||
|
type Error: StdError;
|
||||||
|
|
||||||
|
fn poll_accept(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut task::Context,
|
||||||
|
) -> task::Poll<Option<Result<Self::Conn, Self::Error>>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn poll_fn<F, IO, E>(f: F) -> impl Accept<Conn = IO, Error = E>
|
||||||
|
where
|
||||||
|
F: FnMut(&mut task::Context) -> task::Poll<Option<Result<IO, E>>>,
|
||||||
|
IO: io::AsyncRead + io::AsyncWrite,
|
||||||
|
E: StdError,
|
||||||
|
{
|
||||||
|
struct PollFn<F>(F);
|
||||||
|
|
||||||
|
impl<F> Unpin for PollFn<F> {}
|
||||||
|
|
||||||
|
impl<F, IO, E> Accept for PollFn<F>
|
||||||
|
where
|
||||||
|
F: FnMut(&mut task::Context) -> task::Poll<Option<Result<IO, E>>>,
|
||||||
|
IO: io::AsyncRead + io::AsyncWrite,
|
||||||
|
E: StdError,
|
||||||
|
{
|
||||||
|
type Conn = IO;
|
||||||
|
type Error = E;
|
||||||
|
|
||||||
|
fn poll_accept(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut task::Context,
|
||||||
|
) -> task::Poll<Option<Result<Self::Conn, Self::Error>>> {
|
||||||
|
(self.get_mut().0)(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PollFn(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_stream<S, IO, E>(stream: S) -> impl Accept<Conn = IO, Error = E>
|
||||||
|
where
|
||||||
|
S: Stream<Item = Result<IO, E>>,
|
||||||
|
IO: io::AsyncRead + io::AsyncWrite,
|
||||||
|
E: StdError,
|
||||||
|
{
|
||||||
|
use pin_project::pin_project;
|
||||||
|
|
||||||
|
#[pin_project]
|
||||||
|
struct FromStream<S>(#[pin] S);
|
||||||
|
|
||||||
|
impl<S, IO, E> Accept for FromStream<S>
|
||||||
|
where
|
||||||
|
S: Stream<Item = Result<IO, E>>,
|
||||||
|
IO: io::AsyncRead + io::AsyncWrite,
|
||||||
|
E: StdError,
|
||||||
|
{
|
||||||
|
type Conn = IO;
|
||||||
|
type Error = E;
|
||||||
|
|
||||||
|
fn poll_accept(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut task::Context,
|
||||||
|
) -> task::Poll<Option<Result<Self::Conn, Self::Error>>> {
|
||||||
|
self.project().0.poll_next(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FromStream(stream)
|
||||||
|
}
|
168
src/server/mod.rs
Normal file
168
src/server/mod.rs
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::task::{self, Poll};
|
||||||
|
|
||||||
|
use futures::io::{AsyncRead, AsyncWrite};
|
||||||
|
use imap_codec::types::response::Capability;
|
||||||
|
use tower::Service;
|
||||||
|
|
||||||
|
use crate::proto::{Request, Response};
|
||||||
|
use accept::Accept;
|
||||||
|
pub use service::MakeServiceRef;
|
||||||
|
|
||||||
|
pub mod accept;
|
||||||
|
mod pipeline;
|
||||||
|
mod service;
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum Error<A> {
|
||||||
|
Accept(#[source] A),
|
||||||
|
MakeService(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone)]
|
||||||
|
pub struct Imap {
|
||||||
|
pub capabilities: Vec<Capability>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pin_project::pin_project]
|
||||||
|
pub struct Server<I, S> {
|
||||||
|
#[pin]
|
||||||
|
incoming: I,
|
||||||
|
make_service: S,
|
||||||
|
protocol: Imap,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I> Server<I, ()>
|
||||||
|
where
|
||||||
|
I: Accept,
|
||||||
|
{
|
||||||
|
#[allow(clippy::new_ret_no_self)]
|
||||||
|
pub fn new(incoming: I) -> Builder<I> {
|
||||||
|
Builder::new(incoming)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I, S> Future for Server<I, S>
|
||||||
|
where
|
||||||
|
I: Accept,
|
||||||
|
I::Conn: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
||||||
|
S: MakeServiceRef<I::Conn, Request, Response = Response>,
|
||||||
|
S::MakeError: Into<Box<dyn std::error::Error + Send + Sync + 'static>> + std::fmt::Display,
|
||||||
|
S::Error: std::fmt::Display,
|
||||||
|
S::Future: Send + 'static,
|
||||||
|
{
|
||||||
|
type Output = Result<(), Error<I::Error>>;
|
||||||
|
|
||||||
|
fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
|
||||||
|
loop {
|
||||||
|
let this = self.as_mut().project();
|
||||||
|
|
||||||
|
if let Some(conn) = futures::ready!(this.incoming.poll_accept(cx)) {
|
||||||
|
let conn = conn.map_err(Error::Accept)?;
|
||||||
|
|
||||||
|
futures::ready!(this.make_service.poll_ready_ref(cx))
|
||||||
|
.map_err(Into::into)
|
||||||
|
.map_err(Error::MakeService)?;
|
||||||
|
|
||||||
|
let service_fut = this.make_service.make_service_ref(&conn);
|
||||||
|
|
||||||
|
tokio::task::spawn(Connecting {
|
||||||
|
conn,
|
||||||
|
service_fut,
|
||||||
|
protocol: this.protocol.clone(),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return Poll::Ready(Ok(()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Builder<I: Accept> {
|
||||||
|
incoming: I,
|
||||||
|
protocol: Imap,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::needless_update)]
|
||||||
|
impl<I: Accept> Builder<I> {
|
||||||
|
pub fn new(incoming: I) -> Self {
|
||||||
|
Self {
|
||||||
|
incoming,
|
||||||
|
protocol: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn capabilities(self, capabilities: impl IntoIterator<Item = Capability>) -> Self {
|
||||||
|
let protocol = Imap {
|
||||||
|
capabilities: capabilities.into_iter().collect(),
|
||||||
|
..self.protocol
|
||||||
|
};
|
||||||
|
|
||||||
|
Self { protocol, ..self }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn serve<S>(self, make_service: S) -> Server<I, S>
|
||||||
|
where
|
||||||
|
S: MakeServiceRef<I::Conn, Request, Response = Response>,
|
||||||
|
{
|
||||||
|
Server {
|
||||||
|
incoming: self.incoming,
|
||||||
|
make_service,
|
||||||
|
protocol: self.protocol,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pin_project::pin_project]
|
||||||
|
struct Connecting<C, F> {
|
||||||
|
conn: C,
|
||||||
|
#[pin]
|
||||||
|
service_fut: F,
|
||||||
|
protocol: Imap,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C, F, ME, S> Future for Connecting<C, F>
|
||||||
|
where
|
||||||
|
C: AsyncRead + AsyncWrite + Unpin,
|
||||||
|
F: Future<Output = std::result::Result<S, ME>>,
|
||||||
|
ME: std::fmt::Display,
|
||||||
|
S: Service<Request, Response = Response>,
|
||||||
|
S::Error: std::fmt::Display,
|
||||||
|
{
|
||||||
|
type Output = ();
|
||||||
|
|
||||||
|
fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
|
||||||
|
use tokio_tower::pipeline::Server as PipelineServer;
|
||||||
|
|
||||||
|
use pipeline::Connection;
|
||||||
|
|
||||||
|
let this = self.project();
|
||||||
|
|
||||||
|
let service = match futures::ready!(this.service_fut.poll(cx)) {
|
||||||
|
Ok(service) => service,
|
||||||
|
Err(err) => {
|
||||||
|
tracing::debug!("Connection error: {}", err);
|
||||||
|
return Poll::Ready(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut conn = Connection::new(this.conn);
|
||||||
|
|
||||||
|
// TODO: Properly handle server greeting
|
||||||
|
{
|
||||||
|
use imap_codec::types::response::{Response, Status};
|
||||||
|
|
||||||
|
let status = Status::ok(None, None, "Hello").unwrap();
|
||||||
|
let res = Response::Status(status);
|
||||||
|
|
||||||
|
conn.send(res).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut server = PipelineServer::new(conn, service);
|
||||||
|
if let Err(err) = futures::ready!(Future::poll(Pin::new(&mut server), cx)) {
|
||||||
|
tracing::debug!("Connection error: {}", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
Poll::Ready(())
|
||||||
|
}
|
||||||
|
}
|
136
src/server/pipeline.rs
Normal file
136
src/server/pipeline.rs
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::task::{self, Poll};
|
||||||
|
|
||||||
|
use bytes::BytesMut;
|
||||||
|
use futures::io::{AsyncRead, AsyncWrite};
|
||||||
|
use futures::sink::Sink;
|
||||||
|
use futures::stream::Stream;
|
||||||
|
|
||||||
|
use crate::proto::{Request, Response};
|
||||||
|
|
||||||
|
type Error = Box<dyn std::error::Error + Send + Sync + 'static>;
|
||||||
|
|
||||||
|
#[pin_project::pin_project]
|
||||||
|
pub struct Connection<C> {
|
||||||
|
#[pin]
|
||||||
|
conn: C,
|
||||||
|
read_buf: BytesMut,
|
||||||
|
write_buf: BytesMut,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> Connection<C> {
|
||||||
|
pub fn new(conn: C) -> Self {
|
||||||
|
Self {
|
||||||
|
conn,
|
||||||
|
read_buf: BytesMut::new(),
|
||||||
|
write_buf: BytesMut::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> Stream for Connection<C>
|
||||||
|
where
|
||||||
|
C: AsyncRead + Unpin,
|
||||||
|
{
|
||||||
|
type Item = Result<Request, Error>;
|
||||||
|
|
||||||
|
fn poll_next(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Option<Self::Item>> {
|
||||||
|
use imap_codec::parse::command::command as parse_command;
|
||||||
|
|
||||||
|
let mut this = self.project();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let (input, command) = match parse_command(this.read_buf) {
|
||||||
|
Ok(res) => res,
|
||||||
|
Err(e) if e.is_incomplete() => {
|
||||||
|
let mut buf = [0u8; 256];
|
||||||
|
let read = futures::ready!(this.conn.as_mut().poll_read(cx, &mut buf))?;
|
||||||
|
|
||||||
|
let data = &buf[..read];
|
||||||
|
this.read_buf.extend(data);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
return Poll::Ready(Some(Err(format!("Error: {:?}", e).into())));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
tracing::debug!("Received: {:#?}", command);
|
||||||
|
|
||||||
|
*this.read_buf = input.into();
|
||||||
|
|
||||||
|
let req = command;
|
||||||
|
return Poll::Ready(Some(Ok(req)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> Connection<C>
|
||||||
|
where
|
||||||
|
C: AsyncWrite,
|
||||||
|
{
|
||||||
|
fn poll_flush_buffer(self: Pin<&mut Self>, cx: &mut task::Context) -> Poll<Result<(), Error>> {
|
||||||
|
use bytes::Buf;
|
||||||
|
|
||||||
|
let mut this = self.project();
|
||||||
|
|
||||||
|
while !this.write_buf.is_empty() {
|
||||||
|
let written = futures::ready!(this.conn.as_mut().poll_write(cx, this.write_buf))?;
|
||||||
|
this.write_buf.advance(written);
|
||||||
|
}
|
||||||
|
|
||||||
|
Poll::Ready(Ok(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn send(&mut self, item: Response) -> Result<(), Error> {
|
||||||
|
use bytes::BufMut;
|
||||||
|
use imap_codec::codec::Encode;
|
||||||
|
|
||||||
|
let mut writer = BufMut::writer(&mut self.write_buf);
|
||||||
|
|
||||||
|
tracing::debug!("Sending: {:#?}", item);
|
||||||
|
item.encode(&mut writer)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> Sink<Response> for Connection<C>
|
||||||
|
where
|
||||||
|
C: AsyncWrite + Unpin,
|
||||||
|
{
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn poll_ready(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
) -> Poll<Result<(), Self::Error>> {
|
||||||
|
futures::ready!(self.poll_flush_buffer(cx))?;
|
||||||
|
Poll::Ready(Ok(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_send(self: Pin<&mut Self>, item: Response) -> Result<(), Self::Error> {
|
||||||
|
debug_assert!(self.write_buf.is_empty());
|
||||||
|
self.get_mut().send(item)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_flush(
|
||||||
|
mut self: Pin<&mut Self>,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
) -> Poll<Result<(), Self::Error>> {
|
||||||
|
futures::ready!(self.as_mut().poll_flush_buffer(cx))?;
|
||||||
|
futures::ready!(self.project().conn.poll_flush(cx))?;
|
||||||
|
Poll::Ready(Ok(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_close(
|
||||||
|
mut self: Pin<&mut Self>,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
) -> Poll<Result<(), Self::Error>> {
|
||||||
|
futures::ready!(self.as_mut().poll_flush_buffer(cx))?;
|
||||||
|
futures::ready!(self.project().conn.poll_close(cx))?;
|
||||||
|
Poll::Ready(Ok(()))
|
||||||
|
}
|
||||||
|
}
|
50
src/server/service.rs
Normal file
50
src/server/service.rs
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
use std::future::Future;
|
||||||
|
use std::task::Context;
|
||||||
|
use std::task::Poll;
|
||||||
|
|
||||||
|
use tower::Service;
|
||||||
|
|
||||||
|
pub trait MakeServiceRef<Target, Request>: self::sealed::Sealed<(Target, Request)> {
|
||||||
|
type Response;
|
||||||
|
type Error;
|
||||||
|
type Service: Service<Request, Response = Self::Response, Error = Self::Error>;
|
||||||
|
|
||||||
|
type MakeError;
|
||||||
|
type Future: Future<Output = Result<Self::Service, Self::MakeError>>;
|
||||||
|
|
||||||
|
fn poll_ready_ref(&mut self, cx: &mut Context) -> Poll<Result<(), Self::MakeError>>;
|
||||||
|
fn make_service_ref(&mut self, target: &Target) -> Self::Future;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M, S, Target, Request> self::sealed::Sealed<(Target, Request)> for M
|
||||||
|
where
|
||||||
|
M: for<'a> Service<&'a Target, Response = S>,
|
||||||
|
S: Service<Request>,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M, ME, MF, S, Target, Request> MakeServiceRef<Target, Request> for M
|
||||||
|
where
|
||||||
|
M: for<'a> Service<&'a Target, Response = S, Future = MF, Error = ME>,
|
||||||
|
MF: Future<Output = Result<S, ME>>,
|
||||||
|
S: Service<Request>,
|
||||||
|
{
|
||||||
|
type Response = S::Response;
|
||||||
|
type Error = S::Error;
|
||||||
|
type Service = S;
|
||||||
|
|
||||||
|
type MakeError = ME;
|
||||||
|
type Future = MF;
|
||||||
|
|
||||||
|
fn poll_ready_ref(&mut self, cx: &mut Context) -> Poll<Result<(), Self::MakeError>> {
|
||||||
|
self.poll_ready(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_service_ref(&mut self, target: &Target) -> Self::Future {
|
||||||
|
self.call(target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod sealed {
|
||||||
|
pub trait Sealed<T> {}
|
||||||
|
}
|
Loading…
Reference in a new issue