Nodejs HTTP/HTTPS サーバーとクライアントのコーディングパターン

node.js の express フレームワークを利用しないで、HTTP/HTTPSサーバーと対応するクライアントのコーディング・パターンの忘備録です。

## 低レベル HTTPサーバー

nodeで"Hello world"を返すHTTPサーバーのコードです。 次のコードは、リクエストがあった時のイベントハンドラーを登録しておき、このHTTPサーバーにイベントが発生したら無条件に"Hello world"を返します。

~~~lang:node.js
var http = require('http');

// Webサーバーの作成
var server = http.createServer();

// イベントハンドラを登録する
server.on('request',function(req,res) {
    res.writeHead(200,{'Content-Type': 'text/plain'});
    res.write('Hello world\n');
    res.end();
})

// イベントの待機
server.listen(3000);
~~~
ソースコード: [h100_http_server.js](https://github.com/takara9/bluemix-cloudant-coding-patterns/blob/master/h100_http_server.js)


### curlコマンドでの実行結果
GETの場合で、curl コマンドを利用したケースです。

~~~
$ curl -X GET http://192.155.208.116:3000/
Hello world
~~~
GETの場合で、パラメータやフォルダやファイルしてを付与した場合は、サーバー側の処理で受け取る必要があります。

~~~
$ curl -X GET http://192.155.208.116:3000/function?query=10
Hello world
~~~

POSTの場合でも同様です。

~~~
$ curl -X POST http://192.155.208.116:3000/function?query=10
Hello world
~~~

### サーバー側でのリクエストの表示
サーバー側でメソッドとフォルダとパラメータを表示するには、イベントハンドラに登録するコールバックに以下のコードを追加します。

~~~
server.on('request',function(req,res) {
    // リクエストの表示
    console.log("Method = ", req.method);
    console.log("URL = ", req.url);
~~~

### コールバック関数の中身を表示する。
登録済みのコールバック関数を表示するには、リスナーのイベント待ちに入る前に以下のコードを実行すると内容をリストする事ができます。

~~~
x = server.listeners('request');
console.log(x[0].toString());
~~~


## HTTPサーバーでデータを受け取る
次はクライアントから送信されるデータをサーバーで受け取るパターンです。その前にHTTPクライアントのコードを作成します。

## HTTPクライアントの開発 JSONデータを送信

以下がHTTPクライアントから、GETでJSONデータを送信するコードです。

~~~
var http = require("http");
var querystring = require("querystring");
var StringDecoder = require('string_decoder').StringDecoder;
var decoder = new StringDecoder('utf8');

// 送信データ作成
var json_data = {
    message: 'Hello World! こんにちは',
    sound: 123
}
var qs_data = querystring.stringify(json_data);
var options = {
    hostname: '192.155.208.116',
    port: 3000,
    path: '/rest',
    method: 'GET',
    headers: {
	'Content-Type': 'application/json',
	'Content-Length': Buffer.byteLength(qs_data)
    }
};

// リクエスト定義と応答処理設定
var req = http.request(options, function(res) {
    console.log("STATUS: ", res.statusCode);
    console.log("HEADERS: ", JSON.stringify(res.headers));
    res.setEncoding('utf8');

    // 応答受信処理
    res.on('data', function(chunk){
	console.log("BODY: ", chunk);
	// Query String -> JSON形式へ変換
	var rcv_text = querystring.parse(decoder.write(chunk))
        var rcv_json_text = JSON.stringify(rcv_text);
        var rcv_json = JSON.parse(rcv_json_text);
        console.log("json text = ", rcv_json.message);
        console.log("json number = ", rcv_json.sound);
        console.log("json boolean = ", rcv_json.reply);
    });
    // 応答終了処理
    res.on('end', function(){
	console.log('これ以上データはありません。')
    });
});
// 送信のエラー処理
req.on('error', function(e){
  console.log( "エラー発生: ", e.message);
});
// データ送信(GET)
req.write(qs_data);
req.end();
~~~
ソースコード:
[h201_http_get.js](https://github.com/takara9/bluemix-cloudant-coding-patterns/blob/master/h201_http_get.js)

データ送受信に必要なモジュールの読み込み部分です。 HTTPリクエストに付与できる形式に変換するquerystringt と、応答メッセージを受け取る時のデコード文字コードを設定します。

~~~
var querystring = require("querystring");
var StringDecoder = require('string_decoder').StringDecoder;
var decoder = new StringDecoder('utf8');
~~~
以下は、JSON形式でサーバーへ送信するデータ json_data をQuery String 形式へ変換します。そして、optionsに送信先や内容を定義したデータをセットして、リクエストを作成時に利用します。

~~~
var json_data = {
    message: 'Hello World! こんにちは',
    sound: 123
}
var qs_data = querystring.stringify(json_data);
var options = {
    hostname: '192.155.208.116',
    port: 3000,
    path: '/rest',
    method: 'GET',
    headers: {
        'Content-Type': 'application/json',
        'Content-Length': Buffer.byteLength(qs_data)
    }
};
~~~
次のコードは、サーバーへのリクエストの定義と、サーバーからの応答を処理するコールバック関数の定義です。このコードからは実際にリクエストが送信されるものではありません。

~~~
var req = http.request(options, function(res) {
    console.log("STATUS: ", res.statusCode);
    console.log("HEADERS: ", JSON.stringify(res.headers));

    // 応答受信処理                                                                            
    res.on('data', function(chunk){
        console.log("BODY: ", chunk);
        // Query String -> JSON形式へ変換                                                      
        var rcv_text = querystring.parse(decoder.write(chunk))
        var rcv_json_text = JSON.stringify(rcv_text);
        var rcv_json = JSON.parse(rcv_json_text);
        console.log("json text = ", rcv_json.message);
        console.log("json number = ", rcv_json.sound);
        console.log("json boolean = ", rcv_json.reply);
    });
    // 応答終了処理                                                                            
    res.on('end', function(){
        console.log('これ以上データはありません。')
    });
});
~~~
次のコードは、クライアントからサーバーへ送信する際に、エラーが発生した時にコールバックされる関数です。

~~~
req.on('error', function(e){
  console.log( "エラー発生: ", e.message);
});
~~~
例えば、サーバーが停止していた場合、次の様にメッセージが表示されます。

~~~
$ ./h201_http_get.js 
エラー発生:  connect ECONNREFUSED
~~~
先に設定したリクエストを送信するためのコードが次です。 このコードが実行されるとリクエストが送信されます。

~~~
// データ送信(GET)                                                                             
req.write(qs_data);
req.end();
~~~

ソースコード: [h201_http_get.js](https://github.com/takara9/bluemix-cloudant-coding-patterns/blob/master/h201_http_get.js)


## POSTで送信するクライアント
POSTリクエストを送信するには、options の method を POST に変更します。ついでに変数名も変更しておきます。

~~~
var post_data = querystring.stringify(json_data);
var options = {
    hostname: '192.155.208.116',
    port: 3000,
    path: '/upload',
    method: 'POST',
    headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        'Content-Length': Buffer.byteLength(post_data)
    }
};
~~~
コードが読み易くなる様に、同じく変数名も変更しておきます。

~~~
// データ送信(POST)                                                                            
req.write(post_data);
req.end();
~~~

ソースコード: [h200_http_post.js](https://github.com/takara9/bluemix-cloudant-coding-patterns/blob/master/h200_http_post.js)


## HTTPサーバーで送信データを受け取るコードの開発
次は、クライアントが出来たのでサーバー側を開発します。 [低レベルのHTTPサーバーのコード](https://github.com/takara9/bluemix-cloudant-coding-patterns/blob/master/h100_http_server.js)に、受信データをJSON形式に変換するためのモジュールを追加します。

~~~
var querystring = require("querystring");
const StringDecoder = require('string_decoder').StringDecoder;
const decoder = new StringDecoder('utf8');
~~~
さらに、コールバック関数の中で、Query String から JSON 形式の変数に格納するコードを追加します。さらに、受けたデータに情報を付加して返信します。

~~~
server.on('request',function(req,res) {

    // リクエストの表示
    console.log("Method = ", req.method);
    console.log("URL = ",    req.url);

    // POSTデータ受信処理
    req.on('data',function(chunk) {
	console.log("header = ", req.headers);
	// Query String -> JSON形式へ変換
	var rcv_data = querystring.parse(decoder.write(chunk))
	var rcv_text = JSON.stringify(rcv_data);
	var rcv_json = JSON.parse(rcv_text);
        console.log("json text = ", rcv_json.message);
        console.log("json number = ", rcv_json.sound);
        console.log("json boolean = ", rcv_json.reply);

	// 何かの処理
	rcv_json.message = "こんにちは、良い天気ですね。";
	rcv_json.reply = true;

	// 応答送信
	res.writeHead(200,{'Content-Type': 'application/json'});
	var text_data = JSON.stringify(rcv_json);
	var ReplyData = querystring.stringify(rcv_json);
	res.write(ReplyData);
	res.end();
    });

});
~~~

ソースコード: [h102_http_server2.js](https://github.com/takara9/bluemix-cloudant-coding-patterns/blob/master/h102_http_server2.js)

### 実行結果 サーバーサイド GETの場合

~~~
Method =  GET
URL =  /rest
header =  { 'content-type': 'application/json',
  'content-length': '80',
  host: '192.155.208.116:3000',
  connection: 'close' }
json text =  Hello World! こんにちは
json number =  123
json boolean =  undefined
~~~

### 実行結果 サーバーサイド POSTの場合

~~~
Method =  POST
URL =  /upload
header =  { 'content-type': 'application/x-www-form-urlencoded',
  'content-length': '80',
  host: '192.155.208.116:3000',
  connection: 'close' }
json text =  Hello World! こんにちは
json number =  123
json boolean =  undefined
~~~



## 低レベル HTTPSサーバー
次は、[低レベルのHTTPサーバーのコード](https://github.com/takara9/bluemix-cloudant-coding-patterns/blob/master/h100_http_server.js)に加えて、HTTPS サーバーにアップグレードしていきます。

### ドメイン名
HTTPSサーバーを作るには、サーバーのIPアドレスに、DNS名と証明書を取得する必要があります。 無料でドメイン名を取得するには、[MyDNS](http://www.mydns.jp/)が便利です。

### 証明書
無料の証明書を取得するには、[Let's Encrypt](https://letsencrypt.jp/) を利用して取得することができます。 先に作成したドメイン名を使って証明書を作成します。

### コード追加部分
JSON形式で、key と cert を読み込んで、httpsサーバー作成の関数にセットします。 これで、HTTPSサーバーへのアップグレード完了です。

~~~
var fs = require('fs');
var https = require('https');
中略
var options = {
  key:  fs.readFileSync('../lets_encript.key'),
  cert: fs.readFileSync('../lets_encript_fullchain.crt')
};
server = https.createServer(options);
~~~

ソースコード: [h101_https_server.js](https://github.com/takara9/bluemix-cloudant-coding-patterns/blob/master/h101_https_server.js)

### curlコマンドの実行結果
IPアドレスでアクセスした場合、以下の様にエラーが発生します。

~~~
$ curl -X GET https://192.155.208.116:3000/
curl: (51) SSL: certificate subject name 'www.mahotakara.wjg.jp' does not match target host name '192.155.208.116'
~~~
DNS名でGETでアクセスした結果です。

~~~
$ curl -X GET https://www.mahotakara.wjg.jp:3000/
Hello world
~~~
同様にPOSTでアクセスした結果です。
~~~
$ curl -X POST https://www.mahotakara.wjg.jp:3000/
Hello world
~~~


## HTTPSサーバーでデータを受け取る

[低レベルのHTTPSサーバー](https://github.com/takara9/bluemix-cloudant-coding-patterns/blob/master/h101_https_server.js)に、コードを加えます。 加えるポイントは、HTTPサーバーのケースと同じです。

ソースコード: [h103_https_server2.js](https://github.com/takara9/bluemix-cloudant-coding-patterns/blob/master/h103_https_server2.js)

## HTTPSクライアントの開発
追加する内容は、HTTPクライアントと同じです。

GETのソースコードです。 [h203_https_get.js](https://github.com/takara9/bluemix-cloudant-coding-patterns/blob/master/h203_https_get.js) しかし、秘匿性が必要な場合は、GETを利用してはいけません。その理由は、IPAのウェブ記事の「[クエリストリングから情報が漏れる](https://www.ipa.go.jp/security/awareness/vendor/programmingv1/a01_04.html)」を参考にしてください。

POSTのケース
こちらが、本番利用にオススメのPOSTの場合のソースコード [h202_https_post.js](https://github.com/takara9/bluemix-cloudant-coding-patterns/blob/master/h202_https_post.js)です。

### POST実行結果
クライアント側のコンソール・ログ出力

~~~
$ ./h202_https_post.js 
STATUS:  200
HEADERS:  {"content-type":"application/json","date":"Sun, 07 May 2017 07:21:46 GMT","connection":"close","transfer-encoding":"chunked"}
BODY:  message=%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF%E3%80%81%E8%89%AF%E3%81%84%E5%A4%A9%E6%B0%97%E3%81%A7%E3%81%99%E3%81%AD%E3%80%82&sound=123&reply=true
json text =  こんにちは、良い天気ですね。
json number =  123
json boolean =  true
これ以上データはありません。
~~~
サーバー側のコンソール・ログ出力

~~~
Method =  POST
URL =  /upload
header =  { 'content-type': 'application/x-www-form-urlencoded',
  'content-length': '80',
  host: 'www.mahotakara.wjg.jp:3000',
  connection: 'close' }
json text =  Hello World! こんにちは
json number =  123
json boolean =  undefined
~~~

## 参考資料
* [Node.js v4.8.3 Documentation HTTP](https://nodejs.org/dist/latest-v4.x/docs/api/http.html)
* [Node.js v4.8.3 Documentation HTTPS](https://nodejs.org/dist/latest-v4.x/docs/api/https.html)
* [Node.js v4.8.3 Documentation Query String](https://nodejs.org/dist/latest-v4.x/docs/api/querystring.html)
* [MDN JavaScript Object Notation](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/JSON)