Node.jsによるWebサーバの作成(基本的な事柄)

JavaScript

Node . js による Webサーバの作成(基本的な事柄)

macOS 10.15.7 / node v20.15.1 / express 4.18.2

概要

Node.jsの expressモジュールを使用して Webサーバを構築する。
本章では次の事柄について説明する。
補足として
Webサーバ(Apache)との互換性
本サイトではサーバに Apacheを使用しているが、全ての Webコンテンツ(HTML、CSS、クライアントJavaScript)は、何ら変更することなく、Node.jsに移行することができる。サーバの利用者から見れば完全に互換性があるといえる。

Webサーバの作成

サーバ処理として最低限必要な記述をする。これだけあれば、基本的な HTTPインタフェース処理を実行することができる。GETコマンドはデフォルの設定で受信できるが、POSTコマンドを受信するには body-parserモジュールを読み込み所定の設定を行う必要がある。

ドキュメントルートの指定について

ドキュメントルートは URLに指定したファイルとサーバ上の実ファイルを結びつける役割を果たす。例えば、サーバに次のようなディレクトリ構成があり、referenceディレクトリの下に HTMLファイル・test1.htmlがあるとする。
次のリクエストを送信すると referenceディレクトリ下の test1.htmlが起動する。referenceディレクトリが URLのルートディレクトリの(ポート番号直下のスラッシュ)に相当すると理解すれば良い。
expressモジュールの static関数でサーバー内のパス名を指定すれば、そこがドキュメントルートはになる。パス名は上述したように相対パス名か、または絶対パス名を指定することができる。相対パスの基準は node.js のサーバプロセスを起動したディレクトリになる。
パスは、シンボリックリンクを指定することもできる。
なお、ドキュメントルートは複数指定することもできる。この場合いずれのディレクトリもドキュメントルートとして有効である。もし両方のドキュメントルートの下に、同じパス名のファイルがあった場合、最初指定したドキュメントルートのものが使われる。

静的ファイルのアクセス方法(CSS、JavaScript、Imageなど)

上述した static関数は、Webアプリケーションで使う静的ファイル(HTML、CSS、JavaScript、画像などのファイル)を格納するディレクトリを指定するための機能である。従って、HTML以外のファイルについても同関数を使って格納ディレクトリを指定することができる。
仮に、次のようなディレクトリ構造を作成し、CSS、JavaScript、画像ファイルは、それぞれの種類ごとに作成したのフォルダに格納するとする。
ドキュメントルートを起点にパス名が解決できる(存在する)ディレクトリ/ファイルは、何らかの制限をかけない限り、全てアクセスできると理解してよい。
CSS、JavaScript、画像ファイルはドキュメントルートを介して通常の方法で参照することができる。
CSS、JavaScript、画像ファイルを参照するには、それぞれのファイルをドキュメントルート(reference)の下に配置するだけでよい。

Webサーバの起動方法

コンソールから nodeコマンドで サーバアプリケーションを起動する。ここではフォラグラウンドで実行されるので、終了するときは Ctrl+C を入力する。
サーバアプリケーションをバックグラウンドで起動する方法である。起動するとプロセスID(580)が表示される。psコマンドでもプロセスIDを確認することができる。
終了するときはプロセスIDを指定して killコマンドを実行する。
[ 注意 ] フォアグランドのプロセスを Ctrl+Cで終了させても、状況によってはプロセスが残ってしまう場合がある。そのときにはプロセスIDを調べて killコマンドで終わらせる必要がある。

Node. js によるサーバーサイド処理

Node.js で Webアプリケーションを構築する大きな利点は、JavaScriptでサーバサイドの処理を記述できることである。クライアントサイドは JavaScriptで実装するケースが一般的だが、クライアントとサーバを同じ言語で開発できるのは大きなメリットである。使い方はほぼ共通なので、非常に効率の良い開発・保守が可能となる。(一方サーバ側を PHPで作成することを考えてみれば良い)
サーバ側の処理は主に、データベースの操作やファイルのI/O処理などが対象になるだろう。
HTMLをベースにした Webアプリケーションで、サーバ側で何らかの処理を行わせたい場合は、選択肢は Ajax方式と CGI方式の二つになるだろう。
現在では Ajax方式が主流だと思うので、本章ではこちらを中心に説明したい。

受信メッセージのルーティング

サーバーサイドの処理は、クライアントから受け取ったリクエストコマンドに応じた処理を実行し、必要であれば結果を編集してレスポンスデータとして返却するという流れになる。
expressモジュールのルーティング機能は、受信した HTPPリクエストのパス名に応じた対応したメソッドを起動するものである。メソッドは、受信する可能性のあるリクエストについて、あらかじ定義しておく。

メソッドの仕様

getコマンド受信用の getメソッド、postコマンド受信用の postメソッドの2種類がある。引数のシグネチャはいずれも同じで、第1引数にパス名、第2引数にリクエストを処理するコールバック関数を定義する。
パス名は、urlのパス名に相当する部分になる。なお、パス名は、サーバーのルート(/)からの絶対パスを指定すること。サーバは、当該ポート番号に送られるリクエストを受信すると、urlのパス名を識別し、対応する getメソッドまたは postメソッドが定義されていたらそれを起動する。この働きを expressのルーティング機能という。
コールバック関数の中にサーバ処理を記述する。第1引数(req)は、受信メッセージをパッケージ化したオブジェクトである。処理に必要なコマンドパラメータはここから取得する。第2引数(res)は、レスポンスデータをパッケージ化したオブジェクトである。
クライアントに返すデータを格納し sendメソッドを実行すると、レスポンスはメッセージを送信したクライアントに返信される。Content-Typeは、データの種類を判別し自動的に設定される。レスポンスデータがプレーンテキストの場合は text/plainに、 JSON形式のテキストの場合は application/json になる。また、オブジェクトを直接指定しても内部で自動的に JSON形式のテキストに変換される。
ヘッダを自前で設定し、レスポンスを返すには、writeHeadメソッド、writeメソッド、endメソッドを使う。writeHeadメソッドの最初の引数にはこの処理のステータスコードを設定することができる。(レスポンスに関しては他にもメソッドがあるのでマニュアル等で確認されたい)

サンプルコード

処理パターン コマンド パラメータ レスポンス
Ajax方式 1 GET URLパラメータ text/plain
Ajax方式 2 POST JSON text/plain
Ajax方式 3 POST URLパラメータ text/plain
Ajax方式 4 GET なし application/json
CGI方式 GET URLパラメータ text/plain

Ajax方式・パターン1

クライアントは1個のパラメータを指定して GETコマンドをサーバに送る。サーバはパラメータの値を2倍にして返す。
HTML
http://localhost:50001/test1.html
fetch関数のデフォルのコマンドは GETである。パラメータは urlにそのまま記述する。Ajax(あるいは、form要素)から Webサーバにリクエストを送信するとき、URLのドメイン名を省略すると暗黙的にその Webページのドメイン名が指定されたとみなされる。
サーバの getメソッド
パラメータはリクエストオブジェクトの queryプロパティから取得する。レスポンスのタイプは text/plain(プレーンテキスト)を指定する。

Ajax方式・パターン2

クライアントは POSTコマンドをサーバに送る。パラメータは2個の値を持つオブジェクトを JSON形式に変換し送る。サーバはパラメータに指定された2個の値を合算し、3倍にして返す。
HTML
http://localhost:50001/test2.html
fetch関数で POSTコマンドを実行するときは、methodに 'POST'、ヘッダの Content-Typeに 'application/json' を指定する。パラメータは bodyプロパティに JSON文字列を格納する。パラメータには、JSON文字列に変換可能なオブジェクトを指定することができる。配列や入れ子になったオブジェクトも可能である。
サーバの postメソッド
POSTコマンドを受け付けるには、専用のモジュール(body-parser)が必要である。設定方法は既述の「Webサーバの作成」を参照のこと。パラメータの値はリクエストオブジェクトの bodyプロパティから取得する。

Ajax方式・パターン3

クライアントは POSTコマンドをサーバに送る。パラメータは2個の値を x-www-form-urlencoded形式で送る 。サーバはパラメータの値を3倍にして返す。
(注)POSTコマンドの場合、パラメータは JSON形式で送るのが一般的であり、この方法を採用する理由はあまりないように思うが、一応紹介しておく。
HTML
http://localhost:50001/test3.html
パラメータは bodyプロパティにそのままの形式で指定する。
サーバの postメソッド
パラメータの値をリクエストオブジェクトの bodyプロパティから取得するのはパターン2と同じ。

Ajax方式・パターン4

レスポンスにJSON形式のデータを受け取る。クライアントは パラメータなしの GETコマンドをサーバに送る。サーバは、本日の日付を取得し JSON形式のデータに編集しクライアントに返す。
HTML
http://localhost:50001/test4.html
レスポンスデータは JSON形式文字列なので、JSON.parseメソッドでオブジェクトに変換する。
サーバの getメソッド
日付を格納したオブジェクトは レスポンスデータに格納するため、JSON.stringifyメソッドで JSON形式文字列に変換する。Content-Typeは 'application/json' を指定する。

CGI方式

ページ1からページ2に遷移する。
ページ1は、テキストフィールドとボタンを持つ。ボタンをクリックするととテキストフィールドの値をパラメータにして GETコマンドをサーバに送信する。ここでページは終了する。
サーバは、ページ2の HTMLを編集し、クライアントに返す。HTMLはテンプレートをファイルから読み込み、そこにパラメータの値を2倍したものを埋め込む。 
HTML・ページ1
http://localhost:50001/test5.html
テンプレートHTML・ページ2
サーバの getメソッド
Content-Typeを 'text/html' にして HTMLコードをそのまま返す。

CORS(Cross-Origin Resource Sharing)について

CORSとは、セキュリティの観点から、異なるオリジンへのリクエストを制限する機能である。オリジンとはプロトコル、ドメイン名、ポート番号の組み合わせである。URLのパス名を除いた部分に相当。
仕組みを理解するために、以下のような方法でテストを行った。
ポート番号の異なるサーバを二つ起動する。
localhost:50001 と localhost:50002 
HTMLファイル(test.html)は、ポート番号 50001 のサーバに格納する。
ブラウザからは、http://localhost:50001/test1.html に対してアクセスする。
test.htmlが開くと、そこから Ajaxによりポート番号 50002 のサーバへ GETリクエストが送られる。しかし、クライアント側でレスポンス受信時に CORSエラーが発生し処理は失敗する。
サーバは、リクエストしたクライアントのオリジン(http://localhost:50001)が自身のオリジン(http://localhost:50002)と異なるからリクエストの受け入れを拒否したのである。
(50002 から見ると 50001 は部外者になる)
ブラウザからのリクエスト
test1.html
サーバの getメソッド

回避策

これを回避するには、サーバの getメソッドに変更を加える。 レスポンスの返信でレスポンスヘッダに Access-Control-Allow-Originプロパティを追加し、受け入れを許容するリクエストのオリジン名を指定する。これにより、そのオリジンからからのリクエストが許可される。(50002 にとって 50001 は仲間になった)
なお値にアスタリスク(*)を指定した場合はあらゆるオリジンからのリクエストを受け入れる。