retty/lib.rs
1//! ### What is Retty?
2//! Retty is an asynchronous Rust networking framework that makes it easy to build protocols, application clients/servers.
3//!
4//! It's like [Netty](https://netty.io) or [Wangle](https://github.com/facebook/wangle), but in Rust.
5//!
6//! ### What is a Pipeline?
7//! The fundamental abstraction of Retty is the [Pipeline](crate::channel::Pipeline).
8//! It offers immense flexibility to customize how requests and responses are handled by your service.
9//! Once you have fully understood this abstraction,
10//! you will be able to write all sorts of sophisticated protocols, application clients/servers.
11//!
12//! A [Pipeline](crate::channel::Pipeline) is a chain of request/response [handlers](crate::channel::Handler) that handle inbound request and
13//! outbound response. Once you chain handlers together, it provides an agile way to convert
14//! a raw data stream into the desired message type and the inverse -- desired message type to raw data stream.
15//! Pipeline implements an advanced form of the Intercepting Filter pattern to give a user full control
16//! over how an event is handled and how the handlers in a pipeline interact with each other.
17//!
18//! A [Handler](crate::channel::Handler) should do one and only one function - just like the UNIX philosophy. If you have a handler that
19//! is doing more than one function than you should split it into individual handlers. This is really important for
20//! maintainability and flexibility as its common to change your protocol for one reason or the other.
21//!
22//! ### How does an event flow in a Pipeline?
23//! ```text
24//! | write()
25//! +---------------------------------------------------+---------------+
26//! | Pipeline | |
27//! | \|/ |
28//! | +----------+----------+------------+-----------+----------+ |
29//! | | Handler N | |
30//! | +----------+----------+------------+-----------+----------+ |
31//! | /|\ | |
32//! | | | |
33//! | | | |
34//! | | \|/ |
35//! | +----------+----------+------------+-----------+----------+ |
36//! | | Handler N-1 | |
37//! | +----------+----------+------------+-----------+----------+ |
38//! | /|\ | |
39//! | | | |
40//! | | Context.fire_poll_write() |
41//! | | | |
42//! | | | |
43//! | Context.fire_read() | |
44//! | | | |
45//! | | \|/ |
46//! | +----------+----------+------------+-----------+----------+ |
47//! | | Handler 2 | |
48//! | +----------+----------+------------+-----------+----------+ |
49//! | /|\ | |
50//! | | | |
51//! | | | |
52//! | | \|/ |
53//! | +----------+----------+------------+-----------+----------+ |
54//! | | Handler 1 | |
55//! | +----------+----------+------------+-----------+----------+ |
56//! | /|\ | |
57//! +---------------+-----------------------------------+---------------+
58//! | read() | poll_write()
59//! | \|/
60//! +---------------+-----------------------------------+---------------+
61//! | | | |
62//! | Internal I/O Threads (Transport Implementation) |
63//! +-------------------------------------------------------------------+
64//! ```
65//!
66//! ### Echo Server Example
67//! Let's look at how to write an echo server.
68//!
69//! Here's the main piece of code in our echo server; it receives a string from inbound direction in the pipeline,
70//! prints it to stdout and sends it back to outbound direction in the pipeline. It's really important to add the
71//! line delimiter because our pipeline will use a line decoder.
72//! ```ignore
73//! struct EchoServerHandler {
74//! transmits: VecDeque<TaggedString>,
75//! }
76//!
77//! impl Handler for EchoServerHandler {
78//! type Rin = TaggedString;
79//! type Rout = Self::Rin;
80//! type Win = TaggedString;
81//! type Wout = Self::Win;
82//!
83//! fn name(&self) -> &str {
84//! "EchoServerHandler"
85//! }
86//!
87//! fn handle_read(
88//! &mut self,
89//! _ctx: &Context<Self::Rin, Self::Rout, Self::Win, Self::Wout>,
90//! msg: Self::Rin,
91//! ) {
92//! println!("handling {}", msg.message);
93//! self.transmits.push_back(TaggedString {
94//! now: Instant::now(),
95//! transport: msg.transport,
96//! message: format!("{}\r\n", msg.message),
97//! });
98//! }
99//!
100//! fn poll_write(
101//! &mut self,
102//! ctx: &Context<Self::Rin, Self::Rout, Self::Win, Self::Wout>,
103//! ) -> Option<Self::Wout> {
104//! if let Some(msg) = ctx.fire_poll_write() {
105//! self.transmits.push_back(msg);
106//! }
107//! self.transmits.pop_front()
108//! }
109//! }
110//! ```
111//!
112//! This needs to be the final handler in the pipeline. Now the definition of the pipeline is needed to handle the requests and responses.
113//! ```ignore
114//! let mut bootstrap = BootstrapServerTcp::new();
115//! bootstrap.pipeline(Box::new(move || {
116//! let pipeline: Pipeline<TaggedBytesMut, TaggedString> = Pipeline::new();
117//!
118//! let line_based_frame_decoder_handler = TaggedByteToMessageCodec::new(Box::new(
119//! LineBasedFrameDecoder::new(8192, true, TerminatorType::BOTH),
120//! ));
121//! let string_codec_handler = TaggedStringCodec::new();
122//! let echo_server_handler = EchoServerHandler::new();
123//!
124//! pipeline.add_back(line_based_frame_decoder_handler);
125//! pipeline.add_back(string_codec_handler);
126//! pipeline.add_back(echo_server_handler);
127//! pipeline.finalize()
128//! }));
129//! ```
130//!
131//! It is very important to be strict in the order of insertion as they are ordered by insertion. The pipeline has 4 handlers:
132//!
133//! * [TaggedByteToMessageCodec](crate::codec::byte_to_message_decoder::TaggedByteToMessageCodec)
134//! * Inbound: receives a zero-copy byte buffer and splits on line-endings
135//! * Outbound: just passes the byte buffer to AsyncTransport
136//! * [TaggedStringCodec](crate::codec::string_codec::TaggedStringCodec)
137//! * Inbound: receives a byte buffer and decodes it into a std::string and pass up to the EchoHandler.
138//! * Outbound: receives a std::string and encodes it into a byte buffer and pass down to the TaggedByteToMessageCodec.
139//! * EchoHandler
140//! * Inbound: receives a std::string and writes it to the pipeline, which will send the message outbound.
141//! * Outbound: receives a std::string and forwards it to TaggedStringCodec.
142//!
143//! Now that all needs to be done is plug the pipeline factory into a [BootstrapServerTcp](crate::bootstrap::BootstrapTcpServer) and that’s pretty much it.
144//! Bind a local host:port and wait for it to stop.
145//!
146//! ```ignore
147//! bootstrap.bind(format!("{}:{}", host, port)).await?;
148//!
149//! println!("Press ctrl-c to stop");
150//! tokio::select! {
151//! _ = tokio::signal::ctrl_c() => {
152//! bootstrap.graceful_stop().await;
153//! }
154//! };
155//! ```
156//!
157//! ### Echo Client Example
158//! The code for the echo client is very similar to the Echo Server. Here is the main echo handler.
159//!
160//! ```ignore
161//! struct EchoClientHandler;
162//!
163//! impl Handler for EchoClientHandler {
164//! type Rin = TaggedString;
165//! type Rout = Self::Rin;
166//! type Win = TaggedString;
167//! type Wout = Self::Win;
168//!
169//! fn name(&self) -> &str {
170//! "EchoClientHandler"
171//! }
172//!
173//! fn handle_read(
174//! &mut self,
175//! _ctx: &Context<Self::Rin, Self::Rout, Self::Win, Self::Wout>,
176//! msg: Self::Rin,
177//! ) {
178//! println!("received back: {}", msg.message);
179//! }
180//!
181//! fn read_exception(
182//! &mut self,
183//! ctx: &Context<Self::Rin, Self::Rout, Self::Win, Self::Wout>,
184//! err: Box<dyn Error + Send + Sync>,
185//! ) {
186//! println!("received exception: {}", err);
187//! ctx.fire_close();
188//! }
189//!
190//! fn read_eof(&mut self, ctx: &Context<Self::Rin, Self::Rout, Self::Win, Self::Wout>) {
191//! println!("EOF received :(");
192//! ctx.fire_close();
193//! }
194//!
195//! fn poll_write(
196//! &mut self,
197//! ctx: &Context<Self::Rin, Self::Rout, Self::Win, Self::Wout>,
198//! ) -> Option<Self::Wout> {
199//! ctx.fire_poll_write()
200//! }
201//! }
202//! ```
203//!
204//! Notice that we override other methods—read_exception and read_eof.
205//! There are few other methods that can be overriden. If you need to handle a particular event,
206//! just override the corresponding method.
207//!
208//! Now onto the client’s pipeline factory. It is identical the server’s pipeline factory, which
209//! handles writing data.
210//! ```ignore
211//! let mut bootstrap = BootstrapClientTcp::new();
212//! bootstrap.pipeline(Box::new( move || {
213//! let pipeline: Pipeline<TaggedBytesMut, TaggedString> = Pipeline::new();
214//!
215//! let line_based_frame_decoder_handler = TaggedByteToMessageCodec::new(Box::new(
216//! LineBasedFrameDecoder::new(8192, true, TerminatorType::BOTH),
217//! ));
218//! let string_codec_handler = TaggedStringCodec::new();
219//! let echo_client_handler = EchoClientHandler::new();
220//!
221//! pipeline.add_back(line_based_frame_decoder_handler);
222//! pipeline.add_back(string_codec_handler);
223//! pipeline.add_back(echo_client_handler);
224//! pipeline.finalize()
225//! }));
226//! ```
227//!
228//! Now that all needs to be done is plug the pipeline factory into a BootstrapTcpClient and that’s pretty much it.
229//! Connect to the remote peer and then read line from stdin and write it to pipeline.
230//! ```ignore
231//! let pipeline = bootstrap.connect(format!("{}:{}", host, port)).await?;
232//!
233//! println!("Enter bye to stop");
234//! let mut buffer = String::new();
235//! while tokio::io::stdin().read_line(&mut buffer).await.is_ok() {
236//! match buffer.trim_end() {
237//! "" => break,
238//! line => {
239//! pipeline.write(TaggedString {
240//! now: Instant::now(),
241//! transport,
242//! message: format!("{}\r\n", line),
243//! });
244//! if line == "bye" {
245//! pipeline.close();
246//! break;
247//! }
248//! }
249//! };
250//! buffer.clear();
251//! }
252//!
253//! bootstrap.graceful_stop().await;
254//! ```
255#![doc(html_logo_url = "https://raw.githubusercontent.com/retty-io/retty/master/docs/retty.io.jpg")]
256#![warn(rust_2018_idioms)]
257#![allow(dead_code)]
258#![warn(missing_docs)]
259
260pub mod bootstrap;
261pub mod channel;
262pub mod codec;
263pub mod executor;
264pub mod transport;