一から勉強させてください( ̄ω ̄;)

最下級エンジニアが日々の学びをアウトプットしていくだけのブログです。

node.jsで画像アップロードアプリをつくってみた

今回は、node.jsを用いて画像アップロードアプリケーションの作成に挑戦しました。


といってもこちらを参考に真似しただけなんですけどね。。


ちゃっかりgithubに公開もしてます↓

https://github.com/danimal141/file_upload_app


まずどういうアプリケーションかというと、


1, /startにアクセスして、画像をローカルからアップロードする。

               ↓

2, uploadボタンを押すと、/uploadに遷移。1で選択した画像が表示される。
この時、アップロードした画像はtest.jpg(今回はjpgのみ扱うことにしてます)という名前にリネームされ、imgフォルダに保存される。


というシンプルなものです。


まず、用意するjsファイルは以下の4つです。

  • index.js
  • server.js
  • router.js
  • requestHandler.js


これらはそれぞれ、

index.js → メインファイルです。アプリケーション起動時はこいつを召喚します。

server.js → webサーバーを動かすための命令を書きます。

router.js→ ルーター的役割を担います。アクセス元のパスを判別して、条件に沿ったリクエストハンドラorエラーを返します。

requestHandler.js→ 画像アップロード〜表示 まで、アプリケーションの肝となる部分はすべてこれに書いてます。


といった役割を持っています。

この役割別にモジュールを使いこなしている感じが乙ですね!!


では各ファイルの中身を見ていきます。

1, index.js

まず、メインファイルであるindex.jsです!

//index.js

 var server = require("./server");
 var router = require("./router");
 var requestHandlers = require("./requestHandlers");

 var handle = {};
 handle["/"] = requestHandlers.start;
 handle["/start"] = requestHandlers.start;
 handle["/upload"] = requestHandlers.upload;
 handle["/show"] = requestHandlers.show;

 server.start(router.route, handle);


ここではまず、server, router, requestHandlerといった他のモジュールをrequireで召喚しておきます。

そして、handleオブジェクトを作ります。
これはrouterで、アクセス元のURLによって処理を切り換えるときに使います。

で、serverのstartメソッドを呼び出してスタートです。
ちなみに引数にはrouterのrouteメソッドそのものと先ほどつくったhandleオブジェクトを渡しています。関数自体を引数で渡すパターンですね。


2, server.js

次に、上記で呼び出されたserver.jsの中身です。

//server.js

var http = require("http");
var url = require("url");

function start(route, handle){
	function onRequest(req, res){
		var pathname = url.parse(req.url).pathname;
		console.log("pathname: " + pathname );

		route(handle, pathname, res, req);
	}

	http.createServer(onRequest).listen(8888);
	console.log("Server has started");
}

exports.start = start;


ここではhttpとurlをrequireしてます。
httpはサーバー起動、urlはrouterにurlのパスを渡す際に使ってます。

startメソッドでは先ほど、index.jsで渡した引数がroute, handleとして書かれています。
そして、その中でさらにhttp.createServer()に渡す用のメソッドonRequestが書かれています。これはクライアントからサーバーにリクエストイベントがあった時に発生するコールバック関数的な役割を担っています。


よりイベント感あふれる書き方をすると

var server = http.createServer();
server.on("request", onRequest);
server.listen(8888);


とすることもできると思います。

そしてonRequestの中では、リクエストがあったURLの名前を url.parse(req.url).pathnameで取得してます。
たぶんurl.parse(req.url)で {"pathname":"~/~"}みたいなオブジェクトが返ってくるんだとおもいます。

あとはコンソールを交えつつ、routerにhandle, pathname, req, resをすべて渡して役目は終了です。
resの処理はrequestHandlerで書くので、ここでは引数として渡すだけです。

最後にexports.start = startとしてこのモジュールのメソッドとしてエクスポートしておきます。
index.jsでserver.startってできるのもこの作業をやっているおかげですね。


3, router.js

続いてrouter.jsです。

//router.js

function route(handle, pathname, res, req){
	console.log("About to route a request for " + pathname);

	if(typeof handle[pathname] === "function"){
		return handle[pathname](res, req);
	}else{
		console.log("No Request handler found for "+ pathname);
		res.writeHead(404, {"Content-Type" : "text/plain"});
		res.write("404 Not Found");
		res.end();
	}
}
exports.route = route;

handle[pathname]がfunctionとして存在する場合はそれにres,reqを渡して返します。これはindex.jsで登録しておいたものですね。実際のfuntionの中身はrequestHandler.jsに書きます。
そして、handle[pathname]が存在しない場合は404を返して Not Foundを表示します。

こちらもserver.js同様、エクスポートしてあります。


4, requestHandler.js

最後はrequestHandler.jsです。

var fs = require("fs"),
    formidable = require("formidable");

 function start(res){
 	console.log("Request handler 'start' was called.");

 	var body = '<html>'+
    '<head>'+
    '<meta http-equiv="Content-Type" '+
    'content="text/html; charset=UTF-8" />'+
    '</head>'+
    '<body>'+
    '<h1>ファイルをアップロードしてください</h1>'+
    '<form action="/upload" enctype="multipart/form-data" '+
    'method="post">'+
    '<input type="file" name="upload">'+
    '<input type="submit" value="Upload" />'+
    '</form>'+
    '</body>'+
    '</html>';

    res.writeHead(200, {"Content-Type":"text/html"});
    res.write(body);
    res.end();
 }


 function upload(res, req){
    console.log("Request handler 'show' was called.");
    var form = new formidable.IncomingForm();
    console.log("about parse");
    form.parse(req, function(error, fields, files){
        console.log("parsing done");
        fs.rename(files.upload.path, "img/test.jpg", function(err){
            if(err){
                fs.unlink("img/test.jpg");
                fs.rename(files.upload.path, "img/test.jpg");
            }
        });
       res.writeHead(200, {"Content-Type":"text/html; charset=UTF-8"});
       res.write("送信された画像ですよー↓<br/>");
       res.write("<img src='/show' />");
       res.end();
    });
 }

 function show(res){
     console.log("Request handler 'show' was called.");
     fs.readFile("img/test.jpg", function(error, file){
        if(error){
            res.writeHead(500, {"Content-Type": "text/plain; charset=UTF-8"});
            res.write("OH MY GOD\n"+error+"\n");
            res.end();
        }else{
            res.writeHead(200, {"Content-Type":"image/jpg"});
            res.write(file);
            res.end();
        }
     });
 }

 exports.start = start;
 exports.upload = upload;
 exports.show = show;


ここではfsとformidableという二つのモジュールをrequireして同名の変数にいれています。
fsはファイルシステムを扱うモジュール、formidableはnode.jsでファイル(特に画像とか動画とか)のアップロード処理を簡単に扱えるモジュールです。

ファイルシステムって多分、フォルダやファイルを作成したり、移動したり、削除したり、っていうことを管理するシステムのことを表しておるのではないかと思います。

今回の場合だと画像をアップロードして表示したり、アップロードした画像を名前をつけて保存したりするので、これらのモジュールの助けを借ります。

まずはじめのstartメソッドはhtmlを表示させるための命令が書いてあります。
ここにファイルをアップロードするためのフォームも置いてあるので、ここからファイルを選択してアップロードすることで次のupload処理に移ります。


次にuploadメソッドです。
new formidable.IncomingForm()で作成したオブジェクトformがリクエストを受け取るとコールバック関数が呼び出されます。このコールバック関数内でアップロードされたファイルの名前をtest.jpgにリネームしてimgフォルダに保存しています。(jpg限定ですみません)

fs.renameの引数は古いパス、新しいパス、コールバック関数です。

そしてimgタグのsrcに/showを指定して表示します。あとは/showがアップロードした画像になれば完了です!!

※ちなみにwriteHeadのところでcharset=UTF-8を入れ忘れると派手に文字化けしましたので、ご注意ください。。


最後はshowメソッドです。
先ほど保存しておいたimg/test.jpgをfs.readFileで読み込んでいます。

ちなみにreadFileの2番目の引数に"binary"を指定した場合、res.write(file,"binary")としないと怒られました。こちらもご注意くださいw

そしてtest.jpgが無い状態で/showにアクセスしてもOH MY GODエラーが出るようにしときました。


以上で全メソッドが完成したので、エクスポートして、作業終了です。


実際にアプリケーションの動きを確認するには、まず、node index.jsでアプリを起動。
つぎにlocalhost:8888の/または/startにアクセスしてファイルを適当にアップロードすると、/uploadに遷移します。
そしてimgのsrcに/showが指定され、アップした画像が表示されていると思います。


まだ完全に理解しきれていない命令などもありますが、役割ごとにモジュールを分けて実装する感じがとてもおもしろかったです。

文字化けとかにハマったりもして、良い勉強になりました。。

もっといっぱいサンプルをつくってさらに勉強を重ねて行きたいと思います。
がんばろー


小さな事からコツコツと。