Node.jsを学んで、大いに活用しよう!
数日間の余暇時間を使って、この
Node.js
入門課題を終え、知識点を整理しました。
HELLO WORLD
プログラムを作成し、ターミナル(標準出力 stdout)に「HELLO WORLD」と出力してください。
言語を学ぶ最良の方法は、まずHello Worldプログラムを書くことです。この問題は非常に簡単で、誰もが理解できるでしょう。
console.log("HELLO WORLD")
BABY STEPS
シンプルなプログラムを作成し、1つまたは複数のコマンドライン引数を受け取り、それらの引数の合計をターミナル(標準出力 stdout)に出力してください。
受け取った引数は process.argv
を介してアクセスできます。これは配列です。最初の2つの要素は固定されており、1つ目は node
、2つ目はファイルのパス、それ以降がプログラムへの入力内容となります。
まず、以下の簡単なコードを含むプログラムを作成して、慣れてみてください。
console.log(process.argv)
node program.js
というコマンドを実行し、その後にいくつかの引数を追加してプログラムを実行してください。例えば:
$ node program.js 1 2 3
そうすると、以下のような配列が得られます:
[ 'node', '/path/to/your/program.js', '1', '2', '3' ]
これらの知識があれば、プログラムを書くのは非常に簡単です。
var result = 0;
process.argv.forEach(function(el,index){
if (index>1) {
result += +el;
}
});
console.log(result);
MY FIRST I/O!
プログラムを作成し、同期ファイルシステム操作を実行してファイルを読み込み、そのファイルの内容の行数をターミナル(標準出力 stdout)に出力してください。
cat file | wc -l
コマンドの実行に似ています。
読み込むファイルの完全なパスは、コマンドラインの最初の引数として提供されます。
まずファイルを操作するには、fs
モジュールを使用する必要があります。var fs = require('fs');
を使ってインポートできます。その後、fs
変数を通じて fs
モジュール全体にアクセスできるようになります。
fs
モジュールでは、すべての同期(またはブロッキング)ファイルシステム操作メソッドの名前は ‘Sync’ で終わります。ファイルを読み込むには、fs.readFileSync('/path/to/file')
メソッドを使用する必要があります。このメソッドは、ファイルの内容全体を含むBuffer
オブジェクトを返します。Buffer
オブジェクトは、NodeがASCII、バイナリ、その他の形式を問わず、データを効率的に処理する方法です。Buffer
は、toString()
メソッドを呼び出すことで簡単に文字列に変換できます。例:var str = buf.toString()
。
したがって、まず fs
モジュールをインポートし、入力データを読み込みます。私たちの目的は行数を数えることですが、各行の末尾には実際には \n
があることがわかります。\n
の数を数えるだけで十分です。
var fs = require('fs');
var data = fs.readFileSync(process.argv[2]);
var str = data.toString();
var array = str.split('\n');
console.log(array.length-1);
MY FIRST ASYNC I/O!
プログラムを作成し、非同期ファイルシステム操作を実行してください。ファイルを読み込み、そのファイルの内容の行数をターミナル(標準出力 stdout)に出力してください。
cat file | wc -l
コマンドの実行に似ています。
読み込むファイルの完全なパスは、コマンドラインの最初の引数として提供されます。
この問題は前の問題とほぼ同じですが、非同期読み込みに変わっています。そのため、fs.readFileSync()
ではなく fs.readFile()
を使用する必要があります。
覚えておいてください、Node.jsのコールバック関数は通常、以下のような特徴を持っています:
function callback (err, data) { /* ... */ }
したがって、最初の引数の真偽値をチェックすることでエラーが発生したかどうかを判断できます。エラーが発生しなかった場合、2番目の引数で
Buffer
オブジェクトを受け取ります。readFileSync()
と同様に、2番目の引数に'utf8'
を渡し、3番目の引数にコールバック関数を渡すことで、Buffer
ではなく文字列を受け取ることができます。
var fs = require('fs');
var data = fs.readFile(process.argv[2],function(err,data){
if(err){
throw console.error(err);
}
var list = data.toString().split('\n');
console.log(list.length-1);
});
FILTERED LS
指定されたディレクトリ内のファイルリストを出力し、特定のファイル拡張子でこのリストをフィルタリングするプログラムを作成してください。今回は2つの引数が提供されます。1つ目は指定されたディレクトリのパス(例:
path/to/dir
)、2つ目はフィルタリングするファイルの拡張子です。
例として、2番目の引数が
txt
の場合、.txt
拡張子を持つファイルをフィルタリングする必要があります。
注意:2番目の引数には先頭の
.
は含まれません。
フィルタリングされたリストをターミナルに1行につき1ファイルで出力する必要があります。また、非同期I/O操作を使用しなければなりません。
これも非常に簡単です。ファイル名を .
で2つの部分に分割し、後ろの部分が拡張子になります。
var fs = require('fs');
var path = process.argv[2];
var extname = process.argv[3];
fs.readdir(path,function(err,list){
if(err){
throw err;
}else{
list.forEach(function(file){
if(file.split('.')[1] === extname){
console.log(file);
}
});
}
});
MAKE IT MODULAR
この問題は前の問題と同じですが、今回はモジュールの概念が導入されます。この問題を解決するには2つのファイルを作成する必要があります。
指定されたディレクトリ内のファイルリストを出力し、特定のファイル拡張子でこのリストをフィルタリングするプログラムを作成してください。今回は2つの引数が提供されます。1つ目はリスト化するディレクトリ、2つ目はフィルタリングするファイル拡張子です。フィルタリングされたファイルリストをターミナルに(1行につき1ファイルで)出力する必要があります。さらに、非同期I/Oを使用しなければなりません。
ほとんどの処理を行うモジュールファイルを作成する必要があります。このモジュールは、ディレクトリ名、ファイル拡張子、コールバック関数の3つの引数をこの順序で受け取る関数をエクスポート(export)しなければなりません。ファイル拡張子は、プログラムに渡される拡張子文字列とまったく同じでなければなりません。つまり、正規表現に変換したり、
"."
プレフィックスを追加したり、その他の処理をしたりせず、そのままモジュールに渡し、モジュール内でフィルターが正しく機能するように処理を行うことができます。
このコールバック関数は、Nodeプログラミングで慣例的に使用される形式(
err, data
)で呼び出されなければなりません。この慣例は、エラーが発生しない限り、コールバック関数に渡される最初の引数はnull
であり、2番目の引数がデータであることを示しています。この問題では、このデータはフィルタリングされたファイルリストであり、配列形式になります。fs.readdir()
からのエラーなど、エラーを受け取った場合は、そのエラーを最初の唯一の引数としてコールバック関数に渡し、コールバック関数を実行する必要があります。
モジュールファイル内で直接結果をターミナルに出力してはなりません。結果を出力するコードは、元のプログラムファイルにのみ記述できます。
プログラムがエラーを受け取った場合は、単純にそれらをキャッチし、関連情報をターミナルに出力してください。
あなたのモジュールが遵守すべき4つのルールがあります:
- 上記の引数を正確に受け取ることができる関数をエクスポートすること。
- エラーが発生した場合、またはデータがある場合に、コールバック関数を正確に呼び出すこと。
- グローバル変数やstdoutなど、他のいかなるものも変更しないこと。
- 発生しうるすべてのエラーを処理し、それらをコールバック関数に渡すこと。
いくつかの慣例に従うことの利点は、あなたのモジュールがこれらの慣例に従う他の誰にでも使用できることです。したがって、ここで作成する新しいモジュールは、他のlearnyounodeの学習者によって使用されたり、検証されたりしても、うまく機能するでしょう。
まず、モジュールファイルですが、前の問題から改造し、program-6-require.js
と命名できます。その役割は、フィルタリングされたファイル名の配列を返すことです。
var fs = require('fs');
module.exports = function(path, extname, callback) {
fs.readdir(path, function(err, list) {
if (err) {
return callback(err, null);
} else {
list = list.filter(function(file) {
return file.split('.')[1] === extname;
});
}
callback(null, list);
});
};
次に、プログラムファイルでこのモジュールを呼び出し、モジュールから返された配列の内容を出力するだけで済みます。
var mymodule = require('./program-6-require');
var path = process.argv[2];
var extname = process.argv[3];
mymodule(path,extname,function(err,files){
if(err){
return console.log(err);
}
files.forEach(function(file){
console.log(file);
});
});
HTTP CLIENT
HTTP GETリクエストを発行するプログラムを作成してください。リクエストするURLはコマンドライン引数の最初のものです。そして、各「data」イベントで得られたデータを文字列形式でターミナル(標準出力 stdout)に新しい行で出力してください。
この演習を完了するには、Node.jsのコアモジュールの1つである
http
を使用する必要があります。
http.get()
メソッドは、シンプルなGETリクエストを発行するためのショートカットであり、このメソッドを使用することでプログラムをある程度簡素化できます。http.get()
の最初の引数はGETしたいURL、2番目の引数はコールバック関数です。
他のコールバック関数とは異なり、このコールバック関数は以下の特徴を持っています:
function callback (response) { /* ... */ }
response
オブジェクトはNodeのStream
型のオブジェクトであり、Node Streamをいくつかのイベントを発生させるオブジェクトとして考えることができます。通常、私たちが関心を持つイベントは「data
」、「error
」、そして「end
」の3つです。イベントは次のようにリッスンできます:
response.on("data", function (data) { /* ... */ })
「
data
」イベントは、各データチャンクが到着し、処理可能になったときにトリガーされます。データチャンクのサイズはデータソースによって異なります。
http.get()
から得られるresponse
オブジェクト/Streamには、setEncoding()
メソッドもあります。このメソッドを呼び出し、引数にutf8
を指定すると、「data
」イベントでは標準のNodeBuffer
オブジェクトではなく文字列が渡されるため、Buffer
オブジェクトを手動で文字列に変換する必要がなくなります。
上記のヒントがあれば、実装は非常に簡単です。
var http = require('http');
http.get(process.argv[2],function(response){
response.setEncoding('utf8');
response.on("data",function(data){
console.log(data);
});
response.on("error",function(err){
throw err;
});
response.on("end",function(){
// console.log(date);
});
});
HTTP COLLECT
HTTP GETリクエストを発行するプログラムを作成してください。リクエストするURLは、提供されたコマンドライン引数の最初のものです。サーバーから返されるすべてのデータ(「data」イベントだけでなく)を収集し、ターミナル(標準出力 stdout)に2行で出力してください。
出力する内容は、1行目に受け取った文字列コンテンツの長さを表す整数、2行目にサーバーから返された完全な文字列結果であるべきです。
私たちがすべきことは、まずすべてのデータを収集し、リクエストが終了した後にそれらを結合するだけです。
var http = require('http');
var url = process.argv[2];
http.get(url,function(response){
var list = [];
// response.setEncoding('utf8');
response.on("data",function(data){
list.push(data.toString());
});
response.on("error",function(err){
throw err;
});
response.on("end",function(){
var str = '';
list.forEach(function(s){
str += s;
});
console.log(str.length);
console.log(str);
});
});
JUGGLING ASYNC
今回の問題は以前の問題(HTTPコレクター)とよく似ており、
http.get()
メソッドを使用する必要があります。しかし、今回は3つのURLが最初の3つのコマンドライン引数として提供されます。
各URLから返される完全なコンテンツを収集し、それらをターミナル(標準出力 stdout)に出力する必要があります。今回はコンテンツの長さを出力する必要はなく、コンテンツ自体(文字列形式)のみで構いません。各URLに対応するコンテンツは1行です。重要なのは、これらのURLが引数リストに記載されている順序で、対応するコンテンツを並べて出力しなければならないということです。
ここにはいくつかの問題があります:
1 すべてのURLから返される完全なコンテンツを収集する
2 すべてのリクエストが完了したかどうかを判断する
3 URLの順序で取得したコンテンツを出力する
1つ目の問題
これは非常に簡単で、前の問題と同じです。
2つ目の問題
すべてのリクエストが完了したかどうかを判断するには、ステータスを格納するリストが必要です。リクエストが完了したら、それをマークします。
3つ目の問題
URLの順序でコンテンツを出力するには、データを保存する際にそのインデックスを記録し、最後にこのインデックスに従って出力するだけで済みます。
var http = require('http');
var urls = process.argv.slice(2,process.argv.length);
var datas = [];
var end = [];
var is = false;
urls.forEach(function(url,index){
http.get(url,function(response){
var list = [];
response.setEncoding('utf8');
response.on("data",function(data){
list.push(data);
});
response.on("error",function(err){
throw err;
});
response.on("end",function(){
var str = '';
list.forEach(function(s){
str += s;
});
datas[index] = str;
end.push(true);
if (isEnd()) {
for (var i = 0; i < datas.length; i++) {
console.log(datas[i]);
}
}
});
});
});
function isEnd(){
if (end.length === urls.length) {
end.forEach(function(blo){
if(blo){
is = true;
}else{
is = false;
}
});
}
return is;
}
この問題に対する私の方法は少し煩雑です。以下に公式の解答を添付しますので、参考にしてください。
var http = require('http')
var bl = require('bl')
var results = []
var count = 0
function printResults () {
for (var i = 0; i < 3; i++)
console.log(results[i])
}
function httpGet (index) {
http.get(process.argv[2 + index], function (response) {
response.pipe(bl(function (err, data) {
if (err)
return console.error(err)
results[index] = data.toString()
count++
if (count == 3)
printResults()
}))
})
}
for (var i = 0; i < 3; i++)
httpGet(i)
TIME SERVER
TCPタイムサーバーを作成してください。
あなたのサーバーは、最初のコマンドライン引数としてプログラムに渡されるポートをリッスンし、TCP接続を受け取る必要があります。各TCP接続に対して、現在の年月日と24時間制の時刻を以下の形式で書き込む必要があります:
“YYYY-MM-DD hh
”
その後、改行文字が続きます。
月、日、時、分は、固定の2桁になるようにゼロで埋める必要があります:
“2013-07-06 17<42>42>”
公式のヒント:
今回の演習では、TCPサーバーを作成します。HTTPに関するものは一切含まれないため、Nodeのコアモジュールである
net
を使用するだけで十分です。これにはすべての基本的なネットワーク機能が含まれています。
net
モジュールにはnet.createServer()
というメソッドがあり、コールバック関数を受け取ります。Nodeの他のコールバック関数とは異なり、createServer()
で使用されるコールバック関数は複数回呼び出されます。あなたのサーバーがTCP接続を受け取るたびに、このコールバック関数が一度呼び出されます。このコールバック関数は以下の特徴を持っています:
function callback (socket) { /* ... */ }
net.createServer()
はTCPサーバーのインスタンスも返します。特定のポートをリッスンするために、server.listen(portNumber)
を呼び出す必要があります。
典型的なNode TCPサーバーは以下のようになります:
var net = require('net')
var server = net.createServer(function (socket) {
// socket 処理ロジック
})
server.listen(8000)
最初のコマンドライン引数で指定されたポートを必ずリッスンしてください。
socket
オブジェクトには各接続に関する多くの情報(メタデータ)が含まれていますが、同時にNodeの双方向ストリーム(duplex Stream)でもあるため、読み書きが可能です。この演習では、socket
にデータを書き込み、それを閉じるだけで十分です。
socket.write(data)
を使用してsocket
にデータを書き込み、socket.end()
を使用してsocket
を閉じることができます。また、.end()
メソッドはデータオブジェクトを引数として受け取ることもできるため、socket.end(data)
を使用してデータの書き込みとクローズの両方の操作を簡単に完了できます。
公式のヒントがあれば、すべてが非常に簡単になります。
var net = require('net');
var port = process.argv[2];
var getTime = function(){
var date = new Date(),
year = date.getFullYear(),
month = formate(date.getMonth()+1),
day = formate(date.getDate()),
hour = formate(date.getHours()),
minute = formate(date.getMinutes());
var time = year + "-"+month+"-"+day+" "+hour+":"+minute;
function formate(time){
return (time.toString().length>1?'':'0')+time;
}
return time;
};
var server = net.createServer(function(socket){
socket.write(getTime()+'\n');
socket.end();
});
server.listen(port);
HTTP FILE SERVER
HTTPファイルサーバーを作成してください。これは、リクエストされたファイルを毎回クライアントに返します。
あなたのサーバーは、提供された最初のコマンドライン引数で指定されたポートをリッスンする必要があります。
同時に、プログラムに提供される2番目の引数は、応答する必要があるテキストファイルの場所です。この問題では、
fs.createReadStream()
メソッドを使用して、ストリーム形式でリクエストに応答する必要があります。
ここでは http
の http.createServer()
メソッドを使用する必要があります。これは前の net
と似ていますが、HTTPプロトコルで通信するという点が異なります。また、以下の fs
メソッドも使用します。
fs
コアモジュールには、ファイルを処理するためのストリーム(stream)APIも含まれています。fs.createReadStream()
メソッドを使用して、コマンドライン引数で指定されたファイルに対してストリームを作成できます。このメソッドはストリームオブジェクトを返します。このオブジェクトは、src.pipe(dst)
のような構文を使用して、src
ストリームからdst
ストリームにデータを転送(pipe)できます。この形式により、ファイルシステムストリームとHTTPレスポンスストリームを簡単に接続できます。
var fs = require('fs'),
http = require('http');
var port = process.argv[2],
path = process.argv[3];
var fileStream = fs.createReadStream(path);
var server = http.createServer(function(req,res){
res.writeHead(200,{'content-type':'text/plain'});
fileStream.pipe(res);
});
server.listen(port);
HTTP UPPERCASERER
HTTPサーバーを作成してください。これはPOST形式のリクエストのみを受け入れ、POSTリクエストのボディに含まれる文字を大文字に変換してクライアントに返します。
あなたのサーバーは、最初のコマンドライン引数で指定されたポートをリッスンする必要があります。
ここでは toUpperCase()
メソッドを知っていれば十分です。
var http = require('http');
var port = process.argv[2];
var server = http.createServer(function(req,res){
var post = '';
// console.log(req.method);
if (req.method !== "POST") {
return res.end("The method must be POST");
}
res.writeHead(200,{'content-type':'text/plain'});
req.on('data',function(data){
post += data;
});
req.on('end',function(){
res.end(post.toString().toUpperCase());
});
});
server.listen(port);
HTTP JSON API SERVER
HTTPサーバーを作成してください。パスが
/api/parsetime
のGETリクエストを受け取るたびに、JSONデータで応答します。リクエストには、キーがiso
、値がISO形式の時刻であるクエリパラメータ(query string)が含まれることを想定しています。
例:
/api/parsetime?iso=2013-08-10T12:10:15.474Z
応答されるJSONは、hour
、minute
、second
の3つのプロパティのみを含むべきです。例:{ "hour": 14, "minute": 23, "second": 15 }
次に、
/api/unixtime
というパスのインターフェースを追加します。これは同じクエリパラメータ(query string)を受け取ることができますが、その戻り値にはunixtime
というプロパティが含まれ、対応する値はUNIXタイムスタンプです。例:{ "unixtime": 1376136615474 }
あなたのサーバーは、最初のコマンドライン引数で指定されたポートをリッスンする必要があります。
まず、時間をそれぞれの形式の JSON
ファイルに変換する2つのメソッドが必要です。次に、リクエストを解析するために url
モジュールを使用する必要があります。
var http = require('http'),
url = require('url');
var port = process.argv[2];
var parseTime = function(time){
return{
"hour": time.getHours(),
"minute": time.getMinutes(),
"second": time.getSeconds()
};
};
var getUnixTime = function(time){
return{
"unixtime": time.getTime()
};
};
var server = http.createServer(function(req,res){
// console.log(url.parse(req.url,true));
var queryString = url.parse(req.url,true).query,
time = new Date(queryString.iso),
pathname = url.parse(req.url,true).pathname,
result;
if (req.method != "GET") {
res.end('Method must be GET');
}
if (pathname === '/api/parsetime') {
result = parseTime(time);
}else if(pathname === '/api/unixtime'){
result = getUnixTime(time);
}
if (result) {
res.writeHead(200,{'Content-Type':'application/json'});
res.end(JSON.stringify(result));
}else{
res.writeHead(404);
res.end('404,not found');
}
});
server.listen(Number(port));
最後に
私も初めての経験だったので、コードに問題があるかもしれません。ぜひ議論して、お互いに学びましょう。
上記のすべてのコードはこちらに統合されています。
この記事は 2015年6月11日 に公開され、2015年6月11日 に最終更新されました。3769 日が経過しており、内容が古くなっている可能性があります。