クモのようにコツコツと

フロントエンドエンジニア イイダリョウの技術ブログ。略称「クモコツ」

【Node.js】Webサーバを立ててハローワールドを表示する

JS de バックエンド!の続きです。前回、ExpressとJSフレームワーク(React、Vue、Angular)との位置付けについて調べました。今回はNode.jsでWebサーバを立てるというのも体験したく(Expressを事始めようと思ったがその前に!)。それではいきましょう!

【目次】

前回記事
※参考:ExpressとJSフレームワーク(React、Vue、Angularなど)との関係について調べたこと - クモのようにコツコツと

※Node.js / Expressを習得するためにやったことまとめ
qiita.com

これまでのNode.js(ターミナルでの応答)

当ブログで最初に行ったNode.jsの記事はこちら。

※参考:初めてのNode.js:インストール確認、REPL、Hello worldまで - クモのようにコツコツと

この時行ったのはNode.jsを使ったターミナルでの応答。REPLで四則演算したり、ローカルのjsファイルのプログラムを実行をした。実行の結果はWebブラウザではなくターミナルで表示される。

例えばhello.jsというファイルにconsole.log()をかく。

console.log("はろー、のーどじぇいえす!");

ターミナルのcdコマンドでファイルがあるフォルダに移動して

cd フォルダのパス

nodeコマンドでファイルを実行すると…

node hello.js

実行結果がターミナルに表示される!

はろー、のーどじぇいえす!

その後、フロントエンド開発ツールのインストールやコンパイルなどいろんな場面でNode.jsを使ってきたけど、基本的にやってることは同じ。ターミナルからNode.jsに対してコマンドを叩いて、その結果もターミナルに返される(success!とかERR!とか)。

今回やりたいのはWebブラウザに結果(レスポンス)を返したい。すなわちWebサーバを立てる!

JSでWebサーバを立てれる!?

Nodeのハロワ=Webサーバ構築だった!

Expressでハロワ(Hello world)しようとして調べたらこちらの記事でNode.jsとExpressのハロワを比較していた。

※参考:Node.jsのMVCフレームワーク「Express」の基礎知識とインストール (1/3):MEANスタックで始めるWebアプリ開発入門(3) - @IT

ん、ちょっと待てよと。このハロワは前回自分がやったハロワじゃないぞと。これって実行結果がブラウザに返されているじゃないかと。それってばつまり、Webサーバを立てるということじゃないかと。スゴイぞ!WebサーバはJSで立てれるんだ!*1

Webサーバについては前回の記事でも触れたが、HTTPリクエスト(このページおくれ)に対してHTTPレスポンス(ほらよ)と返す仕組みのこと。

※参考:ExpressとJSフレームワーク(React、Vue、Angularなど)との関係について調べたこと - クモのようにコツコツと

この仕組みってApache環境をインストールする必要があるのかと思っていた。

※参考:Apacheとは?Webサーバーの仕組みと人気サーバーソフトを徹底解説 | カゴヤのサーバー研究室

自分はこれまでMAMP環境をインストールして済ませたりしていた。

※参考:ローカル→Sourcetree→GitHub→Herokuに同期する方法(黒い画面不要!) - クモのようにコツコツと

これをJSで自作できるのならば是非やってみたい!Expressをやる前にまずNode.jsで体験してみよう。

Node実行のための準備

先程のNodeとExpressの比較記事のハロワはそれぞれ、Node.js公式サイトとExpress公式サイトのリファレンスと同じような内容だった。

※参考(Node.js):入門ガイド | Node.js
※参考(Epxress):Express の「Hello World」の例

ということで、今回はNode.jsからやってみる。

まず、Node.jsはすでにインストールされている前提(ターミナルで下記のコマンドを打つとバージョン番号が出る)

node -v

Node.jsのインストールについてはこちら
※参考:初めてのNode.js:インストール確認、REPL、Hello worldまで - クモのようにコツコツと

Node.jsを実行するフォルダ、ファイルを作る。node_testというフォルダにnode_test.jsというファイルを作る。

Nodeのコードを書く

node_test.jsにNode.jsのコードを書く。

const http = require('http');

const hostname = '127.0.0.1';
const port = 3000;

const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello Node.js!');
});

server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});
  • 変数httprequire()関数でhttpを読み込み
  • 変数hostnameでホスト名を設定。値は127.0.0.1
  • 変数portでポート番号を設定。値は3000
  • 変数serverhttpcreateServer()関数を実行。引数は無名関数
    無名関数の第一引数はreq、第二引数はres
    resstatusCodeプロパティを200
    ressetHeader()関数を実行。第一引数はContent-Type、第二引数はtext/plain
    resend()関数を実行。値に表示したい文字列を入れる「Hello Node.js!」
  • serverlisten()関数を実行。第一引数はport、第二引数はhostname、第三引数は無名関数(引数はなし) 無名関数の処理はconsole.log()で中はServer running at http://${hostname}:${port}/

さあ、いよいよこれを実行する!

Nodeのコードを実行

ではNode.jsを実行してみる。

まず、cdコマンドでフォルダに移動する。

cd (ファイルのパス)/node_test

ターミナルでnodeコマンドでフィアル名を実行する!

node node_test.js

するとサーバが動いてますよと出た。このURLか。

Server running at http://127.0.0.1:3000/

しかし公式サイト通り、localhostで開いてみる。

http://localhost:3000

おお、文字が表示されとる! f:id:idr_zz:20200221043151j:plain

Chromeのデベロッパーツールで見ると、body要素の中にpre要素として配置されている。 f:id:idr_zz:20200221043217j:plain

pre要素って改行やスペースをそのまま文字列として表示する「整形済みテキスト」。

※参考:<PRE>-HTMLタグリファレンス

コードの中にconsole.log()があったけど、コンソールを見ても何もないようだ。 f:id:idr_zz:20200221043648j:plain

これがWebサーバを立てたってこと、なのかな…?

文字が表示されただけではちょっと何が行われているのかよくわからないですー。

Nodeのコードを読み解く

先程のコードで何が行われているのか詳しく見ていこう。

require()でhttpモジュールを読み込む

最初の変数httpから見ていく。

const http = require('http');

require()関数でhttpモジュールを読み込んでいる。

requireは、モジュール化されたJSファイルをNode.jsから効率よく読み込んで利用できるようにしてくれます。

※参考:【Node.js入門】requireの使い方とモジュールの作り方まとめ! | 侍エンジニア塾ブログ(Samurai Blog) - プログラミング入門者向けサイト

httpモジュールを読み込むことによってHTTPサーバーの機能が実行できるようだ。

「httpモジュール」は、その名の通りHTTPサーバーやHTTPクライアントとしての機能を構築するために使われます。

※参考:【Node.js入門】httpモジュールでサーバー構築、GET・POST通信方法 | 侍エンジニア塾ブログ(Samurai Blog) - プログラミング入門者向けサイト

ホスト名127.0.0.1は自分自身

次の変数hostname

const hostname = '127.0.0.1';

127.0.0.1という数値を読み込んでいる。これは何か。

まず変数名になっているホスト名(hostname)について調べると、URLhttps://www.yahoo.co.jpでいうwwwなどの「サブドメイン」部分を指すという情報もあるが…

※参考:【図解】ドメインとは?をわかりやすく解説します | カゴヤのサーバー研究室

広義では「ネットワーク上でコンピュータを識別するための名前」を意味するようだ。

※参考:https://wa3.i-3-i.info/word1337.html

そもそも「ホスト」とは「サーバの役割をするコンピュータ」を意味する。

※参考:https://wa3.i-3-i.info/word1336.html

で、127.0.0.1という数値はドメインよりもIPアドレスっぽい。IPアドレスはコンピュータに割り当てられる住所のこと。

※参考:https://wa3.i-3-i.info/word172.html

しかも127.0.0.1という数値は特別で「自分自身」という意味を持つようだ。

※参考:https://wa3.i-3-i.info/word17065.html

変数hostnameに自分自身(このPC)のIPアドレスを入れているということか。

ちなみにページを開いたURLhttp://localhost:3000にあるlocalhost127.0.0.1は同じ意味のようだ。

※参考:127.0.0.1とlocalhostと0.0.0.0の違い - Qiita

ポート番号

次に変数port

const port = 3000;

3000という数値を読み込んでいる。これはポート番号だね。では、ポート番号とは何か。

ポートとは、ネットワークでデータを通信するための扉のようなものだと思うとわかりやすいだろう。ポート番号はその扉の番号だ。

ふむふむ。〇〇マンションでいう「何号室」みたいな感じかな。

※参考:【3分で把握】ポート番号とは?と代表的なポート番号まとめ

0-1023の範囲のポート番号は、ウェルノウンポート番号と呼ばれている。

※参考:TCPやUDPにおけるポート番号の一覧 - Wikipedia

「ウェルノウンポート番号」とは名前の通り「よく知られているポート番号」。なんかaka(also known as)みたいでカッコいいw

※参考:【3分で把握】ポート番号とは?と代表的なポート番号まとめ

こちらの記事によると一般的なWebサーバは80が使われるようだ。

Apacheに限らずWebサーバでは一般的にポート番号として80番を使用します。ただ既に同じサーバ上で80番を使用するアプリケーションが動作していた場合は別のポート番号を使用するように設定する必要があります。

既に使われているポート番号と重複しないことが重要で、ローカルのWebサーバは3000の他に8080もよく使われるようだ。

同一サーバ上で別のWebサーバが起動しており80番ポートが既に使われている場合などには別のポート番号(例えば:8080番)を指定します。何でも好きな番号を指定できるわけではなく、同じサーバ上で動作している他のアプリケーション(メールサーバやDNSサーバなど)が使用していない番号を指定しなければなりません。

8080を使うのはWebサーバ80を連想しやすいためと思われる。

※参考:80番以外のポート番号を使用した場合のWebサーバへのアクセス | Apacheインストール | Apache入門

createServer()でWebサーバを構築!

変数serverhttpモジュールのcreateServer()関数を実行している。

const server = http.createServer((req, res) => {
    // 処理
}

createServer()は名前の通りまさにこれがサーバを構築する関数なんだな!

※参考:【Node.js入門】httpモジュールでサーバー構築、GET・POST通信方法 | 侍エンジニア塾ブログ(Samurai Blog) - プログラミング入門者向けサイト

createServer()関数の引数は無名関数。アロー関数で書いている。

アロー関数についてはこちら。

※参考:【JS】アロー関数の複数の書き方を試してみた - クモのようにコツコツと

無名関数には引数は二つあってreqはHTTPリクエスト、resはHTTPレスポンスの略と推察する。

HTTPリクエスト、HTTPレスポンスについてはこちら。

※参考:ExpressとJSフレームワーク(React、Vue、Angularなど)との関係について調べたこと - クモのようにコツコツと

では、createServer()の中の処理を見ていく。

HTTPレスポンスをデベロッパーツールで表示

createServer()の中を見るとどの処理も引数resに対して行われている。

// resオブジェクトのプロパティ処理
res.hoge = fuga

// resオブジェクトのメソッド処理
res.hoge(fuga) 

これはHTTPレスポンスに対する処理ということだ。

こちらの記事にも書いた通り、HTTPレスポンスは下記の3部構成になっている。

  • ステータスライン
  • ヘッダ
  • (空行【CR+LF】)
  • レスポンスボディ

※参考:ExpressとJSフレームワーク(React、Vue、Angularなど)との関係について調べたこと - クモのようにコツコツと

Chromeのデベロッパーツールの「NETWORK」タブでHTTPレスポンスが見れるようだ!

NETWORK画面で、Ctrl+R を押します。これが重要で、HTTPヘッダー情報はHTMLファイルを受信する際にやり取りされるので、再度リクエストを送信しないと情報が表示されません。

※参考:Google Chrome でHTTPヘッダー情報を確認する方法 | スマコマ

ブラウザをリロードしたら、HTTPレスポンスが表示された!

f:id:idr_zz:20200221055437j:plain

ようやく、Webサーバらしさを感じてきたぞ!(ワクワク)

これの結果と照合しながら確認を進めたい。

Nodeのレスポンス用のプロパティ、メソッド

NodeにはHTTPレスポンス用のプロパティやメソッドがあらかじめ用意されているようだ。こちらの記事がわかりやすかった!

レスポンスのオブジェクトには、レスポンスを関連のプロパティやメソッドがあります。
以下のプロパティとメソッドを使うことが多いと思います。

プロパティ/メソッド 内容
statusCodeプロパティ 送信するステータスコード(200や403など)
setHeaderメソッド レスポンスヘッダーを設定する
writeHeadメソッド ステータスコード/レスポンスヘッダーをまとめて送信する
writeメソッド レスポンスを送信する
endメソッド レスポンスの送信を終了する

※参考:Node.jsを学ぶ(その2・Webサーバー作成の基本) - The blog of H.Fujimoto

今回のcreateServer()関数の中の処理も全てこちらの内容となる。

ステータスコード(ステータスライン)

では、処理を順番に見ていこう。

最初処理。

res.statusCode = 200;

resstatusCodeプロパティを200に。

先程のNode.jsレスポンスの表で言うと「送信するステータスコード(200や403など)」という役割になる。

ステータスコードってなんだっけ?MDNによると

HTTP レスポンスステータスコードは、特定の HTTP リクエストが正常に完了したどうかを示します。レスポンスは 5 つのクラスに分類されています。

  1. 情報レスポンス (100–199),
  2. 成功レスポンス (200–299),
  3. リダイレクト (300–399),
  4. クライアントエラー (400–499),
  5. サーバエラー (500–599)

あー404エラーとかでおなじみのやつか!そして200は成功レスポンス(200–299)に含まれるようだ。

200 OK
リクエストが成功したことを示します。

※参考:HTTP レスポンスステータスコード - HTTP | MDN

一番最初の200はそのものズバリな内容。

先程のHTTPレスポンス三部構成の最初が「ステータスライン」

HTTPステータスラインには「HTTPリクエストの結果」が、ざっくりと書かれています。

※参考:https://wa3.i-3-i.info/word1842.html

リクエストに対して「200で成功したよ!」という結果を返す処理ということか。

デベロッパーツールのNETWORKを見ると…
f:id:idr_zz:20200221060323j:plain
確かにStatus Code: 200 OKとある!

レスポンスヘッダ

2つ目の処理

  res.setHeader('Content-Type', 'text/plain');

今度はressetHeader()を実行している。

これも先程の表で言うと「レスポンスヘッダーを設定する」と言う役割だな。

※参考:Node.jsを学ぶ(その2・Webサーバー作成の基本) - The blog of H.Fujimoto

先程のHTTPレスポンス三部構成でいうと2番目になる。

HTTPレスポンスヘッダとは HTTPレスポンスを構成する部品のひとつ であり 「HTTPステータスラインに書ききれないレスポンスの情報」が書かれている場所 です。

※参考:https://wa3.i-3-i.info/word1847.html

setHeader()の第一引数はContent-Type、第二引数はtext/plainになっている。

で、Content-Typeはメディアの種類を示すらしい。

Content-Type エンティティヘッダーは、リソースのメディア種別を示すために使用します。
レスポンスにおいては、 Content-Type ヘッダーはクライアントに返されたコンテンツが実際にはどのような種類のものであるかを伝えます

※参考:Content-Type - HTTP | MDN

こちらに一覧になってまとめられている!!

※参考:Content-Typeの一覧 - Qiita

text/plainテキストファイルを意味する。

デベロッパーツールのNETWORKを見ると…
f:id:idr_zz:20200221062341j:plain
こちらも確かにContent-Type: text/plainとある!

レスポンスボディと送信終了

最後、res.end()メソッド

  res.end('Hello Node.js!');

引数にHello Node.js!という文字列が入っている。これがページに表示されている。

先程のNodeのレスポンスの表で言うと「レスポンスの送信を終了する」と言う役割になる。

※参考:Node.jsを学ぶ(その2・Webサーバー作成の基本) - The blog of H.Fujimoto

え、それだけ?HTTPレスポンス三部構成でいう最後、レスポンスボディの送信はどうなったの?

というかこのend()メソッドの引数に入っているHello Node.js!がちゃんと送信されているんだけど、どゆこと?

本来、この役割はwrite()メソッドが担うはずなんだが、end()はレスポンスボディの送信と送信終了を同時に行ってくれるらしい!

response.write(str), response.end(str)
HTTPレスポンスボディを出力する。
response.write(str) は、HelloWorldアプリケーションには登場しなかったが、HTTPレスポンスボディに str を出力するメソッド。 response.end(str) も同様だが、こちらは同時に EOF を出力する。

※参考:pxt | node.jsに入門してみる。2 ~基本的な機能に触れてみる編~

「EOF」は「End Of File(エンド・オブ・ファイル)」の略でファイルの終わりを意味する。

※参考:https://wa3.i-3-i.info/word12527.html

これによってページにHello Node.js!が送信されたわけだ!

f:id:idr_zz:20200221043151j:plain

サーバを待ち受け状態に(成功したらターミナルにログを出力)

最後、server.listen()メソッド。これは何だろう?

server.listen(port, hostname, () => {
  // 処理
});

serverオブジェクトに対してserver.listen()メソッドを実行している。第一引数はport、第二引数がhostname、第3引数は無名関数。

無名関数の中にはconsole.log()が書いてある。

console.log(`Server running at http://${hostname}:${port}/`);

中でserver.listen()メソッドの引数porthostnameをURLパスとして読み込んでいる。

が、デベロッパーツールのコンソールでは何も起きていなかった。 f:id:idr_zz:20200221043648j:plain

こちらの記事によると、サーバを「待ち受け状態」にするメソッドのようだ。

http.Serverオブジェクトの準備が整ったら、「listen」メソッドを実行します。これにより、サーバーは待ち受け状態となり、クライアントからリクエストがあればそれを受け取り処理するようになります。

引数の順番で入れるものが決まっているようだ。

引数にはポート番号を指定してあります。第2引数としてホスト名を指定したり、第3引数にバックログを指定したり、第4引数にコールバック関数を用意したりすることもできるんですが、とりあえず「第1引数にポート番号」だけ覚えておけば十分です。

console.log()によって待ち受け状態がスタートするとコンソールに書かれる(はず?)

listenで待ち受けスタートした所で、メッセージをコンソールに出力しています。consoleはコンソールを扱うためのオブジェクトで、「log」でログ出力を行うことができます。ちょっとしたデバッグ用にログの出力は覚えておくと重宝するでしょう。

※参考:Node.jsのスクリプトの基本を覚えよう(1/5):初心者のための Node.jsプログラミング入門 - libro

しかし、書かれないんだよなー。待ち受け状態がスタートしていないということかな??

この記事を見たら、謎が解けた!

ウェブブラウザの実行環境では、console.logメソッドの出力先はブラウザの開発者ツールのコンソールでした。 Node.js環境では、console.logメソッドの出力先は標準出力になります。 このコードは、標準出力に"Hello World!"という文字列を出力するものです。

Node.jsのconsole.log()はデベロッパーツールではなくターミナルに表示されるんだ!

※参考:Node.jsでHello World · JavaScript Primer #jsprimer

ということは最初にnodeコマンドを実行した時に…

node node_test.js

すぐ下に現れた処理結果、

Server running at http://127.0.0.1:3000/

これはNode.jsがデフォルト機能として返してくれたのではなく、自分でconsle.log()に書いてたわけだ!

これによって、サーバが「待ち受け状態」になったかどうかデバッグすることができるということか!

最後に

ということで、Node.jsでWebサーバを立てて、ハローワールドを表示しました!そしてコードに書かれた処理も全て確認することができました。JSコードでサーバを立てるって新鮮な体験でした。

Node.js公式サイトでは「ね、できたでしょ」みたいなサラッとした解説なんですが、コードをよく見るとフロントエンドの人間にとってはこの文字がどうやって表示されているか、バックエンドでHTTP通知がどんな動きをしているのか理解できて、とても勉強になりました! 公式サイトだけでは処理の動きがイメージしづらかった方の助けになると嬉しいです。

次はこのコードを色々打ち替えてみたいと思います。

  • ポート番号を変えてみたい
  • 日本語表示にしたい(文字コードの変更が必要ぽい)
  • プルーンテキストではなくHTMLページにしたい

などなど。それではまた!


※Node.js / Expressを習得するためにやったことまとめ
qiita.com

*1:パズー