Deno + oak で任意のレスポンスヘッダでレスポンスを返すサーバーをサクっと立てた

## はじめに

この記事は 某社の Advent Calendar 10日目の記事です。
こんにちは。 [mirko](https://qiita.com/mirko-san_fp) です。


## この記事で書くこと

この記事では、 Deno と oak で任意のレスポンスヘッダでレスポンスを返すサーバーを立てる方法について解説します。
Deno と oak （と CORS 対応のために必要になる cors ライブラリ) のバージョンは以下で確認しています。

| 名前 | バージョン |
|:-:|:-:|
| Deno  | 1.16.0  |
| oak  | 10.0.0  |
| cors  | 1.2.2  |

## なぜ作ったか

ある API エンドポイントを叩いた時に `Content-Disposition` ヘッダに `attachment; filename="filename.txt"` のように入れるのでそれをパースしてフロントエンドで指定した `filename` でファイルダウンロードできるようにしておいてください、となったが、諸般の事情でバックエンドができる前にフロントエンドを着手したかったので。（また、代わりに使えそうなフリーのAPIがうまいこと見つけられなかったので）

## 実装

ts ファイルを書く

```typescript:server.ts
import { Application } from "https://deno.land/x/oak@v10.0.0/mod.ts";
import { oakCors } from 'https://deno.land/x/cors@v1.2.2/mod.ts';

const app = new Application();

app.use(oakCors());
app.use((ctx: any) => {
  ctx.response.body = 'sample response body';
  ctx.response.headers.set('Content-Type', 'text/plain');
  ctx.response.headers.set('Access-Control-Expose-Headers', '*');
  ctx.response.headers.set('Content-Disposition', 'attachment; filename="filename.txt"');
});

await app.listen({ port: 8000 });
```

実行する

```shell
deno run --allow-net server.ts
```

これだけ。

curl を叩くと、以下のようなレスポンスが得られます。

```
$ curl -v http://localhost:8000
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8000 (#0)
> GET / HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200 OK
< access-control-allow-origin: *
< access-control-expose-headers: *
< content-disposition: attachment; filename="filename.txt"
< content-type: text/plain
< content-length: 20
< date: Fri, 03 Dec 2021 15:41:43 GMT
<
* Connection #0 to host localhost left intact
sample response body* Closing connection 0
```

## 解説

### HTTP サーバーを立てる

// 基本的には [oak](https://deno.land/x/oak@v10.0.0) のドキュメントを通りで恐縮です。

以下のように `Application` class をインポートします。

```typescript
import { Application } from "https://deno.land/x/oak@v10.0.0/mod.ts";
```

`Application` class を初期化したら

```typescript
const app = new Application();
```

`.use()` でリクエストを指定しています。

```typescript
app.use((ctx: any) => {
  ctx.response.body = 'sample response body';
  ctx.response.headers.set('Content-Type', 'text/plain');
  ctx.response.headers.set('Access-Control-Expose-Headers', '*');
  ctx.response.headers.set('Content-Disposition', 'attachment; filename="filename.txt"');
});
```

最後に listen するポートを指定します。

```typescript
await app.listen({ port: 8000 });
```


今回は任意のレスポンスヘッダでレスポンスを返したかったので上記のように書いていますが、任意のレスポンスボディを返すのみでしたら、以下のコードだけでも動きます。

```typescript
// https://deno.land/x/oak@v10.0.0 より引用
import { Application } from "https://deno.land/x/oak/mod.ts";

const app = new Application();

app.use((ctx) => {
  ctx.response.body = "Hello World!";
});

await app.listen({ port: 8000 });
```

### CORS 対応をする

実は上記部分だけでも `curl -v http://localhost:8000` で意図したレスポンスを得られますが、ローカル開発環境が `localhost` ではない場合 CORS エラーとなります。
そのため、以下のように CORS 対応をしました。

`cors` モジュールを import します。

```typescript
import { oakCors } from 'https://deno.land/x/cors@v1.2.2/mod.ts';
```

[公式の例](https://github.com/tajpouria/cors/blob/v1.2.2/README.md#simple-usage-enable-all-cors-requests) を参考に、今回は複雑な route も定義していないので、シンプルに `oakCors` を有効化します。

```
app.use(oakCors());
```

これで CORS 対応は完了しました。はやい！


## 書いてみた感想

- めちゃくちゃサクっと書きたいものが書けた
    - 環境構築に時間をかけず気持ちよく TypeScript が書けていい感じ
- 配布ライブラリに型が書いてあるのもサクっと書く助けになってくれた

これからも公私共に Deno を使っていきたいと思いました！