CoffeeScriptベストプラクティス集 Node.jsアプリケーション編(2)

CodeZine / 2012年5月1日 14時0分

図3 標準のエラーページ

 最近話題の新言語『CoffeeScript』をとりあげた連載「CoffeeScriptによるモダンなWebアプリケーション開発」。連載第3回からはベストプラクティス編として、CoffeeScriptでNode.jsアプリケーションを開発する際によく使われる実用的な開発手法を4回に分けて紹介しています。CoffeeScriptの歴史や概要については過去の連載も参照ください。

■Node.jsのモジュールシステム

 Node.jsはCommonJS(注1)をベースとしたモジュールの仕組みを採用しており、require()で他のモジュールをロードできるようになっています。この仕組みのおかげでアプリケーションを複数のファイルに分割して管理でき、全体の見通しをよくすることができます。このrequire()の仕組みはブラウザではサポートされていません。

注1:CommonJS
 ブラウザ以外でのJavaScriptを取り巻く仕様を決めるためのプロジェクト。モジュール仕様はその一部。Node.jsのモジュールシステムはCommonJSの一部であるモジュール仕様をベースとして作られた。



●Node.jsとブラウザの両方に対応したライブラリを作る

 Node.jsとブラウザのどちらにも対応したライブラリを作る時は、Node.jsではrequire()で、ブラウザでは<script>タグで読み込めるよう、[リスト1]のように記述しておきます。ブラウザでは関数の内側にないthisはグローバルオブジェクトのwindowを指すため、thisのプロパティに代入すると他のスクリプトから参照できるようになります。

[リスト1]Node.jsとブラウザに両対応したライブラリ
class MyLibrary # … if module?.exports # Node.jsの場合 module.exports = MyLibrary else # ブラウザの場合 @MyLibrary = MyLibrary
 また、RequireJSで使われているAMDというモジュール定義方式に対応するには[リスト2]のように記述します。

[リスト2]リスト1をAMDにも対応させる(mylib.coffee)
class MyLibrary # … if define? and define.amd # AMDの場合 define -> MyLibrary # 使い方: require ['mylib'], (MyLibrary) -> else if module?.exports # Node.jsの場合 module.exports = MyLibrary # 使い方: MyLibrary = require 'mylib' else # ブラウザの場合 @MyLibrary = MyLibrary # 使い方: <script src="mylib.js"></script>
●exportsとmodule.exportsの違い

 他のモジュールから利用できるようにプロパティや関数などをエクスポートするにはexportsというオブジェクトに追加すればよいですが、代わりにmodule.exportsに追加してエクスポートすることもできます。一体このexportsとmodule.exportsは何が違うのでしょうか?

 exportsとmodule.exportsは、それ自身に代入が可能かという点が異なります。exportsオブジェクトに追加したプロパティは正しくエクスポートされますが、exports自体を上書きしてしまうと何もエクスポートされません。

[リスト3]誤ったエクスポート方法
exports = MyClass # 何もエクスポートされない
 一方、module.exportsに値を代入すると問題なくエクスポートされ、代入した値がrequire()の戻り値となります。エクスポートする関数をまとめて指定したい場合や、require()の戻り値としてオブジェクト以外の値を返したい場合などにmodule.exportsに代入する方法を使います。もっとも、exportsの代わりにmodule.exportsを常に使っても問題ありません。

[リスト4]mymod.coffee
# ここはrequire()された時点で実行される console.log "モジュールをロード中" # require()で返されるオブジェクト module.exports = setup: -> console.log "setup" myfunc: -> console.log "myfunc"
[リスト5]test.coffee
console.log "step 1" mymod = require './mymod' console.log "step 2" mymod.myfunc()
[リスト6]リスト5の実行結果
step 1 モジュールをロード中 step 2 myfunc
 一方、exportsのプロパティとして代入する場合にはmodule.exportsと記述する必要はありません。リスト4はmodule.exportsを使わずにリスト7のように記述できます。

[リスト7]リスト4をmodule.exportsを使わずに書く
# ここはrequire()された時に実行される console.log "モジュールをロード中" # エクスポートする関数をexportsに追加していく exports.setup = -> console.log "setup" exports.myfunc = -> console.log "myfunc"
■URLからコンテンツを取得する

 Node.jsの標準APIを使ってURLからコンテンツを取得するにはリスト8のように少し面倒なコードを書く必要があります。

[リスト8]標準APIでURLからコンテンツを取得
http = require 'http' http.get host: 'ja.wikipedia.org' path: '/wiki/%E6%9D%B1%E4%BA%AC' , (res) -> if res.statusCode is 200 body = '' res.setEncoding 'utf8' res.on 'data', (chunk) -> body += chunk res.on 'end', -> console.log body else console.log "error: #{res.statusCode}"
 Requestというモジュール(Apacheライセンス2.0)を使うと、より短いコードでコンテンツを取得できます。Requestをインストールするには、プロジェクトのディレクトリで次のコマンドを実行します。

$ npm install request
 Requestを使うと、リスト8と同様の単純なGETリクエストをリスト9のように記述することができます。

[リスト9]Requestを使ったGET
request = require 'request' request url: 'http://ja.wikipedia.org/wiki/%E6%9D%B1%E4%BA%AC' , (err, response, body) -> throw err if err # 接続エラーなどが発生した場合 if response.statusCode is 200 # ステータスコードが「OK」の場合 console.log body else console.log "response error: #{response.statusCode}"
 request()の第1引数でURLなどのパラメータを指定します。ここでqsにオブジェクトを指定すると、URLの?以降のクエリストリングをオブジェクトで指定することができます。

url: 'http://localhost/path' qs: abc: 123 def: 456
 これはhttp://localhost/path?abc=123&def=456へのリクエストとなります。

 また、POSTリクエストの場合はリスト10のようにrequest.post()メソッドを使用します。formパラメータをオブジェクトで指定すると、HTMLでフォームを送信した場合のようにname1=value1&name2=value2の形式でPOSTデータが送信されます。リクエストのContent-Typeは自動的にapplication/x-www-form-urlencoded; charset=utf-8となります。

[リスト10]Requestを使ったPOST
request.post url: 'http://localhost/post' form: abc: 123 def: 456 , (err, response, body) ->
 このリクエストで送信されるPOSTデータは以下のようになります。

abc=123&def=456
 また、jsonパラメータをオブジェクトで指定した場合、JSON文字列に変換されてPOSTデータが送信されます。リクエストのContent-Typeは自動的にapplication/jsonとなります。

[リスト11]JSON文字列でのPOST
request.post url: 'http://localhost/post' json: abc: 123 def: 456 , (err, response, body) ->
 このリクエストで送信されるPOSTデータは以下のようになります。

{"abc":123,"def":456}
 その他、任意のPOSTデータを送信するにはbodyパラメータで文字列を指定します。

[リスト12]任意の文字列でのPOST
request.post url: 'http://localhost/post' body: 'something' , (err, response, body) ->
 バイナリデータなどを送信する場合にはBufferオブジェクトをbodyパラメータに指定することができます。

[リスト13]バイナリデータでのPOST
fs = require 'fs' # ファイルを読み込みBufferオブジェクトを取得する imageData = fs.readFileSync 'image.png' request.post url: 'http://localhost/post' body: imageData , (err, response, body) ->
●バイナリデータの受け取り

 レスポンスはデフォルトでUTF-8として解釈された文字列となりますが、Bufferオブジェクトで受け取りたい場合はencodingパラメータにnullを指定します。

[リスト14]POSTでBufferオブジェクトで受け取りたい場合
request url: 'http://localhost/image.png' encoding: null , (err, response, body) -> throw err if err if response.statusCode is 200 process.stdout.write body else console.log "response error: #{response.statusCode}"
●タイムアウト

 リクエストにタイムアウトを設定するにはtimeoutをミリ秒で指定します。タイムアウトが発生した場合、コールバックのerr引数に値が入ります。

[リスト15]リクエストにタイムアウトを設定する場合
request url: 'http://localhost/' timeout: 30000 # 30秒でタイムアウト , (err, response, body) -> throw err if err


■関連記事
CoffeeScriptベストプラクティス集 ブラウザ向けJavaScript編(3)
CoffeeScriptベストプラクティス集 ブラウザ向けJavaScript編(2)
CoffeeScriptベストプラクティス集 ブラウザ向けJavaScript編(1)
CoffeeScriptベストプラクティス集 Node.jsアプリケーション編(4)
CoffeeScriptベストプラクティス集 Node.jsアプリケーション編(3)

■記事全文へ

CodeZine

トピックスRSS

ランキング