NodeSchoolのlearnyounodeを解いてみる #2

[#1](http://qiita.com/naoyamuto/items/40dda3983f5aec28801b)の続きになります。

![image](https://qiita-image-store.s3.amazonaws.com/0/37467/48d4c1f7-0902-aa41-f379-5362cc271563.png)

# HTTP CLIENT
- コマンドライン引数で指定したURLにGETリクエストする
- レスポンスは`data`イベントで受け取り、受け取った文字列を1つずつ改行して出力
- `http.get()`関数を用いる。

```program.js
var http = require("http");
var url = process.argv[2];

http.get(url, function(res) {
  res.setEncoding("utf8");

  res.on("data", function(data) {
    console.log(data);
  });
});
```

# HTTP COLLECT
- コマンドライン引数にURLを入れて、HTTPのデータをロードする
- 1行目は文字数・2行目はデータを文字列で出力
- `bl`モジュールを使うことでデータを`pipe`することができる
  - `pipe`：Stream中に流れてくるデータを次々と橋渡しする関数を登録すること
    - Unixの`|`でデータを次に渡して処理する概念を踏襲している。 (例: `$ cat hoge.txt | grep HOGE` )

```program.js
var http = require("http");
var bl = require("bl");
var url = process.argv[2];

http.get(url, function(res) {

  res.pipe(bl(function(err, data) {
    if(err) return console.error(err);
      console.log(data.length);
      console.log(data.toString());
  }));
});

```

# JUGGLING ASYNC
- コマンドライン引数で３つのURLを指定してGETリクエストを行い、その結果をコンソールに出力する
- どの順番でデータが提供されるのか決まっていない（レスポンスの順番はリクエストの順番と異なる為）ため、そのまま出力すると順番がばらばらになる
  - 今回では、出力する文字列はコマンドライン引数の順番と一致させることを厳守しなければならない
- 今回は`map`メソッドを使わずに組んだが、mapメソッドでもできそう。
  - `Array.map(callbackfn)`：定義されたコールバック関数を配列の各要素に対して呼び出し、結果を含む配列を返す

```program.js

var http = require("http");
var urls = [process.argv[2], process.argv[3], process.argv[4]];
var bl = require("bl");
var results = [];
var count = 0;

//resultsの配列に格納した値を出力する関数
function printResults() {
  for (var i = 0; i < results.length; i++) {
    console.log(results[i]);
  }
};

//http.get()する関数
function httpGet(urls, index) {
  http.get(urls[index], function(res) {

    res.pipe(bl(function(err, data) {
      if(err) return console.error(err);
        results[index] = data.toString();
        count = count + 1;
        //全部のgetリクエストをコンソールに出力
        if (count == urls.length) {
          printResults();
        }
    }));
  });
};

//URLの数だけGETリクエストを送信
for(var i = 0; i < urls.length; i++) {
  httpGet(urls, i);
}

```

# TIME SERVER
- TCPのタイムサーバーを書く
    - TCPとはインターネットなどのネットワークで、IP（Internet Protocol）の一段階上位層のプロトコル（通信規約）として標準的に使われるものの一つ。
    - httpサーバーよりも上位層にあるものらしい。
- サーバーを起動し、コネクションに今の日付と時間を`YYYY-MM-DD hh:mm`の形式で返すようにする
  - その際、月、日、時間や分は２桁にゼロパディング(足りない桁を0で埋めること)する
- http専用のTCPは使っていないので`net`モジュールの`net.createServer()`関数を用いる。
- 今回の問題はデータを送信して接続を閉じるだけなので、`socket.end()`で接続を閉じ、引数に出力する値を設定する。
- ゼロパディングするためのコードがいけてない・・

```program.js

var net = require('net');
var portNum = Number(process.argv[2]);

//日時を取得してゼロパディングする関数
function t() {
  var d = new Date();
  var month = "0" + (d.getMonth() + 1);
  var date = "0" + d.getDate();
  var hour = "0" + d.getHours();
  var minutes = "0" + d.getMinutes();

  var month = month.slice(-2);
  var date = date.slice(-2);
  var hour = hour.slice(-2);
  var minutes = minutes.slice(-2);

  return d.getFullYear() + '-'
    + month + '-'
    + date + ' '
    + hour + ':'
    + minutes
};

console.log("starting server at port " + process.argv[2])

//TCPサーバー起動
var server = net.createServer(function(socket) {
  socket.end(t() + '\n');
});

server.listen(portNum);
```

# HTTP FILE SERVER
- 常に同じテキストファイルを返す、HTTPサーバーを書く
- `http.createServer()`関数を用いる。以下は関数の説明。

```
http.createServer()の第1引数であるリスナー関数は、コネクションの度に毎回呼ばれます。

一般的なHTTPリクエストリスナー関数の例：

     function listener (request, response) { /* ... */ }

上記の例の2つの引数はHTTPのリスナーにおける代表的なものです。第1引数のrequestにはHTTPのプロパティが入ります。例：ヘッダーやクエリ文字列。
第2引数のresponseは、クライアントにヘッダーやボディを返すためのオブジェクトです。またrequestやresponseはNode.js におけるStreamです！Streamなので、他のStream由来のシステムと送受信するのにぴったりです。

http.createServer()はサーバのオブジェクトを返します。指定したポートで起動するためには、サーバオブジェクトの server.listen(portNumber) を呼んでください。
```

- 1つ目のコマンドライン引数で供給されているポートでサーバを起動し、2つ目の引数でテキストファイルのパスを指定する
  - ファイルを返す為には`fs.createReadStream()`を使う
    - 第一引数にファイルパスを渡すとそのファイルを表す`Stream`オブジェクトが返ってくる
      - 返ってきた`Stream`を`pipe()`で繋げることができる、今回はHttpのレスポンスStreamにつなげる
    - 第二引数にオプションを取れ、文字コード等を指定できる


```program.js
var http = require("http");
var fs = require("fs");
var portNum = Number(process.argv[2]);
var filePath = process.argv[3];

var server = http.createServer(function(req, res) {

  res.writeHead(200, { 'content-type': 'text/plain' })

  var file = fs.createReadStream(filePath, {encoding: 'utf8'});
  file.pipe(res);

});

server.listen(portNum);
console.log('app listening on port: ', portNum);
```

# HTTP UPPERCASERER
- POSTリクエストで渡される文字列を大文字に書き換えて返すHTTPサーバを書く。
- サーバーは一つ目のコマンドライン引数で与えられたポートで起動させる
- Streamで流れているデータを途中で変えるためには、`through2-map`というパッケージを用いる。
  - `through2-map`が提供する関数を使うことで、書き換えた新しい`Stream`を作ることができる
    - 上記の例では、`inStream`のデータを使って文字列を逆順に並び替えて`outStream`に書き出している

      ```sample.js
      var map = require('through2-map')
           inStream.pipe(map(function (chunk) {
             return chunk.toString().split('').reverse().join('')
           })).pipe(outStream)
      ```
- 上記をパクって書いてみる
- `pipe()`で繋ぐ場合、`response.end()`メソッドはいらないのか？
  - [ここ](http://kjunichi.cocolog-nifty.com/misc/2014/11/code-revisions-.html)が参考に
    - pipeにはオプションがあり、デフォルトだと読み込み完了したら書き込み終了するので読み込み用ストリームの終了は意識しなくてOK、らしい。

```program.js
var http = require("http");
var map = require('through2-map');
var portNum = process.argv[2];

var server = http.createServer(function(req, res) {
    res.writeHead(200, { 'Content-type': 'text/plain' });

    if(req.method != "POST") {
      return res.end("Request is not POST....\n");
    }

    req.pipe(map(function(chunk) {
      return chunk.toString().toUpperCase();
    })).pipe(res);
});

server.listen(portNum);
console.log('app listening on port: ', portNum);


```


# HTTP JSON API SERVER
- `'/api/parsetime'`、`'/api/unixtime'`へのGETリクエスト時にJSONデータを返すHTTPサーバを書く
    - その際、リクエストクエリには`iso`というISO形式のタイムスタンプパラメータを渡す
- `url.parse()`で以下のようにurlオブジェクトが返却される
    - 第二引数に`true`を指定するとクエリパラメータの解析を行ってくれる
- 結果をJSON文字列形式に変換する`JSON.stringify()`を忘れていて、ブラウザに結果が出てこなくて1時間ほどつまずいた。。

```
Url {
  protocol: null,
  slashes: null,
  auth: null,
  host: null,
  port: null,
  hostname: null,
  hash: null,
  search: '?iso=2013-08-10T12:10:15.474Z',
  query: { iso: '2013-08-10T12:10:15.474Z' },
  pathname: '/api/parsetime',
  path: '/api/parsetime?iso=2013-08-10T12:10:15.474Z',
  href: '/api/parsetime?iso=2013-08-10T12:10:15.474Z' }
```

```program.js
var http = require("http");
var url = require("url");
var portNum = process.argv[2];

//parseしたクエリパラメータをjsonで返却する
function parsetime(time) {
  return {
    "hour": time.getHours(),
    "minute": time.getMinutes(),
    "second": time.getSeconds()
  };
}

function unixtime(time) {
  return { "unixtime": time.getTime() };
}

//サーバー起動
var server = http.createServer(function(req, res) {

  if(req.method == "GET") {
    res.writeHead(200, { 'Content-type': 'application/json' });
    var parseUrl = url.parse(req.url, true);
    var time = new Date(parseUrl.query['iso']);
    var result;
    if(parseUrl.pathname == '/api/parsetime') {
      result = parsetime(time);
      console.log(result);
    } else if(parseUrl.pathname == '/api/unixtime') {
      result = unixtime(time);
      console.log(result);
    }
    //結果をJSON文字列の形式に変更
    res.write(JSON.stringify(result)+ '');
    res.end();
  }
});

//サーバ待ち受け
server.listen(portNum);
console.log('app listening on port: ', portNum);

```