🌐

DenoでHTTP Webサーバーを作る

2023/03/05に公開約3,800字

Fetch API

DenoはFetch APIのインターフェースを使ってHTTP Webサーバーを作ることができます。

Fetch APIとは、JavaScriptでブラウザからサーバーにリクエストを投げるときに使うこんなやつです。

let response: Response = await fetch(new Request('http://localhost:8000'))

Requestオブジェクトを投げるとResponseオブジェクトが返ってきます。
(普通、Requestオブジェクトは省略しますが、あえて明記しています。)

DenoでHTTPサーバーを作るときは逆にRequestオブジェクトを受け取ってResponseオブジェクトを返すだけです。

Fetch APIを使ったHTTP通信

std/httpライブラリ

serve()関数

serve()関数を使うと、HTTPサーバーを簡単に実現できます。

例えばリクエストボディをオウム返しするHTTPサーバーはこれだけでできてしまいます(@の後ろは最新バージョンを使ってください)。

app.ts
import { serve } from 'https://deno.land/std@0.178.0/http/server.ts'

function handler(request: Request) {
  return new Response(request.body)
}

serve(handler)

serve()関数に対して、Requestオブジェクトを受け取ってResponseオブジェクトを返す関数を渡しています。
あとは、--allow-netオプションを付けて実行するだけです。

deno run --allow-net app.ts

RequestオブジェクトからリクエストメソッドやURL等を取得できますので、後はswitch文でもルーティングライブラリでも使って分岐して、リクエストに応じたレスポンスを返すようにすればOKです。

IPアドレス

RequestオブジェクトにはIPアドレスの情報がありませんので、Webサーバー側でリクエストしてきた相手のIPアドレスを確認したいときは、第二引数のConnInfoを使います。

app.ts
import { serve, ConnInfo } from 'https://deno.land/std@0.178.0/http/server.ts'

function handler(request: Request, connInfo: ConnInfo) {
  return new Response(connInfo.remoteAddr.hostname)
}

serve(handler)

ポート指定

serve()関数は第二引数にオプションを受け取ることができます。もし受信するポート番号を変更する場合はportオプションを指定します。

serve(handler, {
 port: 3000,
})

httpsサーバー

httpsでサーバーを立てる場合はserveTls()関数を使います。

import { serveTls } from 'https://deno.land/std@0.178.0/http/server.ts'

function handler(request: Request) {
  return new Response(request.body)
}

serveTls(handler, {
 certFile: '証明書ファイルパス',
 keyFile: '秘密鍵ファイルパス',
})

標準API

標準APIでHTTPサーバーを作る場合は、Deno.listen()Deno.serveHttp()を組み合わせて使います。
ひと工夫してRequestオブジェクトを受け取ってResponseオブジェクトを返す関数を作るだけにしてしまうと楽です。

例えばリクエストボディをオウム返しするHTTPサーバーは次のように作ります。

app.ts
function handler(request: Request) {
  return new Response(request.body)
}

let listener = Deno.listen({ port: 80 })
for await (const conn of listener) {
  for await (const { request, respondWith } of Deno.serveHttp(conn)) {
    respondWith(handler(request))
  }
}

実行するときは、--allow-netオプションを付けます。

deno run --allow-net app.ts

handler()内でRequestオブジェクトからリクエストメソッドやURL等を取得できますので、後はswitch文でもルーティングライブラリでも使って分岐して、リクエストに応じたレスポンスを返すようにすればOKです。

非同期処理

respondWith()PromiseLike<Response>を受け取るので、handler()は非同期関数にしても問題ありません。

async function handler(request: Request) {
  return new Response(request.body)
}
function handler(request: Request) {
  return Promise.resolve(new Response(request.body))
}

IPアドレス

リクエストしてきた相手のIPアドレスの情報はconnに入っていますので、必要な場合はconnhandler()に渡します。

app.ts
function handler(request: Request, conn: Deno.Conn) {
  return new Response(conn.remoteAddr.hostname)
}

let listener = Deno.listen({ port: 80 })
for await (const conn of listener) {
  for await (const { request, respondWith } of Deno.serveHttp(conn)) {
    respondWith(handler(request, conn))
  }
}

httpsサーバー

httpsでサーバーを立てる場合はDeno.listenTls()関数を使います。

let listener = Deno.listenTls({
  port: 80,
  certFile: '証明書ファイルパス',
  keyFile: '秘密鍵ファイルパス',
})

ファイルを返す

ファイルのReadableStreamをResponseオブジェクトに渡してやればファイルの中身を返すことができます。

async function handler(request: Request) {
  let file = await Deno.open('hello.txt')
  return new Response(file.readable)
}

Discussion

ログインするとコメントできます