SHOYAN BLOG

I am a pragmatic programmer.

Expressのインストールと構成を把握する

Node.jsのフレームワーク、Expressを試してみました。
ExpressはNode.jsでwebアプリケーションを作成するためのフレームワークです。

インストール

まずは、Node.jsをインストールします。

以下のページからパッケージをダウンロードしてインストールします。
https://nodejs.org/en/

次にnpmを使ってexpress-generatorをインストールします。
express-generatorはExpressのスケルトンアプリケーションを作成するコマンドです。
npmはNode.jsをインストールした際にインストールされていると思います。

1
$ sudo npm install express-generator -g

スケルトンアプリケーションを作成する

expressコマンドでスケルトンアプリケーションを作成します。

1
2
3
$ express myapp
$ cd myapp
$ npm install

起動してみます。

1
2
3
4
5
6
7
8
9
⇒  DEBUG=myapp:* npm start

> myapp@0.0.0 start /Users/PMAC025S/Development/sample/nodejs/myapp
> node ./bin/www

  myapp:server Listening on port 3000 +0ms
GET / 200 659.865 ms - 170
GET /stylesheets/style.css 200 9.088 ms - 111
GET /favicon.ico 404 70.846 ms - 1285

http://localhost:3000 にアクセスしてみます。
Welcome to Express と表示されていれば正常に起動できています。

サーバーの停止はCtrl + Cです。

ルーティング

ルーティングの基本的な構造です。

1
app.METHOD(PATH, HANDLER)

app: expressのインスタンス
METHOD: HTTPメソッド(GET, POST, PUT, PATCH, DELETE等)
PATH: サーバーのパス
HANDLER: 実行する関数

Hello World!を返すルーティングのサンプルです。
/にGETリクエストを送ると、Hello World!が返却されます。

1
2
3
app.get('/', function (req, res) {
  res.send('Hello World!');
});

/にPOSTリクエストを送ると、Got a POST requestが返却されます。

1
2
3
app.post('/', function (req, res) {
  res.send('Got a POST request');
});

Expressの構造

express-generator で作成されたファイルを確認していきます。
ファイル構成は以下です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
  tree -L 2
.
├── app.js
├── bin
   └── www
├── node_modules
   ├── body-parser
   ├── cookie-parser
   ├── debug
   ├── express
   ├── jade
   ├── morgan
   └── serve-favicon
├── package.json
├── public
   ├── images
   ├── javascripts
   └── stylesheets
├── routes
   ├── index.js
   └── users.js
└── views
    ├── error.jade
    ├── index.jade
    └── layout.jade

app.js

app.jsではアプリの設定やルーティングを定義します。
簡単なアプリであれば、ここに全て書いてしまってもよいでしょう。
express-generatorで作成されたファイル構成では、routes/views/ ディレクトリが作成されており、ルーティングやテンプレートは分離する構成となっています。

bin

実行ファイルが格納されます。

node_modules

Expressなどのモジュールが入っているディレクトリです。npmでインストールしたファイルが格納されます。

package.json

アプリの設定やメタ情報を定義するファイルです。

public

公開ディレクトリです、cssファイル、imageファイル、JavaScriptファイルを格納します。

routes

アプリケーションのルーティングと処理を定義したファイルを格納します。

viewsディレクトリ

テンプレートを定義したファイルを格納します。

Expressの構成は把握できました。
次はExpressを使ってチャットアプリケーションを作ってみたいと思います(次回へ続く)。

参考リンク

Rubyでスラッシュを意識せずにコーディングする方法

ファイルのパスを生成する際にセパレーターを意識しなくてはならず、どうにもいけていないコードがあります(ありますというか自分が量産していました)。
以下のようなコードです。

1
path = "#{path1}/{$path2}"

セパレーターを意識せずコーディングできないものかと調べたところ、File.joinを使えばよしなにやってくれることがわかりました。
File.joinを使えばセパレーターを指定する必要はありません。

1
2
File.join("hoge", "moge")
=> "hoge/moge"

File.joinは正しいであろうパスのフォーマットに補正してくれるため、//hogeのようなパスが生成されることもありません。

1
2
File.join("hoge", "moge/", "/fuga")
=> "hoge/moge/fuga"

パラメーターが配列でもよしなにやってくれます。

1
2
File.join(%w(hoge moge/ fuga))
=> "hoge/moge/fuga"

地味ですが、これでセパレーターの悩みから解放されて幸せになれます。

参考リンク

Exception Notificationでundefined Method `current' for Time:Classエラーがでた

例外発生時にException Notificationで通知をしようと思い、導入してみたところ以下のエラーがでました。

1
2
ERROR: Failed to generate exception summary:
ActionView::Template::Error: undefined method `current' for Time:Class

日付の取得にTime.currentを使っており、Time.currentはActive supportにより拡張されたメソッドなのでActive Supportを使っている環境でしか動作しません(要するにRailsじゃないと動かない。Sinatraは…)。

PRもでているので対応してほしいところです。

対応方法

require 'active_support/core_ext/time’ をすることでTime.currentを使えるようにしました。

サンプルコード

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
require 'rubygems'
require 'bundler/setup'
require "sinatra/base"
require 'exception_notification'
# Time.currentを使えるようにする
require 'active_support/core_ext/time'

class App < Sinatra::Application
  use ExceptionNotification::Rack,
    :email => {
      :email_prefix => "[Exception] ",
      :sender_address => %{"notifier" <notifier@example.com>},
      :exception_recipients => %w{shoyan@example.com}
    }

  get '/' do
    begin
      // 何かの処理
    rescue Exception => e
      status 500
      ExceptionNotifier.notify_exception(e, env: env)
      e.message
    end
  end
end

Sinatraのエラーハンドリング

Sinatraには not_foundハンドラとerrorハンドラの2つのハンドラが用意されています。
not_foundハンドラは404エラーを補足するためのエラーハンドラです。
errorハンドラは様々なエラーを補足するためのエラーハンドラです。

Not Foundハンドラ

not_foundハンドラは404エラーを補足するためのエラーハンドラです。
Sinatra::NotFoundが発生したとき、またはステータスコードが404のときは not_foundハンドラが実行されます。

1
2
3
not_found do
  'This is nowhere to be found.'
end

Errorハンドラ

errorハンドラは様々なエラーを補足するためのエラーハンドラです。
例外オブジェクトにはRack変数の sinatra.error でアクセスできます。

1
2
3
error do
  'Sorry there was a nasty error - ' + env['sinatra.error'].message
end

以下の設定をすると、environmentがDevelopmentのときにブラウザにスタックトレースを表示することができます。

1
set :show_exceptions, :after_handler

エラー固有の制御もできます。
MyCustomeErrorのエラーハンドリングをしたいときは以下のように定義します。

1
2
3
error MyCustomError do
  'So what happened was...' + env['sinatra.error'].message
end

raiseでエラーを発生させるようにしてみます。

1
2
3
4
5
6
7
get '/' do
  raise MyCustomError, 'something bad'
end

# 以下のようにレスポンスが返ります。

So what happened was... something bad

ステータスコードを指定してエラーハンドリングを行う方法もあります。

1
2
3
error 403 do
  'Access forbidden'
end

レンジの指定も可能です。

1
2
3
error 400..510 do
  'Boom'
end

errorハンドラにerrorコードを指定しなかった場合は何を補足するのか

errorハンドラにerrorコードを指定しなかった場合は、Exceptionを補足します。

1
2
3
4
5
6
7
  def error(*codes, &block)
    args  = compile! "ERROR", /.*/, block
    codes = codes.map { |c| Array(c) }.flatten
    codes << Exception if codes.empty? #errorコードの指定がない場合
    codes << Sinatra::NotFound if codes.include?(404)
    codes.each { |c| (@errors[c] ||= []) << args }
  end

https://github.com/sinatra/sinatra/blob/939ce04c1b77d24dd78285ba0836768ad57aff6c/lib/sinatra/base.rb#L1287

その他の例外は補足しません。
例えばExceptionのサブクラスであるStandardErrorは拾ってくれません。
ですので、明示的にerrorコードを指定しておいたほうがよいです。

1
2
3
error 500 do
  'Sorry there was a nasty error - ' + env['sinatra.error'].message
end

参考文献

Knife Zeroを使ってレシピを適用する

Chefでよく使われるknifeコマンドですが、そのプラグインであるknife zeroを使ってレシピを適用する方法を紹介します。
knife zeroはknifeプラグインで、リモートnode上でchef-clientを実行するツールです。

リモートnodeとはchefを適用するサーバー(管理対象となるサーバー)のことです。

インストール

今回はGemfileに定義してインストールします。

1
2
# Gemfile
gem 'knife-zero'

以下でインストールされます。

1
$ bundle

chefをリモートnodeにインストール

まずはchefをリモートnodeにインストールします。
以下のコマンドでインストールします。

1
$ bundle exec knife zero bootstrap shoyan@server01.example.com --sudo

実行すると node/ 配下にファイルが作成さます。

chef_environmentrun_listを追加します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
  "name": "server01.example.com",
  "chef_environment": "production",
  "run_list": [
    “role[awesome_cookbook]"
  ],
  "normal": {
    "knife_zero": {
      "host": "server01.example.com"
    },
    "tags": [

    ]
  },
  "automatic": {
    "fqdn": "server01.example.com",
    "os": "linux",
    "os_version": "2.6.32-504.3.3.el6.x86_64",
    "platform": "centos",
    "platform_version": "6.4",
    "hostname": "server01.example.com",
    "ipaddress": “192.168.1.1",
    "roles": [

    ]
  }
}

リモートnodeにログインして、chefのコマンドが実行されていることを確認してみます。

1
2
3
4
$ ssh shoyan@server01.example.com

[shoyan@server01 ~]$ chef-[Tabを押す]
chef-apply   chef-client  chef-shell   chef-solo

レシピをリモートnodeに適用する

Chefを実行する準備ができました。
以下のコマンドでレシピをリモートnodeに適用します。

1
$ bundle exec knife zero converge 'fqdn:server01.example.com' -x shoyan