Express で レスポンスに "Content-Type" ヘッダを自前セットする方法どれを選べばいいの

# 概要

Express 4 で、HTTPレスポンスヘッダーに "Content-Type" をマニュアルでセットする方法のハンズオン記録です

- "Content-Type" ヘッダをセットする書き方はよく見かけるアプローチだけでも
**res.type,　res.set,　res.header,　res.setHeader,　res.writeHead** がある
- それぞれの書き方、結果について実際にやってみた
- 例として、HTTPレスポンス時 の HTTPヘッダー **Content-Type** が 
`Content-Type: text/plain; charset=utf-8` となるようにコードをかいてみる

## 先に、全体像 → Express で HTTPヘッダをセットする方法一覧

**「"Hello" を "Content-Type: text/plain;" ヘッダつけてレスポンスする」いろんなやり方**

<table>
<tr><td>メソッド</td><td>コード例</td><td>説明</td></tr>
<tr><td rowspan="2"><a href="https://expressjs.com/ja/api.html#res.type">res#type</a></td><td>res.type('text/plain');<br>res.send('Hello');</td><td>"content-type"ヘッダー設定専用メソッド</td></tr>
<tr><td>res.type('.txt');<br>res.send(`Hello`);</td><td>ファイル拡張子を指定すると適切なContent-Typeを設定してくれる</td></tr>
<tr><td rowspan="2"><a href="https://expressjs.com/ja/api.html#res.set">res#set</a></td><td>res.set('content-type', 'text/plain');<br>res.send('Hello');</td><td>ヘッダー名,値 で指定する</td></tr>
<tr><td>res.set({'content-type': 'text/plain',<br>'x-original-header': 'original_value'});<br>res.send('Hello');</td><td>res#setで、ヘッダーを複数指定する書き方</td></tr>
<tr><td rowspan="2"><a href="https://expressjs.com/ja/api.html#res.set">res#header</a></td><td>res.header('Content-Type', 'text/plain');<br>res.send('Hello');</td><td>res#set のエイリアス</td></tr>
<tr><td>res.header({'content-type': 'text/plain',<br>'x-original-header': 'original_value'});<br>res.send('Hello');</td><td>ヘッダーを複数指定する書き方</td></tr>
<tr><td><a href="https://nodejs.org/api/http.html#requestsetheadername-value">res#setHeader</a></td><td>res.setHeader('content-type', 'text/plain');<br>res.send(`Hello`);</td><td>Node.js の 'http' モジュールに所属するメソッド</td></tr>
<tr><td><a href="https://nodejs.org/api/http.html#responsewriteheadstatuscode-statusmessage-headers">res#writeHead</a></td><td>res.writeHead(200, { 'Content-Type': 'text/plain' });<br>res.write('Hello');<br>res.end();// 応答プロセスを終了する</td><td>Node.js の 'http' モジュールに所属するメソッド<br>res.sendとは併用できない</td></tr>
</table>


# 本編
## Expressで簡易サーバーを書く

実験用簡易サーバーを動作させて、HTTPヘッダーがどのように出力されるか curl で確認する



```js:実験用簡易サーバー
import express from 'express';

export default class HttpServer {
  constructor() {
    this.server = null;
  }

  async start(options = {}) {
    const { port } = options;
    const app = express();

    app.get('test', (req, res) => {
      // ここでいろいろ試す
      res.set('content-type', 'text/plain');
      res.send('Hello');
    });

    return new Promise((resolve) => {
      this.server = app.listen(port, () => {
        console.log(`Server started on port:${port}`);
        resolve();
      });
    });
  }

  stop() {
    this.server.close((() => {
      console.log(`Server stopped`);
    }));
  }
}

(async () => {
  const server = new HttpServer();
  await server.start({ port: 8080 });
})()

```

ヘッダー確認用curl

```shell
curl --head http://localhost:8080/test
```

## res.type

- [res#type](https://expressjs.com/ja/api.html#res.type) をつかう。

- res#type は "text/plain" のように"/"が含まれていたら、`Content-Type : text/plain`のように、そのまま反映するが、"/" 含まれていなければ、指定した文字列をファイル拡張子とみなして、MIMEタイプが検索され適切な `Content-Type`が設定される。

**コード**

```js
    app.get('test', (req, res) => {
      res.type('text/plain');
      res.send('Hello');
    });
```

**結果**

```shell
Content-Type: text/plain; charset=utf-8
```

- テキストファイルの拡張子を指定してみる


**コード**

```js
    app.get('/test', (req, res) => {
      res.type('.txt');
      res.send(`Hello`);
    });
```

**結果**

ちゃんと  "text/plain" になっている。

```shell
Content-Type: text/plain; charset=utf-8
```

こうしても結果は同じだった

```js
    app.get('/test', (req, res) => {
      res.type('txt');
      res.send(`Hello`);
    });
```


## res.set()

- [res#set](https://expressjs.com/ja/api.html#res.set) をつかう

**コード**

```js
    app.get('/test', (req, res) => {
      res.set('content-type', 'text/plain');
      res.send('Hello');
    });
```

以下のように書くこともできる。こちらは、複数ヘッダをセットできる

```js
    app.get('/test', (req, res) => {
      res.set({'content-type':'text/plain'});
      res.send('Hello');
    });
```


**結果**

HTTPヘッダーは以下のとおり。charset が自動付与されているが、Expressではデフォルトcharsetは"utf-8"となる。

```shell
Content-Type: text/plain; charset=utf-8
```

実際の応答はこんな感じ。ひとまず Content-Type に着目する。

```shell
curl --head http://localhost:8080/test
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/plain; charset=utf-8
Content-Length: 5
ETag: W/"5-9/+ei3uy4Jtwk1pdeF4MxdnQq/A"
Date: Thu, 24 Feb 2022 05:05:55 GMT
Connection: keep-alive
Keep-Alive: timeout=5
```

**res#setでヘッダを複数指定してみる**

```js
    app.get('/test', (req, res) => {
      res.set(
        {
          'content-type': 'text/plain',
          'x-original-header': 'original_value'
        });
      res.send('Hello');
    });
```

**結果**

ちゃんと複数指定できていた。

```shell
Content-Type: text/plain; charset=utf-8
x-original-header: original_value
```

## res.header 

- res#header は res#set のエイリアス
なので、 res#set と等価（おんなじ）


**コード**

```js
    app.get('/test', (req, res) => {
      res.header('Content-Type', 'text/plain');
      res.send('Hello');
    });
```


**結果**

```shell
Content-Type: text/plain; charset=utf-8
```

- setメソッドと同じく複数指定も可

```js
    app.get('/test', (req, res) => {
      res.header(
        {
          'content-type': 'text/plain',
          'x-original-header': 'original_value'
        });
      res.send('Hello');
    });
```

## res.setHeader

- [res#setHeader](https://nodejs.org/api/http.html#requestsetheadername-value)は Express ではなく Node.js のコアモジュールである 'http' モジュールがもつメソッド

**コード**

```js
    app.get('/test', (req, res) => {
      res.setHeader('content-type', 'text/plain');
      res.setHeader('x-original-header', 'original_value');
      res.send(`Hello`);
    });
```

**結果**

```shell
Content-Type: text/plain; charset=utf-8
x-original-header: original_value
```

## res.writeHead

- [res#writeHead](https://nodejs.org/api/http.html#responsewriteheadstatuscode-statusmessage-headers) は Express ではなく Node.js のコアモジュールである 'http' モジュールがもつメソッド


**コード**

- res.writeHead は res.send と併用できないので注意（理由は後述）


```js
    app.get('/test', (req, res) => {
      res.writeHead(200, { 'Content-Type': 'text/plain' });
      res.write('Hello');
      res.end();// 応答プロセスを終了する
    });
```

**結果(ヘッダーぜんぶのせ)**

```shell
curl --head http://localhost:8080/test
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/plain
Date: Thu, 24 Feb 2022 05:50:41 GMT
Connection: keep-alive
Keep-Alive: timeout=5
```

**res.writeHead**と**res.send**を併用できない理由

うっかり以下のようにしてしまうとエラーとなる

```js
    app.get('/test', (req, res) => {
      res.writeHead(200, { 'Content-Type': 'text/plain' });
      res.send(`hello`);
    });
```

エラー

```
 Cannot set headers after they are sent to the client
```

これは writeHead を実行すると、即座にレスポンスコードとヘッダ書き込みが起こるが、 res.send 内でも、再度 ヘッダ書き込みを行うため。

## おまけ res.send 後に res.end　は不要


```js
    app.get('/test', (req, res) => {
      res.writeHead(200, { 'Content-Type': 'text/plain' });
      res.write('Hello');
      res.end();// 応答プロセスを終了する
    });
```

この場合は res.endで応答のプロセスを終了しているが、 res.send の場合は res.send 内で res.end をしているので、 res.end は不要となる。

## おまけ、その２: Content-Type ヘッダーを送出しない方法

- 以下のように、何も返さないで出力を終える　ということもできる
この場合、 Content-type は付与されない

**コード**

```js
    app.get('/test', (req, res) => {
      res.status(200).end();
    });
```

**結果(ヘッダーぜんぶのせ)**

```
curl --head http://localhost:8080/test
HTTP/1.1 200 OK
X-Powered-By: Express
Date: Thu, 24 Feb 2022 05:59:52 GMT
Connection: keep-alive
Keep-Alive: timeout=5
```

- こうやっても同様に何も返さないで出力をおえる

**コード**

```js
    app.get('/test', (req, res) => {
      res.status(200).send();
    });
```

**結果(ヘッダーぜんぶのせ)**

```
curl --head http://localhost:8080/test
HTTP/1.1 200 OK
X-Powered-By: Express
Date: Thu, 24 Feb 2022 06:03:10 GMT
Connection: keep-alive
Keep-Alive: timeout=5
```

- res.send で空文字を返すと Content-Type は付与される

**コード例**

```js
    app.get('/test', (req, res) => {
      res.status(200).send('');
    });
```

**結果(ヘッダーぜんぶのせ)**

- Content-Length: 0 だが、Content-Type は text/html が付与される。

```
>curl --head http://localhost:8080/test
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 0
ETag: W/"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"
Date: Thu, 24 Feb 2022 06:03:34 GMT
Connection: keep-alive
Keep-Alive: timeout=5
```

# 関連

https://qiita.com/riversun/items/9938531414865cd4c633