Handling clients

Let's now see the handle_client() function we've just mentioned:

use std::result;

use futures::prelude::await;

#[async]
use tokio_core::net::TcpStream;

fn handle_client(stream: TcpStream) -> result::Result<(), ()> {
    await!(client(stream))
        .map_err(|error| println!("Error handling client: {}", error))
}

It is a simple wrapper over the client future. Here, we used a new macro, await!, which allows us to write asynchronous code in an asynchronous way. When the result of the future inside await!() is not ready, the event loop will execute other stuff, and when it's ready it will continue executing the code after the await!(). In this case, we print the error returned by the client future. This is why we needed a wrapper.

Now, let's write this client future:

use futures::{Sink, Stream};
use futures::stream::SplitSink;
use tokio_io::AsyncRead;
use tokio_io::codec::Framed;

use codec::FtpCodec;
use error::Result;
use ftp::{Answer, ResultCode};

#[async]
fn client(stream: TcpStream) -> Result<()> {
    let (writer, reader) = stream.framed(FtpCodec).split();
    let writer = await!(writer.send(Answer::new(ResultCode::ServiceReadyForNewUser, 
"Welcome to this FTP server!")))?; let mut client = Client::new(writer); #[async] for cmd in reader { client = await!(client.handle_cmd(cmd))?; } println!("Client closed"); Ok(()) }

Here, we specify that the stream will be handled by a FtpCodec, which means that we'll be able to encode and decode structured data instead of dealing with bytes directly. We'll write this FtpCodec soon. Then, we split the stream between a reader and a writer. This split() method is very useful in Rust, because of ownership: we cannot have two owners, one that will write to the socket and another that will read to it. To fix this issue, we split the stream and we can now have an owner for the reader and another owner for the writer.

Then, we use the writer to send a welcome message. Again, we use the await! macro to specify that the code after will be executed when the message is sent (but without blocking the whole program, thanks to asynchronous I/O). Next, we create a Client which will be the object that will manage a client, by executing the appropriate actions when it receives commands and sending the right responses.

After that, we use again an #[async] for loop to iterate over a stream; here, we iterate over the stream of the data received by this specific client. In the for loop, we call the handle_cmd() method that we will soon write. This method, as its name indicates, will handle the command received from this FTP client, act accordingly, and send a response back. Here, we use await!()? with a question mark at the end. The futures-await crate allows us to do so; this means that if the future returned an error, this error will propagate to the client future, which is the same semantic for the normal ? operator used in a function returning a Result. We'll see why we reassign the result to client when we write the handle_cmd() method.