Expand description
Netty-rs allows exposes a simple-to-use API used to create stateful application level network protocols as both a client or server.
Netty-rs allows requires consumers specify how to handle messages in different circumstances. Whenever specifying this the same API is used by using Connection. This very simple API allows consumers to specify restful protocols of varying complexity. Each message-and-reply chain is in their own channel which does not and is not impacted by messages sent or received in other message-and-reply chains.
The situations where how to handle messages need to be specified are:
- When a consumer sends a message it can choose to wait for a reply and handle it in a custom way.
- When acting as a server consumers need to specify how to handshake with new connections, which allows custom authentication of clients among any other handshake related action.
- When acting as a server consumers need to specify how to handle non-reply messages from connections that have already been authenticated.
The main API is accessed through the Networker struct.
Netty-rs uses the DirectoryService trait in order to allow consumers to either implement
their own directory service for example a DNS or use the SimpleDirectoryService
struct that
implements this trait.
Example
fn generate_challenge() -> Vec<u8> {
// Generate the challenge ...
}
fn verify_challenge(a: &Vec<u8>) -> bool {
// Verify challenge answer ...
}
fn sign(c: &Vec<u8>) -> Vec<u8> {
// Sign the challenge
}
// Enum for the different types of messages we want to send
#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)]
enum Content {
Init,
Challenge(Vec<u8>),
Answer(Vec<u8>),
Accept,
Deny,
Request,
Response(i32),
ProtocolError,
}
let ds = SimpleDirectoryService::new();
let networker = Networker::new("127.0.0.1:8080".parse().unwrap(), ds,
|handshake_msg: NetworkMessage<Content>, mut con: Connection<Content, MyError>| async move {
// Perhaps you authenticate by producing a challenge and then
// waiting for a response
let challenge = generate_challenge();
let message = handshake_msg.reply(Content::Challenge(challenge));
let timeout = Duration::from_secs(2);
// On timeout or other errors we just abort this whole process
let response = con.send_message_await_reply(message, Some(timeout)).await?;
if let Content::Answer(a) = &response.content {
if verify_challenge(a) {
let accept_msg = response.reply(Content::Accept);
con.send_message(accept_msg).await?;
} else {
let deny_msg = response.reply(Content::Deny);
con.send_message(deny_msg).await?;
}
} else {
let deny_msg = response.reply(Content::Deny);
con.send_message(deny_msg).await?;
}
// Return the id of this client
let inner = Arc::try_unwrap(handshake_msg.from).unwrap_or_else(|e| (*e).clone());
Ok(inner)
},
|message: NetworkMessage<Content>, mut con: Connection<Content, MyError>| async move {
if let Content::Request = message.content {
// Respond with the magical number for the meaning of life
let response = message.reply(Content::Response(42));
con.send_message(response).await?;
} else {
let response = message.reply(Content::ProtocolError);
con.send_message(response).await?;
}
Ok(())
}).await.map_err(|_| MyError)?;
networker.listen(true).await.map_err(|_| MyError)?;
// Send a message to ourselves
let first_message = NetworkMessage::new(
Arc::new("127.0.0.1:8080".to_string()),
Arc::new("127.0.0.1:8080".to_string()),
Content::Init,
);
let timeout = Duration::from_secs(2);
let action = Action::new(
|msg: NetworkMessage<Content>, mut con: Connection<Content, MyError>| {
async move {
if let Content::Challenge(c) = &msg.content {
let answer = sign(c);
let resp = msg.reply(Content::Answer(answer));
let timeout = Duration::from_secs(2);
let accept = con.send_message_await_reply(resp, Some(timeout)).await?;
if let Content::Accept = accept.content {
Ok(())
} else {
Err(MyError.into())
}
} else {
Err(MyError.into())
}
}
.boxed()
},
);
networker
.send_message(first_message, Some(timeout), Some(action))
.await.map_err(|_| MyError)?;
Result::<(), MyError>::Ok(())
Structs§
- Action
- Action contains a closure that handles the communication on a channel
- Connection
- A connection serves as the main API to specify how a network conversation should look. It
allows sending messages to the recipiant and awaiting their response using the two methods
send_message
andsend_message_await_reply
. - Error
- Error returned by netty-rs
- Network
Message - This struct represents the network messages sent and received, it can be created either from
the new
new
constructor if a fresh message is desired. If a reply is desired then thereply
method should be used. - Networker
- This struct is the main API for using netty. It allows the creation of a server and the ability to send messages to clients.
- Simple
Directory Service - Directory service that translates Strings and socket addresses to socket addresses Strings have to be in the format of “127.0.0.1:8080”
Traits§
- Directory
Service - Netty-rs uses a pluggable directory service to translate from an id to an IP address and port number. This is provided via this trait. A SimpleDirectoryService struct is provided which translates from any type that implements ToSocketAddrs. This includes strings which are in the format of for example “127.0.0.1:8080”, which will allow consumers to easily use strings or IP addresses as identifiers. If a more complicated lookup is required then implementing this trait is always avaliable.
- Handler
Error - Marker trait for errors that are returned by the handlers
- Network
Content - Marker trait for the content contained in the NetworkMessage struct