🌐

DenoでHTTP Webサーバーを作る

2023/03/05に公開

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): Response {
  return new Response(request.body)
}

serve(handler)

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

deno run --allow-net app.ts

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

リクエストからよく使うデータを取得する

リクエストからは次のようなデータを取得できます。他に必要なデータはRequestオブジェクトを参照します。

async function handler(request: Request): Response {
  let method = request.method
  let url = new URL(request.url)
  let pathname = url.pathname
  let query = Object.fromEntries(url.searchParams)
  let data = await request.json()
  let authorization = request.headers.get('authorization')

  return new Response('Hello')
}

Cookieを利用する

Cookieを利用するには、標準ライブラリのCookieMapオブジェクトが便利です。

import { serve } from 'https://deno.land/std@0.178.0/http/server.ts'
import { CookieMap, mergeHeaders } from "https://deno.land/std@0.181.0/http/mod.ts"

function handler(request: Request): Response {
  let cookies = new CookieMap(request)
  cookies.set('name', 'value')
  return new Response('Hello', {
    headers: mergeHeaders(cookies)
  })
}

serve(handler)

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): Response {
  return new Response((connInfo.remoteAddr as Deno.NetAddr).hostname)
}

serve(handler)

ファイルを返す

ファイルの内容をそのまま返す場合は、serveFile()を使います。

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

function handler(request: Request): Response {
  return serveFile(request, 'hello.txt')
}

serve(handler)

リダイレクト

別のURLにリダイレクトする場合はResponse.redirect()を使えます。ブラウザではパスだけで利用できますが、Denoの場合はフルURLが必要です。

function handler(request: Request): Response {
  let url = new URL(request.url)
  return Response.redirect(`${url.protocol}//${url.host}/path`, 302)
}

locationヘッダーを使うこともできます。この場合はパスだけで問題ありません。

function handler(request: Request): Response {
  return new Response(null, {
    status: 302,
    headers: {
      location: `/path`
    }
  })
}

ポート指定

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): Response {
  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): Response {
  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): Response {
  return new Response(request.body)
}
function handler(request: Request): Response {
  return Promise.resolve(new Response(request.body))
}

IPアドレス

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

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

let listener = Deno.listen({ port: 80 })
for await (const conn of listener) {
  (async () => {
    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): Response {
  let file = await Deno.open('hello.txt')
  return new Response(file.readable)
}

Discussion