Servestで始めるDeno HTTPサーバー開発
DenoでHTTPサーバーを書いてみようと思い立ったとき、どのモジュールを使うか考えると思います すでにDenoにはいくつかのHTTPフレームワークが誕生しており、それぞれ違った特色を持っています
この記事ではそれらをかんたんに紹介し、自分が開発しているServestというHTTPフレームワークの入門を書きます Deno入ってる?
え?まだDenoがパソコンに入ってない?
四の五の言わずにこれを実行するのです…
code:bash
$ export PATH=$HOME/.local/bin:$PATH
$ deno -V
まずはコピペから始めよう
Denoのコードは、Denoがインストールされてさえいれば基本的にはコピペするだけで動きます
これまでのプログラミング言語では、フレームワークの入門しようとおもっても環境構築してたら日が暮れてしまったみたいなことがたくさんあったと思います
どんなに便利な機能でも、そういったゼロに至る道筋が困難であるのはやはりよくないと感じます
以下がServestの最もシンプルなコードです
code:server.ts
listenAndServe({port: 8899}, async req => {
await req.respond({
status: 200,
body: "Hello Servest!"
})
})
実行してみましょう
code:bash
$ deno --allow-net server.ts
モジュールのダウンロードとコンパイルが終われば、8899版ポートでサーバーが起動します
curlでリクエストしてみます
code:bash
$ curl localhost:8899
Hello Servest!
レスポンスが返ってくれば成功です!
ルートを定義する
上記のサンプルではlocalhost:8899以下のどのURLにアクセスしても、同じレスポンスが返ってきてしまいます
またGETでもPOSTでも区別されないので、実際のWebサイトやWebサービスを作るのは少し難しいでしょう
そういった一般的な目的のHTTPサーバーを作成するためにservestはRouterというAPIを提供しています
code:router.ts
const router = createRouter()
// GETリクエストのハンドラーを設定
router.get("/", async req => {
await req.respond({
status: 200,
body: "Hello Servest!"
});
});
// POSTリクエストのハンドラーを設定
// bodyをそのままかえすechoサーバー
router.post("/post", async req => {
const body = await req.body?.text();
await req.respond({
status: 200,
headers: req.headers,
body,
})
});
router.listen({port: 8899});
Router APIは、listenAndServeをベースによくあるURLのルートとそれのハンドラを定義することができます
このサーバーはGET /で先ほどと同じようにHello Servest!が返ってきますが、POST /postにリクエストを投げるとbody部分がそのままかえってきます
code:bash
$ curl -X POST localhost:8899/post -d "Deno is nice"
Deno is nice
パラメータ入りのルートを定義する
code:ts
// GET /users/{数値} にマッチするルート
router.get(new RegExp("^/users/(.\\d+?)$"), async req => {
await req.respond({
status: 200,
body: Your id is ${userId},
})
});
REST的な思想でURLを設計した場合、URLの中にリソースのIDとなるパラメータが入っていることも多いでしょう
ExpressやSinatraでは /:idのようなコロン記法のパラメータ定義が使われています
Servestではこれとは異なり、正義表現をルートのマッチャーとして使うようにしました
理由としては、コロン記法のマッチャーに比べて実装することが少なく、テストも書かなくて済むということが挙げられます
Expressが内部的に使用しているマッチャーも、path-to-regexというライブラリを使ってコロン記法を正規表現に変換してマッチ処理をしているので、実際は同じことをしています 加えて正規表現のほうがユーザがより自由にマッチ処理を記述出来るうえ、知識的にも慣れ親しんでいると判断したことが一番の理由です
req.matchには、文字列の.matchの結果が入っているので、通常の正規表現と同じように部分マッチを取り出すことができます。ハンドラーが呼ばれている場合は必ずreq.matchがあるので、安全に配列のデストラクチャが可能です
さらに機能を知る
このサイト自体もServevstで動いており、よく落ちたりしない安定したサイトになっています
おまけ: Deno HTTPモジュール戦国時代
戦国時代はいいすぎですね。鎌倉時代後期ぐらい。
DenoにはServest以外にもHTTPフレームワークの選択肢があります。その中からいくつかご紹介。
std/http
まず、Denoのstd(標準モジュール)にもhttpモジュールがあります
標準だけあって機能は控えめで、最低限のTCPベースのHTTPサーバー機能と、Cookieなどのパーサーが付属しています
特徴としては 何故かfor awaitをめちゃくちゃ推していて、リクエストのハンドラーがfor awaitで書くようになっています 個人的にHTTPサーバーのインターフェイスとして直列処理のfor awaitを使用するのは平行性を落とすために良くないと思っているのですが、色々あってこのままになっています dinatra
deno-jaメンである/deno-ja/syumai.icon作のsinatraライクなフレームワーク
結構人気のようです
koa
denoコントリビュータであるKitson Kellyによるkoaライクなフレームワーク
これも結構人気のようです
abc
denoコントリビュータである木杉 (Mu Shan) によるフレームワーク
資料があまりない
alosaur
TS Decoratorを使ったJavaっぽいフレームワーク
Spring-Bootっぽい(アノテーション? ウッ頭が…)
付記: Servestとはなにか
機能などは前述のとおりですが、大きく3つの特徴を持っています
1. フルスクラッチのHTTPパーサー
ServestはHTTPのIO処理部分で、std/httpに依存していません。Deno.listen/Deno.dialをベースに独自のHTTPパーサーを作成しました
この理由として、自分自身std/httpの設計に不満があり、実際にstd/httpはまだKeep-AliveまわりのHTTP/1.1の機能に準拠していないところがあり、そういった改善や再設計をコアの中でやっていくのがしんどくになったので自分で作り直すことにしました
結果、ServestのHTTP IO APIは自分の納得の行く使いやすいものになったと思っています
2. 最小限のフレームワーク機能
自分はNode.jsのExpressがとてもいいライブラリだと思っているのですが、Expressのように「Unopinionated, fast, minimal」なフレームワーク機能を提供しようと作られています
HTTPサーバーは、サーバーサイドのプログラミングの中で最も求められる機能なので、多くの人の求めるものを提供することは不可能だと思っています
そういうメガ盛りな思想をもとに作られた数々のフレームワークが闇を生んで人々を苦しめているのを見ると、Expressの小回りのよさに設計的な美しさを感じるのです
ServestのAPIとしての設計は、Expressとfetch Standardに影響を受けていて、そのどちらかを使ったことのある人には親しみやすくなっていると思います
3. HTTPを過度に抽象化しない
世の中の多くのHTTPフレームワークは不必要な抽象化をしすぎています
HTTPサーバーの基本は、「リクエストを読み込み、レスポンスを書き込む」ということに付きます
TCPベースのプロトコルはやっていることはファイルIOと同じです
ファイルからリクエストを読み込み、ファイルにレスポンスを書き込んでいるだけです
そこにまじないめいた利便性を持ち込むと、もともとかんたんなはずのHTTPに不要な複雑性を持たせてしまう
RailsやSpring-Bootのような一生ドキュメントを読み続けるようなフレームワークになってしまう
そういうのは数年も経てば陳腐化するムダ知識にすぎないと思います
自分はExpressにふれる事によってHTTPのシンプルな仕組みをなんとなく理解することが出来たし、Servestを作ることによってその詳細な仕様を完全に理解することができました
HTTPサーバーが何をやっているのか? HTTPというプロトコルは何なのか?
そういう知識をServestを使いながら考えられるようになるといいかなと思います