Shoeisha Technology Media

CodeZine(コードジン)

記事種別から探す

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

CoffeeScriptによるモダンなWebアプリケーション開発 第4回

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2012/05/01 14:00

 最近話題の新言語『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に追加してエクスポートすることもできます。一体このexportsmodule.exportsは何が違うのでしょうか?

 exportsmodule.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

  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

  • 飯塚 直(イイヅカ ナオ)

    1984年東京都生まれ。 高校時代に趣味でPerlやJavaを使ってプログラミングを始める。 慶応大学湘南藤沢キャンパス卒業後、共同通信社にてニュースサイトの開発などを担当。 その後、面白法人カヤックにてソーシャルゲームの開発などを手がける。 2012年現在、カヤックを退社し...

バックナンバー

連載:CoffeeScriptによるモダンなWebアプリケーション開発

もっと読む

All contents copyright © 2005-2017 Shoeisha Co., Ltd. All rights reserved. ver.1.5