1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
//! # Description
//!
//! This crate aims to make bi-directional, nonblocking I/O easier to reason about by using patterns that extend beyond dealing directly
//! with raw bytes, the [`std::io::Read`] and [`std::io::Write`] traits, and [`std::io::ErrorKind::WouldBlock`] errors.
//! Since this crate's main focus is nonblocking I/O, all [`Session`] implementations provided by this crate are non-blocking by default.
//!
//! # Sessions
//!
//! The core [`Session`] API utilizes associated types to express nonblocking read and write operations.
//! While the [`tcp`] module provides a [`Session`] implementation that provides unframed non-blocking binary IO operations,
//! other [`Session`] impls are able to provide significantly more functionality using the same non-blocking patterns.
//!
//! # Associated Types
//!
//! Sessions operate on implementation-specific [`Session::ReadData`] and [`Session::WriteData`] types.
//! Instead of populating a mutable buffer provided by the user a-la [`std::io::Read`] operations, a reference to a received event is returned.
//! This allows [`Session`] implementations to perform internal buffering, framing, and serialization to support implementation-specific types.
//!
//! # Errors
//!
//! The philosophy of this crate is that an [`Err`] should always represent a transport or protocol-level error.
//! An [`Err`] should not be returned by a function as a condition that should be handled during **normal** branching logic.
//! As a result, instead of forcing you to handle [`std::io::ErrorKind::WouldBlock`] everywhere you deal with nonblocking code,
//! this crate will indicate partial read/write operations using [`ReadStatus::None`], [`ReadStatus::Buffered`], and [`WriteStatus::Pending`]
//! as [`Result::Ok`].
//!
//! # Non-Blocking Examples
//!
//! ## Streaming TCP
//!
//! The following example shows how to use streaming TCP to send and receive a traditional stream of bytes.
//!
//! ```no_run
//! use nbio::{ReadStatus, Session, WriteStatus};
//! use nbio::tcp::StreamingTcpSession;
//!
//! // establish connection
//! let mut client = StreamingTcpSession::connect("192.168.123.456:54321").unwrap();
//!
//! // send some bytes until completion
//! let mut remaining_write = "hello world!".as_bytes();
//! while let WriteStatus::Pending(pending) = client.write(remaining_write).unwrap() {
//! remaining_write = pending;
//! client.drive().unwrap();
//! }
//!
//! // print received bytes
//! loop {
//! if let ReadStatus::Data(data) = client.read().unwrap() {
//! println!("received: {data:?}");
//! }
//! }
//! ```
//!
//! ## Framing TCP
//!
//! The following example shows how to [`frame`] messages over TCP to send and receive payloads framed with a preceeding u64 length field.
//! Notice how it is almost identical to the code above, except it guarantees that read slices are always identical to their corresponding write slices.
//!
//! ```no_run
//! use nbio::{ReadStatus, Session, WriteStatus};
//! use nbio::tcp::StreamingTcpSession;
//! use nbio::frame::{FramingSession, U64FramingStrategy};
//!
//! // establish connection wrapped in a framing session
//! let client = StreamingTcpSession::connect("192.168.123.456:54321").unwrap();
//! let mut client = FramingSession::new(client, U64FramingStrategy::new(), 4096);
//!
//! // send some bytes until completion
//! let mut remaining_write = "hello world!".as_bytes();
//! while let WriteStatus::Pending(pending) = client.write(remaining_write).unwrap() {
//! remaining_write = pending;
//! client.drive().unwrap();
//! }
//!
//! // print received bytes
//! loop {
//! if let ReadStatus::Data(data) = client.read().unwrap() {
//! println!("received: {data:?}");
//! }
//! }
//! ```
//!
//! ## HTTP Request/Response
//!
//! The following example shows how to use the [`http`] module to drive an HTTP 1.x request/response using the same non-blocking model.
//! Notice how the primitives of driving a buffered write to completion and receiving a framed response is the same as any other framed session.
//! In fact, the `conn` returned by `client.request(..)` is simply a [`frame::FramingSession`] that utilizes a [`http::Http1FramingStrategy`].
//!
//! ```no_run
//! use http::Request;
//! use nbio::{Session, ReadStatus};
//! use nbio::http::HttpClient;
//! use tcp_stream::OwnedTLSConfig;
//!
//! // create the client and make the request
//! let mut client = HttpClient::new(OwnedTLSConfig::default());
//! let mut conn = client
//! .request(Request::get("http://icanhazip.com").body(()).unwrap())
//! .unwrap();
//!
//! // drive and read the conn until a full response is received
//! loop {
//! conn.drive().unwrap();
//! if let ReadStatus::Data(r) = conn.read().unwrap() {
//! // validate the response
//! println!("Response Body: {}", String::from_utf8_lossy(r.body()));
//! break;
//! }
//! }
//! ```
use Error;
use TLSConfig;
pub extern crate tcp_stream;
pub extern crate http as hyperium_http;
/// A bi-directional connection supporting generic read and write events.
///
/// ## Connecting
///
/// Some implementations may not default to a connected state.
/// The `is_connected` and `try_connect` functions can drive the session to a connected state.
///
/// ## Retrying
///
/// The [`Ok`] result of `read(..)` and `write(..)` operations may return `None`, `Pending`, or `Buffered`.
/// These statuses indicate that a read or write operation may need to be retried.
/// See [`ReadStatus`] and [`WriteStatus`] for more details.
///
/// ## Duty Cycles
/// Optionally implemented for [`Session`] implementations that support TLS
/// Returned by the [`Session`] read function, providing the outcome or information about the read action.
///
/// The generic type `T` will match the cooresponding [`Session::ReadData`].
/// Returned by the [`Session`] write function, providing the outcome of the write action.
///
/// The generic type `T` will match the cooresponding [`Session::ReadData`].