roa-core 0.5.0-alpha

core components of roa web framework
Documentation
[![Build status](https://img.shields.io/travis/Hexilee/roa/master.svg)](https://travis-ci.org/Hexilee/roa)
[![codecov](https://codecov.io/gh/Hexilee/roa/branch/master/graph/badge.svg)](https://codecov.io/gh/Hexilee/roa)
[![Rust Docs](https://docs.rs/roa-core/badge.svg)](https://docs.rs/roa-core)
[![Crate version](https://img.shields.io/crates/v/roa-core.svg)](https://crates.io/crates/roa-core)
[![Download](https://img.shields.io/crates/d/roa-core.svg)](https://crates.io/crates/roa-core)
[![Version](https://img.shields.io/badge/rustc-1.39+-lightgray.svg)](https://blog.rust-lang.org/2019/11/07/Rust-1.39.0.html)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/Hexilee/roa/blob/master/LICENSE)

### Introduction

Roa is an async web framework inspired by koajs, lightweight but powerful.

### Application

A Roa application is a structure containing a middleware group which composes and executes middleware functions in a stack-like manner.
The obligatory hello world application:

```rust
use roa_core::App;
use log::info;
use std::error::Error as StdError;
#[async_std::main]
async fn main() -> Result<(), Box<dyn StdError>> {
    let mut app = App::new(());
    app.end(|mut ctx| async move {
        ctx.resp_mut().await.write_str("Hello, World");
        Ok(())
    });
    app.listen("127.0.0.1:8000", |addr| {
        info!("Server is listening on {}", addr)
    })?
    .await?;
    Ok(())
}
```

#### Cascading

The following example responds with "Hello World", however, the request flows through
the `logging` middleware to mark when the request started, then continue
to yield control through the response middleware. When a middleware invokes `next().await`
the function suspends and passes control to the next middleware defined. After there are no more
middleware to execute downstream, the stack will unwind and each middleware is resumed to perform
its upstream behaviour.

```rust
use roa_core::App;
use log::info;
use std::error::Error as StdError;
use std::time::Instant;
#[async_std::main]
async fn main() -> Result<(), Box<dyn StdError>> {
    let mut app = App::new(());
    app.gate_fn(|_ctx, next| async move {
        let inbound = Instant::now();
        next().await?;
        info!("time elapsed: {} ms", inbound.elapsed().as_millis());
        Ok(())
    });
    app.end(|mut ctx| async move {
        ctx.resp_mut().await.write_str("Hello, World");
        Ok(())
    });
    app.listen("127.0.0.1:8000", |addr| {
        info!("Server is listening on {}", addr)
    })?
    .await?;
    Ok(())
}
```

### Error Handling

You can catch or straightly throw an Error returned by next.

```rust
use roa_core::{App, throw};
use async_std::task::spawn;
use http::StatusCode;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let (addr, server) = App::new(())
        .gate_fn(|ctx, next| async move {
            // catch
            if let Err(err) = next().await {
                // teapot is ok
                if err.status_code != StatusCode::IM_A_TEAPOT {
                    return Err(err)
                }
            }
            Ok(())
        })
        .gate_fn(|ctx, next| async move {
            next().await?; // just throw
            unreachable!()
        })
        .end(|_ctx| async move {
            throw!(StatusCode::IM_A_TEAPOT, "I'm a teapot!")
        })
        .run_local()?;
    spawn(server);
    let resp = reqwest::get(&format!("http://{}", addr)).await?;
    assert_eq!(StatusCode::OK, resp.status());
    Ok(())
}
```

#### error_handler

App has an error_handler to handle `Error` thrown by the top middleware.
This is the error_handler:

```rust
use roa_core::{Context, Error, Result, State, ErrorKind};
pub async fn error_handler<S: State>(mut context: Context<S>, err: Error) -> Result {
    context.resp_mut().await.status = err.status_code;
    if err.expose {
        context.resp_mut().await.write_str(&err.message);
    }
    if err.kind == ErrorKind::ServerError {
        Err(err)
    } else {
        Ok(())
    }
}
```

The Error thrown by this error_handler will be handled by hyper.

### Use custom runtime.

Use `hyper::Server::builder` to construct a hyper server with your custom runtime.

#### Example
```rust
use roa_core::{App, Server, AddrIncoming, Executor};
use std::future::Future;
/// An implementation of hyper::rt::Executor based on tokio
#[derive(Copy, Clone)]
pub struct Exec;
impl<F> Executor<F> for Exec
where
    F: 'static + Send + Future,
    F::Output: 'static + Send,
{
    #[inline]
    fn execute(&self, fut: F) {
        tokio::task::spawn(fut);
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let app = App::new(());
    let server = Server::builder(AddrIncoming::bind("127.0.0.1:8080")?)
        .executor(Exec)
        .serve(app);
    server.await?;
    Ok(())
}
```