Web Application Server by Rust with actix-web

# はじめに

最近業務で[actix-web](https://github.com/actix/actix-web)によるWeb Application Server開発を実施する機会がありましたので、備忘録を兼ねて記事にしようと思います。
英語になりますが公式ドキュメントには詳細な説明が記載されていますので、必要に応じてそちらを参照下さい。

## actix-webについて

[actix-web](https://github.com/actix/actix-web)とは現在非常に活発に開発が進められているRustのWeb application frameworkです。
公式からの引用になりますが、主な特徴としては以下の通りとなります。

* Supported HTTP/1.x and HTTP/2.0 protocols
* Streaming and pipelining
* Keep-alive and slow requests handling
* Client/server WebSockets support
* Transparent content compression/decompression (br, gzip, deflate)
* Configurable request routing
* Graceful server shutdown
* Multipart streams
* Static assets
* SSL support with OpenSSL or native-tls
* Middlewares (Logger, Session, Redis sessions, DefaultHeaders, CORS, CSRF)
* Includes an asynchronous HTTP client
* Built on top of Actix actor framework

タイプセーフかつ新しいFrameworkであるためモダンな記述が可能となっており、同期、非同期処理に加えて[actix](https://github.com/actix/actix)を使用したアクターモデルでの開発が可能な上、[こちら](https://www.techempower.com/benchmarks/#section=data-r15&hw=ph&test=fortune)にある通りパフォーマンスが優れているのが特徴です。

## 前提

* Rust 1.26
* actix-web 0.6.10

# 本題

## サンプルコード

公式ページのExampleより転記します。
こちらをベースに解説していきます。

```toml:Cargo.toml
[package]
name = "qiita"
version = "0.1.0"
authors = ["test"]

[dependencies]
actix = "0.5"
actix-web = "0.6"
futures = "0.1"
serde = "1"
serde_derive = "1"
tokio-threadpool = "0.1"
```

```rust:main.rs
extern crate actix_web;
use actix_web::{http, server, App, Path};

fn index(info: Path<(u32, String)>) -> String {
    format!("Hello {}! id:{}", info.1, info.0)
}

fn main() {
    server::new(
        || App::new()
            .route("/{id}/{name}/index.html", http::Method::GET, index))
        .bind("127.0.0.1:8080").unwrap()
        .run();
}
```

```
$ curl http://localhost:8080/100/test/index.html
Hello test! id:100
```

## HttpServer

actix-webではTop levelに`HttpServer`が必要になります。
サンプルコードでは`server::new`により作成されています。
`HttpServer`では主に以下の設定を行います。

* App factory
* hostname
* worker thread
* keep-alive
* backlog
* bind address
* SSL

actix-webはactixというアクターモデルライブラリの上に構築されています。
詳細は記述しませんが、サンプルコードの`run`メソッドではactix周りの初期化と実行、処理のブロックを行っています。
actixによるアクターモデルを使用した開発を行いたい場合は、以下のようにactixの`System`生成を自力で実施する必要があります。

```rust:main.rs
extern crate actix;
extern crate actix_web;

use actix::prelude::*;
use actix_web::{http, server, App, Path};

fn index(info: Path<(u32, String)>) -> String {
    format!("Hello {}! id:{}", info.1, info.0)
}

fn main() {
    let sys = System::new("system");
    server::new(
        || App::new()
            .route("/{id}/{name}/index.html", http::Method::GET, index))
        .bind("127.0.0.1:8080").unwrap()
        .start();
    let _ = sys.run();
}
```

尚、`HttpServer`では以下のように`App`を複数関連付けて、prefixによって使用する`App`を変更することも出来ます。

```rust:main.rs
use actix_web::{server, App, HttpResponse};

fn main() {
    server::new(|| vec![
        App::new()
            .prefix("/app1")
            .resource("/", |r| r.f(|r| HttpResponse::Ok())),
        App::new()
            .prefix("/app2")
            .resource("/", |r| r.f(|r| HttpResponse::Ok())),
        App::new()
            .resource("/", |r| r.f(|r| HttpResponse::Ok())),
    ]);
}
```

その他設定例を以下に記載しておきます。

```rust:main.rs
extern crate actix_web;
use actix_web::{http, server, App, HttpRequest};

fn main() {
    server::new(
        || App::new()
            .route("/", http::Method::GET, |_: HttpRequest| "test"))
        .workers(100)
        .backlog(100)
        .keep_alive(server::KeepAlive::Timeout(75))
        .server_hostname("test".to_string())
        .bind("127.0.0.1:8080").unwrap()
        .run();
}
```

## App

`App`は`HttpServer`における1つのworkerが保持するアプリケーションインスタンスです。
こちらでは主に以下の設定を行います。

* Routing
* State
* middleware

`App`は主に`HttpServer`作成時に`new`メソッドの引数として`App`を作成するFactoryを渡し、`HttpServer`が各worker threadを立ち上げる際に渡されたFactoryを実行することで作成されます。
特筆すべき点としては、各worker threadで動作する`App`は全て別のインスタンスであるということです。
こちらは後述する`State`の共有を行う際に意識する必要があります。

### Routing

Routingは`route`又は`resource`メソッドを使用して構築します。
それぞれのメソッドは`App`を返却するため、メソッドチェインして必要な分を設定していくことになります。

簡単な説明としては、`route`は一つのHTTP Methodに対して一つの`Handler`を設定し、`resource`は`ResourceHandler`を引数にとる`Fn`を設定し、その中で`route`よりも細かに設定をすることが出来ます。
例えば、HTTP Methodは問わずに全て同じ`Handler`で処理を行いたい場合や、HTTP Headerの値で`Handler`を分ける場合等に使用できます。

`route`及び`resource`では第一引数としてpathを設定します。
ここで設定されたpathに合致した場合に、同時に設定された`Handler`が実行されることとなります。
pathには{}を使用して`Handler`内で値を抽出することも可能です。サンプルコードでは`"/{id}/{name}/index.html"`のように設定されていますが、こちらの`{id}`及び`{name}`部分を設定された名前で抽出しています。
また、`"/{id}/{name}/{tail:.*}"`の`tail`のように、`:`の後に正規表現を設定することで、デフォルト正規表現である`[^{}/]+`を変更して柔軟に抽出することも可能です。

Routing設定例を以下に記載します。

```rust:main.rs
extern crate actix_web;
use actix_web::{http, server, pred, App, HttpRequest};

fn index(_: HttpRequest) -> &'static str {
    "index"
}

fn register(_: HttpRequest) -> &'static str {
    "register"
}

fn list(_: HttpRequest) -> &'static str {
    "list"
}

fn login(_: HttpRequest) -> &'static str {
    "login"
}

fn main() {
    server::new(
        || App::new()
            // Getのみ
            .route("/", http::Method::GET, index)
            // Postのみ
            .route("/register", http::Method::POST, register)
            // 全HTTP Method
            .resource("/list", |r| r.f(list))
            // Get/PostかつContent-Typeが"text/html"のみ
            .resource("/login", |r| r.route()
                      .filter(pred::Any(pred::Get()).or(pred::Post()))
                      .filter(pred::Header("Content-Type", "text/html"))
                      .f(login)))
        .bind("127.0.0.1:8080").unwrap()
        .run();
}
```

`Handler`については後述します。

### State

Config情報やConnection pool、Cache map等といったApp全体で共有したい情報は`App#new()`の代わりに`App#with_state(state: S)`を使用してAppを作成することで`State`を共有することが可能です。
設定された`State`は`Handler`の中で取得して参照することができます。

尚、前述の通り`App`自体はworker threadの単位で作成されるため、全て異なるインスタンスになります。
`with_state`で設定する`State`も全て異なるインスタンスである必要があるため、インスタンスを個別に作成するか、インスタンスを共有するために`Arc`を使用して`clone`する必要があります。

また、`State`の値を変更出来るようにしたい場合は、`Mutex`や`RwLock`等を使用してInternal Mutabilityを持たせる必要があります。

`State`使用例を以下に記載します。

```rust:main.rs
extern crate actix_web;

use std::{
    collections::HashSet,
    sync::{
        Arc, RwLock,
        atomic::{AtomicUsize, Ordering}
    }
};
use actix_web::{http, server, App, HttpRequest};

struct ApplicationState {
    share_info: String,
    access_count: AtomicUsize,
    access_ips: RwLock<HashSet<String>>
}

fn index(req: HttpRequest<Arc<ApplicationState>>) -> String {
    let state = req.state();

    let first_ip = if let Some(ip) = req.connection_info().remote() {
        let ip = ip.split(":").next().unwrap();
        let access_ips = state.access_ips.read().unwrap();
        if !access_ips.contains(ip) {
            drop(access_ips);
            let mut access_ips = state.access_ips.write().unwrap();
            if !access_ips.contains(ip) {
                access_ips.insert(ip.to_string());
                true
            } else {
                false
            }
        } else {
            false
        }
    } else {
        false
    };

    let count = state.access_count.fetch_add(1, Ordering::SeqCst) + 1;

    format!("Share info: {}, Access count: {}, First IP: {}", state.share_info, count, first_ip)
}

fn main() {
    let app_state = Arc::new(ApplicationState {
        share_info: "share_info".to_string(),
        access_count: AtomicUsize::new(0),
        access_ips: RwLock::new(HashSet::new())
    });

    server::new(
        move || App::with_state(app_state.clone())
            .route("/", http::Method::GET, index))
        .bind("127.0.0.1:8080").unwrap()
        .run();
}
```

### middleware

middlewareを使用すると以下の時点で共通の処理を行うことができます。

* リクエスト開始時 (`Handler`処理前)
* レスポンス返却前 (`Handler`処理後)
* レスポンス返却後

actix-webで用意されているmiddlewareには次のようなものがあります。

* actix_web::middleware::DefaultHeaders
  * 該当headerがResponse headerに設定されていない場合に当middlewareで指定したheaderを設定する
* actix_web::middleware::ErrorHandlers
  * 処理した`Handler`が特定status codeが返却された場合に、指定された`Handler`を実行する
* actix_web::middleware::Logger
  * Access logを標準出力に出力する
* actix_web::middleware::cors
  * CORS設定を行う
* actix_web::middleware::csrf
  * CSRF対応を行う
    * GET, HEAD, OPTIONS以外の場合に、Origin headerがある場合はOrigin headerの値、無ければReferer内のOriginが設定された値と合致するかどうかで判定する
* actix_web::middleware::identity
  * ログイン状態管理を行う
  * デフォルトで用意されているものはCookieに暗号化したデータを保持する
* actix_web::middleware::session
  * セッション管理を行う
  * デフォルトで用意されているものはCookieに暗号化したデータを保持する

また、`Middleware` traitを実装することで自身で作成したり、外部crateで提供されている`Middleware`を使用することも可能です。

`Middleware` trait実装サンプルを以下に記載します。

```rust:main.rs
extern crate actix_web;                                                                                         [74/219]

use std::time::SystemTime;
use actix_web::{
    http, error, server, App, HttpRequest, HttpResponse,
    middleware::{ Middleware, Started, Response }
};

struct ResponseTime;

impl<S> Middleware<S> for ResponseTime {
    fn start(&self, req: &mut HttpRequest<S>) -> error::Result<Started> {
        let _ = req.extensions_mut().insert(SystemTime::now());
        Ok(Started::Done)
    }

    fn response(&self, req: &mut HttpRequest<S>, res: HttpResponse) -> error::Result<Response> {
        if let Some(start_time) = req.extensions().get::<SystemTime>() {
            if let Ok(duration) = start_time.elapsed() {
                println!("Process time: {}.{:09}", duration.as_secs(), duration.subsec_nanos());
            }
        }
        Ok(Response::Done(res))
    }
}

fn main() {
    server::new(|| App::new()
                .middleware(ResponseTime)
                .route("/", http::Method::GET, |_: HttpRequest| "Hello"))
        .bind("127.0.0.1:8080").unwrap()
        .workers(100)
        .run();
}
```

## Handler

`Handler`はRoutingに紐付けられて、リクエストを処理しレスポンスを返却するWeb Applicationにおける業務処理を記述する箇所となります。
尚、`Handler` traitというものがあり、そちらを実装したstructureも`Handler`となりますが、それ以外にもリクエストをハンドリングする形があるため、当記事ではそれらも含めてリクエストをハンドリングする処理を全て`Handler`と呼んでいます。

簡易的ではありますが、`Handler`には大きく4つの形があります。

### `FromRequest`を受けて`Responderを返却するタイプ
```
Fn(T) -> R + 'static
R: Responder + 'static
T: FromRequest<S> + 'static
```

通常使用する場合はこの形となります。
後述する`Extractor`を使用してリクエスト情報を引数に受けて、レスポンスを生成します。

**対象メソッド**

1. `App#route()`
2. `Route#with()`

### `HttpRequest<S>`を受けて`Responderを返却するタイプ
```
Fn(HttpRequest<S>) -> R + 'static
R: Responder + 'static
```

こちらは1つ目の形と似ていますが、`Extractor`は使用せずに`HttpRequest<S>`を引数に受けてレスポンスを生成します。
動的パスやクエリのハンドリングを`Extractor`を使用せず、自力で制御する形となります。
一般的なWeb Application Frameworkに近い形です。

**対象メソッド**

1. `Route#f()`
2. `Route#h()`

### `FromRequest`を受けて`Future<Item = I, Error = E>`を返却するタイプ
```
Fn(T) -> R + 'static
R: Future<Item = I, Error = E> + 'static
I: Responder + 'static
E: Into<Error> + 'static
T: FromRequest<S> + 'static
```

1つ目のパターンにおいて、非同期処理としてレスポンスを返却する形となります。
但し、`Responder`の実装の中にも`Box<Future<Item = I, Error = E>> (I: Responder, E: Into<Error>)`が存在するため、1つ目のパターンでも非同期処理を行うことが出来るため、このパターンを使用する場合についてはよく解っていません。
利点などご存知の方がいればご教示頂ければ幸いです。

**対象メソッド**

1. `Route#with_async()`

### `HttpRequest<S>`を受けて`Future<Item = R, Error = E>`を返却するタイプ
```
Fn(HttpRequest<S>) -> F + 'static
F: Future<Item = R, Error = E> + 'static
R: Responder + 'static
E: Into<Error> + 'static
```

3つ目の形において`Extractor`を使用せずに`HttpRequest<S>`を引数に受けるタイプです。

**対象メソッド**

1. `Route#a()`

### Extractor (FromRequest)

`Handler`では基本的に`HttpRequest`から全てのリクエスト情報及び`State`情報を取得することが出来ます。
しかしそれ以外にもURLパス文字(`Path`)やURL parameter (`Query`)等、`FromRequest`を実装しているものに対しては直接`Handler`の引数として受けることが可能です。
それらを使用した場合、パラメータ名や型等が合致しない場合には`Handler`自体が実行されず、HTTP status 400 (Bad request)が返却されることとなり、パラメータチェックをある程度自動化することが可能になります。
`Handler`の引数としては1つのパラメータしか受け付けられませんが、`FromRequest`は9個までのタプルにも対応しているため、複数の`Extractor`を使用することができます。
また、`HttpRequest`自体も`FromRequest`を実装しているため、HTTPヘッダー情報などが必要な場合は`HttpRequest`を含めて、URL parameterは`Query`を使用する等といった使用方法も可能になっています。
尚、`Query`等で必須ではないパラメータに対しては`Option`を定義することで受容することも可能です。
`Option`が定義されていない場合は必須パラメータとなります。

現時点では以下に対して`FromRequest`が実装されています。

* Bytes
  * Request Payload
* String
  * Request Payload
* Path
  * URL path parameter
* Query
  * URL query parameter
* Form
  * Request Payload
* State
* HttpRequest
* Json
  * Request Payload
* Session

サンプルコードは以下となります。

```rust:main.rs
extern crate actix_web;
#[macro_use]
extern crate serde_derive;

use actix_web::{http, server, App, Path, Query};

#[derive(Debug, Deserialize)]
struct IndexQuery {
    need: u32,
    option: Option<String>
}

fn index(input: (Path<(u32, String)>, Query<IndexQuery>)) -> String {
    let (info, query) = input;
    format!("Hello {}! id:{}. Query: {:?}", info.1, info.0, query.into_inner())
}

fn main() {
    server::new(
        || App::new()
            .route("/{id}/{name}/index.html", http::Method::GET, index))
        .bind("127.0.0.1:8080").unwrap()
        .run();
}
```

### Responder

`Responder`はレスポンスの具体的な内容を表します。
こちらも`Extractor`と同様に、`HttpResponse`が全てを柔軟に扱える一方で、簡易的な返却に関しては直接`String`を返却したりすることも可能になっています。
ただし、`HttpResponse`以外の`Responder`を使用する場合はContent Type等は決められた固定値になりますので、柔軟なコントロールが必要な際は自力で`HttpResponse`を構築して返却することになります。

`HttpResponse`以外の`Responder`について一部を以下に記載します。
他の実装については[こちら](https://docs.rs/actix-web/0.6.9/actix_web/trait.Responder.html)を参照ください。

尚、rust 1.26から使用可能なimpl Traitを使用して、`impl Responder`として戻り値を記載することも可能です。

|Type|Note|
|:-:|:-:|
|&lt;T: Responder, E: Into&lt;Error&gt;&gt;<br>Result&lt;T, E&gt;|エラー発生時に特定のステータスコードを返却したい場合等に使用することができます|
|&lt;I: Responder + 'static, E: Into<Error> + 'static&gt;<br>Box&lt;Future&lt;Item = I, Error = E&gt;&gt;|非同期処理を行う場合に使用します<br>又、`FutureResponse<I, E>`というType定義も存在します。|
|&'static str|文字列を返却する際に使用します。<br>Content Type: "text/plain; charset=utf-8"|
|String|文字列を返却する際に使用します。<br>Content Type: "text/plain; charset=utf-8"|
|&'a String|文字列を返却する際に使用します。<br>Content Type: "text/plain; charset=utf-8"|
|&'static [u8]|バイナリを返却する際に使用します。<br>Content Type: "application/octet-stream"|
|Bytes|バイナリを返却する際に使用します。<br>Content Type: "application/octet-stream"|
|BytesMut|バイナリを返却する際に使用します。<br>Content Type: "application/octet-stream"|
|&lt;T: Serialize&gt;<br>Json&lt;T&gt;|JSONを返却する際に使用します。<br>Content Type: "application/json"|
|NamedFile|静的ファイルを返却する際に使用します。<br>Content Typeはファイル拡張子から算出されます。|

#### HttpResponse生成

Http status codeやResponse Header、Content Typeの設定やChunked Streaming Response等、より詳細なレスポンスを行う際は`HttpResponse`を生成して返却します。
`HttpResponse`の生成は主に`HttpResponse`の`OK()`や`Created()`等を使用して`HttpResponseBuilder`を取得し、各種設定を行って最後に`HttpResponse`を生成して返却する形となります。
各種設定メソッドの詳細は[公式ドキュメント](https://docs.rs/actix-web/0.6.9/actix_web/struct.HttpResponse.html)を参照ください。

公式からの引用ですが、サンプルコードはこちらとなります。

```rust
fn index(req: HttpRequest) -> HttpResponse {
    HttpResponse::Ok()
        .content_encoding(ContentEncoding::Br)
        .content_type("plain/text")
        .header("X-Hdr", "sample")
        .body("data")
}
```

#### 非同期処理

actix-webでは非同期処理を扱うことができます。
非同期処理を行う際は、`Handler`の戻り値として`Box<Future<Item = I, Error = E>>`を返却する形になります。

actix-webではworkersで指定された(無指定の場合はCPU数)単位で個別のイベントループが実行されており、処理を全て非同期で記述した場合はnode.jsのシングルスレッドがworkersの数だけマルチスレッドで動作しているようなイメージになります。
実際に内部的に非同期処理となっているかどうかは実装者に委ねられることとなりますので、Futureを返却したとしてもその中でブロックする処理が含まれていれば結局ブロックされてしまうため気を付けて実装する必要があります。

また、現バージョンのactix(及びactix-web)はtokio-coreをベースにして作られていますので、tokio-coreベースの外部crate等を使用することが可能です。
※最新のtokioベースではないため、外部crateを使用する際はtokioベースかtokio-coreベースかを確認して使用ください。

外部crateが非同期に対応していない場合や別スレッドで動作させたいような場合は、アクセス数が少なければ都度threadを生成しても良いですが、RustのthreadはOS native threadでGolang等のGreen threadよりも生成コストが高いため、[tokio-threadpool](https://crates.io/crates/tokio-threadpool)等のスレッドプールを`State`で共有して使用するか、アクターモデルで作成することをお勧めします。

非同期処理の中で`ThreadPool`を使用して同期処理を行うサンプルを以下に記載します。

```rust:main.rs
extern crate actix_web;
extern crate futures;
extern crate tokio_threadpool;

use std::{sync::Arc, time::Duration, thread};
use actix_web::{server, AsyncResponder, App, State, Responder};
use futures::{future::lazy, sync::oneshot::channel};
use tokio_threadpool::{ThreadPool, Builder};

fn index(pool: State<Arc<ThreadPool>>) -> impl Responder {
    println!("Start index.");
    let (tx, rx) = channel();

    pool.spawn(lazy(|| {
        println!("Start async process.");
        thread::sleep(Duration::from_secs(1));
        println!("End async process.");
        tx.send("Hello").unwrap();
        Ok(())
    }));

    rx.responder()
}

fn main() {
    let pool = Arc::new(Builder::new()
        .pool_size(4)
        .build());

    server::new(
        move || App::with_state(pool.clone())
            .resource("/", |r| r.with(index)))
        .bind("127.0.0.1:8080").unwrap()
        .run();
}
```

# 最後に

本当はもう少し実践的な内容を記載しようと思っていたのですが、機能紹介だけで結構な分量となったため、当記事はここまでで一旦終わりとします。
また別の記事にて実践的な内容を記載したいと思います。

RustのWeb Application Frameworkとして、執筆時点で非同期処理を行えるものは現状hyperとactix-web位しか無いように思われます。(他にもあればご教示下さい)
そしてhyperよりも簡潔に記述することができ、処理速度も悪くないためこれからRustでWeb Applicationを作成するのであればactix-webは良い選択だと思います。

尚、hyperが0.12になり、actix-webに近い文法で記述出来るようになったようです。
あまり詳しく見ていませんが、気になる方はチェックしてみてはいかがでしょうか。

そもそもRustでWeb Applicationを作成するという選択はあまり無いかもしれませんが、タイプセーフなモダン言語であり、メモリリークや並列処理におけるバグをコンパイル時に発見することができ、C++並の速度を出せることから、パフォーマンスが重要な場合には悪くない選択だと考えています。

個人的に好きな言語であり流行ってくれたら嬉しいので、当記事が参考になりましたら幸いです。


# 参考

* actix-web document
  * [User Guide](https://actix.rs/docs/)
  * [User Guide (old)](https://actix.rs/book/actix-web/)
  * [API](https://docs.rs/actix-web/0.6.9/actix_web/)
* crates.io
  * [actix](https://crates.io/crates/actix)
  * [actix-web](https://crates.io/crates/actix-web)
  * [failure](https://crates.io/crates/failure)
  * [tokio-threadpool](https://crates.io/crates/tokio-threadpool)
* other
  * [Web Framework Benchmarks](https://www.techempower.com/benchmarks/#section=data-r15&hw=ph&test=fortune)