AxumでGoのチュートリアル「Writing Web Application」をやってみた

[フューチャーアドベントカレンダー](https://qiita.com/advent-calendar/2021/future)の22日目の記事です

# はじめに
私はプログラミング初心者ですが、ITエンジニアリングスキルを高めて手に職をつけたい！という気持ちがあります。

そこで、まずは社会で比較的需要の多い（のか？）Webバックエンド開発の基礎を習得したいと思いました。Webバックエンドフレームワークは星の数ほどあれど、それらに共通した要素は多いはずで、どれか一つにある程度習熟してしまえば、新たなフレームワークを学ぶのは容易になるはずです。

と考えると何でもいいような気がしたので、自分の好きな言語であるRustのWebフレームワーク[`Axum`](https://github.com/tokio-rs/axum)を選びました。ただ、`Axum`はかなり新しいフレームワークで、チュートリアルなどが見当たらないので、Goの[`net/http`](https://pkg.go.dev/net/http)のチュートリアル[Writing Web Application](https://golang.org/doc/articles/wiki/)を`Axum`で実装してみました。実装は[以下](https://github.com/hayao0727/GoWikiByRust)にあります。


# Axumとは
`Axum`は非常にスリムなフレームワークです。`Axum`はGitHubの[README](https://github.com/tokio-rs/axum)に書かれているように**Ergonomics**と**Modularity**にフォーカスしています。

## Modularity
`Axum`は色々なクレートがレゴブロックのように組み合わされています。
例えば、非同期ランタイムは[`tokio`](https://github.com/tokio-rs/tokio)、サーバーは[`hyper`](https://github.com/hyperium/hyper)が使われています。
また、`Axum`は`Service`という「Requestを受け取りResponseを返すもの」を抽象化したトレイト（Javaでいうインターフェイスのようなもの）を土台としています。`Service`は[`tower`](https://github.com/tower-rs/tower)で定義されています。
`Axum`は`Service`のルーティングに特化しています。後述するハンドラは`Service`に変換できます。


## Ergonomics
Ergonomicsは人間工学という意味だそうです。使いやすさを重視しているということでしょう。
以下のような特徴があるようです。

### マクロフリーなルーティング
Webフレームワークの中にはアノテーション(マクロ)でルーティングするWebフレームワークも多いですが、`Axum`は現在のところ、その方式は採用していません(Axum v0.4時点)。
マクロは非常に高い表現力ゆえに、何をしているのか分かりづらいという側面があるので、なるべく使用を避けようとしているのでしょう。

### 宣言的なリクエストとレスポンスのパース
`net/http`のハンドラは、要求されたインターフェイスを実装した構造体に対して、操作を施し、リクエストからデータを抽出したりレスポンスを返したりしたりする方式を採用していますが、Axumではハンドラはリクエストは0個以上の[`FromRequest`](https://docs.rs/axum/latest/axum/extract/trait.FromRequest.html)を実装したもの([`Extractor`](https://docs.rs/axum/latest/axum/extract/index.html)と呼ばれます)で、レスポンスは[`IntoResponse`](https://docs.rs/axum/latest/axum/response/trait.IntoResponse.html)を実装したものです。

言葉だと分かりづらいので具体例で比較してみます。

```go:net/httpのハンドラの定義の一例
func handler(w http.ResponseWriter, r *http.Request) { ... }
```

```rust:Axumのハンドラの定義の一例
async fn hander(uri: Uri, method: Method, headers: HeaderMap, body: Bytes) -> (StatusCode, HeaderMap, &'static str) { ... }
```

`net/http`ではリクエストとレスポンスを処理したければ脳死で`http.ResoponseWriter`と`*http.Request`を使いますが、`Axum`の場合、リクエストやレスポンスにおいて具体的に欲しい物、返したい物を引数、返り値に書いていきます。
ハンドラの引数や戻り値で使うためには`FromRequest`や`IntoResponse`が実装されていなければなりませんが、よく使われるものには、`FromRequest`や`IntoResponse`の実装が用意されています。

# Gowiki
**Writing Web Application**はGowikiという超簡易的なWikiを作るチュートリアルです。
簡単のため、Wikiのページはファイルで管理されています。
APIとしては

1. ページの参照
1. 編集(新規作成)
1. 保存機能

が提供されています。


## 同じ機能を実装したAxumとnet/httpのコードを見比べたときの所感
`net/http`はGoが標準で提供しているパッケージで、機能は厳選されています。これを素で使うことは少ないでしょうが、渋川さんの[記事](https://future-architect.github.io/articles/20210714a/)によると[`gin`](https://github.com/gin-gonic/gin)も[`echo`](https://echo.labstack.com/)も`net/http`のラッパーみたいなので、実質かなり広く使われているフレームワークと言っていいでしょう。`Axum`はサービスのルーティングに特化したかなりスリムなフレームワークです。もしかしたら、`net/http`と同じように`Axum`をベースにしたさらにリッチなフレームワークが登場するかもしれません。

### ルーティング
まず、実装を見比べてみます。

```go:go
func main() {
    http.HandleFunc("/view/", makeHandler(viewHandler))
    http.HandleFunc("/edit/", makeHandler(editHandler))
    http.HandleFunc("/save/", makeHandler(saveHandler))

    log.Fatal(http.ListenAndServe(":8080", nil))
}
```
https://golang.org/doc/articles/wiki/ から引用

```rust:rust
#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/view/:title", get(view))
        .route("/edit/:title", get(edit))
        .route("/save/:title", post(save))
        .layer(extractor_middleware::<ValidTitle>());

    axum::Server::bind(&"127.0.0.1:8080".parse().unwrap())
        .serve(app.into_make_service())
        .await
        .unwrap();
}
```

`net/http`でまず気になったのが、ぱっと見ハンドラがルータに紐付けられているように見えないことです。
この[記事](https://zenn.dev/hsaki/books/golang-httpserver-internal)が非常に参考になりましたが、`DefaultServeMux`というグローバル変数が存在していて`handlerFunc`が呼ばれると`DefaultServeMux`に紐付けられるそうです。
そして、`http.ListenAndServe`の第2引数に`nil`が渡されるとサーバーに`DefaultServeMux`が渡されるという作りになっています。
知ってしまえばなんてことはないですが、私は最初にコードを見た時に少し戸惑いました。

`Axum`のコードはスタンダードな作りに見えます。`Axum`は`hyper`の`Server`を再エクスポートしていて`axum::Server::bind(&"127.0.0.1:3000".parse().unwrap()).serve(app.into_make_service()).~~`というのは実際は`hyper`の関数を呼び出しています。
`hyper`の`serve`関数はサービスを引数に要求するのでapp(ルーター)の`into_make_service()`関数を呼び出してサービスに変換しています。

### ミドルウェア
こちらも実装を示します。

```go:go
var validPath = regexp.MustCompile("^/(edit|save|view)/([a-zA-Z0-9]+)$")

func makeHandler(fn func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        m := validPath.FindStringSubmatch(r.URL.Path)
        if m == nil {
            http.NotFound(w, r)
            return
        }
        fn(w, r, m[2])
    }
}
```
https://golang.org/doc/articles/wiki/ から引用

```rust:rust
pub struct ValidTitle;

#[async_trait]
impl<B> FromRequest<B> for ValidTitle
where
    B: Send,
{
    type Rejection = (StatusCode, HeaderMap, String);

    async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
        let re = Regex::new(r"^/[a-zA-Z0-9]+$").unwrap();

        let path = req.uri().path();
        let title = path
            .trim_start_matches('/')
            .trim_start_matches(|c| c != '/');

        if !re.is_match(title) {
            return Err((
                StatusCode::NOT_FOUND,
                HeaderMap::new(),
                "invalid Page Title".to_string(),
            ));
        }

        Ok(Self)
    }
}
/*
    let app = Router::new()
        .route("/view/:title", get(view))
        .route("/edit/:title", get(edit))
        .route("/save/:title", post(save))
        .layer(extractor_middleware::<ValidTitle>()); // 1. ここで使用。
*/
```

チュートリアルではURLのpathのバリデーションをするミドルウェアを実装する章がありました。
`net/http`も`Axum`もハンドラに機能を付加するミドルウェアという仕組みを持っています。`net/http`はミドルウェアは`handlerFunc`を返す関数を引数にとり、`handerFunc`を返すという非常に分かりやすい作りになっています。
サンプルコードを見て気になったのは引数の関数のシグネチャに戻り値が書かれていないことです。これは推論されるということなのでしょうか。。

`Axum`は`tower`のミドルウェアの仕組みをシームレスに使えるようになっています。`tower`におけるミドルウェアとは生成時に`Service`を受け取る`Service`です。ハンドラは`Service`に変換できます。なので、ハンドラをミドルウェアでレイヤリングすることができます。

:::note
「～はServiceです。」は「～はServiceトレイトを実装したなにかです。」という意味です。
:::

もともと存在するミドルウェアを使うのは簡単なのですが、ミドルウェアを自作するためにはRustの非同期に関する知識と`tower`の`Service`に関する知識が必要になります。

例としてTimeoutミドルウェアの実装を示します。

```rust:Timeoutのミドルウェアの実装
use pin_project::pin_project;
use std::time::Duration;
use std::{
    fmt,
    future::Future,
    pin::Pin,
    task::{Context, Poll},
};
use tokio::time::Sleep;
use tower::Service;

#[derive(Debug, Clone)]
struct Timeout<S> {
    inner: S,
    timeout: Duration,
}

impl<S> Timeout<S> {
    fn new(inner: S, timeout: Duration) -> Self {
        Timeout { inner, timeout }
    }
}

impl<S, Request> Service<Request> for Timeout<S>
where
    S: Service<Request>,
    S::Error: Into<BoxError>,
{
    type Response = S::Response;
    type Error = BoxError;
    type Future = ResponseFuture<S::Future>;

    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        self.inner.poll_ready(cx).map_err(Into::into)
    }

    fn call(&mut self, request: Request) -> Self::Future {
        let response_future = self.inner.call(request);
        let sleep = tokio::time::sleep(self.timeout);

        ResponseFuture {
            response_future,
            sleep,
        }
    }
}

#[pin_project]
struct ResponseFuture<F> {
    #[pin]
    response_future: F,
    #[pin]
    sleep: Sleep,
}

impl<F, Response, Error> Future for ResponseFuture<F>
where
    F: Future<Output = Result<Response, Error>>,
    Error: Into<BoxError>,
{
    type Output = Result<Response, BoxError>;

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        let this = self.project();

        match this.response_future.poll(cx) {
            Poll::Ready(result) => {
                let result = result.map_err(Into::into);
                return Poll::Ready(result);
            }
            Poll::Pending => {}
        }

        match this.sleep.poll(cx) {
            Poll::Ready(()) => {
                let error = Box::new(TimeoutError(()));
                return Poll::Ready(Err(error));
            }
            Poll::Pending => {}
        }

        Poll::Pending
    }
}

#[derive(Debug, Default)]
struct TimeoutError(());

impl fmt::Display for TimeoutError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.pad("request timed out")
    }
}

impl std::error::Error for TimeoutError {}

type BoxError = Box<dyn std::error::Error + Send + Sync>;
```
https://github.com/tower-rs/tower/blob/master/guides/building-a-middleware-from-scratch.md から引用

。。。🙄
`#[pin]`、`Future`、`Poll`など、Rustの非同期を勉強したことがないと、意味が分からないと思います。私はRustの非同期を少し勉強したことはありますが（全然理解できてない😭）、それでもミドルウェアをすらすら書ける自信がありません🤪

とはいえ、独自のミドルウェアを実装したいときに、必ずしもこのような実装をしなければいけないわけではなく、`Axum`には`Extractor`をミドルウェアに変換する[`extractor_middleware`](https://docs.rs/axum/0.3.2/axum/extract/extractor_middleware/index.html)という関数が存在します。私の実装ではこれを用いました。常識が全く分かりませんが、汎用性を考えないミドルウェアは`extractor_middleware`を使うのが定石なのかもしれません。

### 正常系以外のレスポンスの返し方
サーバーは常に正常のレスポンスを返すわけではなく、`INTERNAL_SERVER_ERROR`、`BAD_REQUEST`、`UNAUTHORIZED`などのエラー(400、500番台)やリダイレクト(300番台)を返すこともあります。
これの実現方法ですが`net/http`では`http.Error(...)`や`http.Redirect(...)`等を呼ぶだけでした。

Axumの場合は`Result`を使います。`Result`は列挙型です。以下のような定義になっていて、`OK<T>`と`Err<E>`の直和です。`Axum`では、正常なら`Ok<T>`を返し、エラーやリダイレクトなら`Err<T>`を返すように実装します。(と思っていますが、違うかもしれません。違かったらごめんなさい🙇‍♂️)

```rust:Resultの定義
pub enum Result<T, E> {
    Ok(T),
    Err(E),
}
```

ただ、`Axum`でエラーとリダイレクトを実装したときに少し困ったことがありました。
Wikiのページを返すview関数では、ページが見つからなければリダイレクト、ページが見つかればそのページを返します。

Goは以下のような実装でした。

```go
func viewHandler(w http.ResponseWriter, r *http.Request, title string) {
    p, err := loadPage(title)
    if err != nil {
        http.Redirect(w, r, "/edit/"+title, http.StatusFound)
        return
    }
    renderTemplate(w, "view", p)
}
```
https://golang.org/doc/articles/wiki/ から引用

チュートリアルなので、`renderTemplate`（内部で`func (t *Template) Execute(wr io.Writer, data interface{}) error`を呼んでいる)のエラーハンドリングは省かれてましたが、renderTemplateはエラーを発生しうる箇所です。つまり、この関数は正常系、リダイレクト、サーバーエラーの3種類のレスポンスを返す可能性があります。

Rustでは、私は最初以下のように書きました。

```rust
async fn view(Path(title): Path<String>) -> Result<impl IntoResponse, impl IntoResponse> {
    let page = match load_page(&title) {
        Ok(page) => page,
        Err(_) => return Err(Redirect::found(format!("/edit/{}", title))), // 1. Err<Redirect>を返す。
    };

    let mut context = Context::new();
    context.insert("title", &page.title);
    context.insert("body", &String::from_utf8(page.body).unwrap());

    match TEMPLATES.render("view.html", &context) {
        Ok(html) => Ok(Html(html)),
        Err(err) => Err(handle_error(err)), // 2.Err<(StatusCode, HeaderMap, String)>を返す。
    }
}
fn handle_error(err: impl std::error::Error) -> (StatusCode, HeaderMap, String) {
    (
        StatusCode::INTERNAL_SERVER_ERROR,
        HeaderMap::new(),
        format!("Something went wrong: {}", err),
    )
}

```
しかし、これはコンパイルエラーになります。なぜなら1と2で返す型が違うからです。従って、私は以下のように書いてエラーで返す型を一致させました。

```rust
async fn view(Path(title): Path<String>) -> Result<impl IntoResponse, impl IntoResponse> {
    let page = match load_page(&title) {
        Ok(page) => page,
        Err(_) => return Err(handle_redirect(&title)), // 1.Err<(StatusCode, HeaderMap, String)>を返す。
    };

    let mut context = Context::new();
    context.insert("title", &page.title);
    context.insert("body", &String::from_utf8(page.body).unwrap());

    match TEMPLATES.render("view.html", &context) {
        Ok(html) => Ok(Html(html)),
        Err(err) => Err(handle_error(err)), // 2.Err<(StatusCode, HeaderMap, String)>を返す。
    }
}

fn handle_error(err: impl std::error::Error) -> (StatusCode, HeaderMap, String) {
    (
        StatusCode::INTERNAL_SERVER_ERROR,
        HeaderMap::new(),
        format!("Something went wrong: {}", err),
    )
}

fn handle_redirect(title: &str) -> (StatusCode, HeaderMap, String) {
    let mut headers = HeaderMap::new();
    headers.insert(
        axum::http::header::LOCATION,
        HeaderValue::from_str(&format!("/edit/{}", title)).unwrap(),
    );

    (StatusCode::TEMPORARY_REDIRECT, headers, "".to_string())
}
```

しかし、リダイレクトを返す関数があるのに、手動でヘッダーに`Location`をセットするというのも変な感じです。おそらくいいやり方はあるんでしょうが、私には分かりませんでした。

# おわりに
本記事では`Axum`でGoのチュートリアル**Writing Web Application**を実装してみました。私はGoもRustも素人で不正確な情報が多いと思うので事前に謝罪申し上げます🤥

`Axum`はまだ情報が少ないため、「こういうときにどう書くの？」という疑問を解消しづらい面はありましたが、フレームワーク自体はとても使いやすかったです。機能は少ないですが、それは意図的なものです。Goで`net/http`をベースとしたフレームワークがたくさん作られているように、`Axum`をベースとしたフレームワークがこれから作られていったり、関連するクレートがさらに充実していけば、開発体験はより快適になると思います。

ただ、Rustは言語自体の習得難易度が高く、使用者も多いとは言えないため、例えばGoの代わりにRustでWebバックエンド開発をやろうとすると、おそらく開発者集めに苦労すると思います。なので、企業がWebバックエンド開発でそれでもRustを採用するという場面は限られていそうで、ニッチなユースケースで使われるに留まるんだろうなあという気はしています。私は特別な理由が無い限り、Rustでコーディングしたいので悲しいです😢


明日は@RuyPKGさんです！