node.jsでファイル操作

node.js使って何か作りたいと漠然と思っているけど、なかなか何を作ろうか、何が作れるのかが浮かんでこないのでとりあえずnode.jsに慣れておこうと思ってまずファイル操作から始めることにした。

今回はcygwin環境。(前に入れたportble ubuntuが動かなかったので…)

構成は以下。

  • index.html
  • jquery.js
  • server.js
  • fu.js

ページでファイルの名前と書き込む内容を入力させて送信するという超簡単なもの。

HTMLのBODY部分
<div>
	file: <input type="text" name="fileName" id="fileName" />.txt<br />
	content: <input type="text" name="content"id="content" /><br />
	<input type="submit" value="ok" id="addButton" />
	<p id="msg"></p>
</div>
クライアント側スクリプト
$(document).ready(function(){
	var fileName = $('#fileName');
	var content = $('#content');
	var msg = $('#msg');
	$('#addButton').click(function(){
		msg.text('');
		$.getJSON('/post', {
			fileName: fileName.val(),
			content: content.val()
		}, function(res){
			if(res && res.result == "200"){
				msg.text('ファイルを作成しました。');
				fileName.val('');
				content.val('');
			}else{
				msg.text('ファイル操作に失敗しました。');
			}
		});
	});
});

テキストフォームに入力されたファイル名とファイルの内容をサーバーに送信します。

サーバー側スクリプト

肝となるサーバー側のスクリプトは node.js を使ったチャットシステムサンプルの node chat を参考。
その際 node chat ではクライアントからのリクエストを「fu」というモジュールを使って捌いているようだったので使おうと思ったのだけど、どうやらこれは外部のモジュールのようだ。
node chat のソースでも

require('./fu');

とやっていて同じディレクトリ内の fu.js を読み込んでいた。
ということで node chat から fu.js をコピーしてきて利用しよう。

HOST = null;
PORT = 8080;

var fu = require('./fu');
var qs = require('querystring');
var url = require('url');
var fs = require('fs');

fu.listen(Number(process.env.PORT || PORT), HOST);

// #1
fu.get('/', fu.staticHandler('index.html'));
fu.get('/jquery.js', fu.staticHandler('jquery.js'));

// #2
fu.get('/post', function(req, res){
	console.log(url.parse(req.url));
	var query = qs.parse(url.parse(req.url).query);
	fileControl(query, function(){
		res.simpleJSON(200, {result: '200', query: query});
	});
});

// #3
var fileControl = function(fileInfo, callback){
	var fileName = fileInfo.fileName.replace('.', '');
	fileName = fileName.replace('/', '');
	fileName = fileName.replace('\\', '');
	fs.open('./' + fileName + '.txt', 'a', 777, function(err, fd){
		var buf = new Buffer(fileInfo.content);
		fs.write(fd, buf, 0, Buffer.byteLength(fileInfo.content), null, function(){
			fs.close(fd);
			callback();
		});
	});
};

#1. 静的なファイルの読み込み

fu.get()メソッドを使ってURLに静的ファイルをマッピングさせます。これを行うことによって静的なファイルを利用出来るようになります。

#2. クライアントからのリクエスト処理

クライアントからのリクエストパラメータを取り出します。

url.parse()

url.parse()はURLをパースしてオブジェクトに変換します。

{
    href: '/post?fileName=foo&content=hogehoge',
    search: '?fileName=foo&content=hogehoge',
    query: 'fileName=foo&content=hogehoge',
    pathname: '/post'
}

こんな感じのオブジェクトを返してくれます。

querystring.parse()

querystring.parse()はクエリ文字列をオブジェクトに変換します。
先程url.parse()によってクエリ文字列の部分を得ることができたのでこれを渡してやります。

{
    fileName: 'foo',
    content: 'hogehoge'
}

#3. ファイル操作

fs.open()

fs.open()で非同期にファイルを開きます。ファイル操作系の関数は関数名にSyncがついてるものとついてないものがありますが、Syncがついてる同期バージョンを使うとプロセス全体をブロックしてしまうそうなのでここでは常に非同期バージョンを使用していきます。
ドキュメントでも非同期バージョンを使うことを強く推奨しています。
fs.open()はファイルを開くとコールバック関数に err と fd を渡します。fd はファイルポインタ?書き込み時やクローズ時にはこれを使用します。

fs.write()

fs.write()でファイルに書き込みをします。書き込みも非同期で行うので書き込み終了後にファイルをクローズするためにfs.write()のコールバック関数内でfs.close()を呼び出しています。

最後に結果をJSON形式で返します。

これで簡単なファイル操作をする事ができました。JavaScriptでファイルを生成するのってすごい違和感を覚えるなぁw
JavaScriptの認識が大きく変わりそう。


もっといろいろ試していこう。

(そういえばWindows環境下では portable ubuntu より cygwin の方が快適な気がする。)