プログラム

node.jsでヒアドキュメント

Node.jsでプログラムを書いている時、JavaScriptでヒアドキュメント書きたいとき*1が稀にあって、そんなときはnpmパッケージのhereを使うと実現できたりします。

$ npm install here --save

した後に、

var here = require('here').here;
var testDoc = here(/*
def foo
  bar
end
*/).unindent();
console.log(testDoc)

とすれば、

def foo
bar
end

と表示されます。

NPM

*1:mochaでテスト書くときとかに...

sails+MongoDBでJSONベースのAPIを作成してみる

先日からAPIサーバをsailsとMongoDBを使って作ってみようとあれこれしてみたのでその際のメモです。

sailsの特徴に

  • 最初からsocket.io(websocket)が利用できる
  • JSONベースのAPIが簡単に作れる

があるのですが、ここでは後者のJSONベースのAPIを作ってみることにして、データストアにMongoDBを使ってみました。*1


準備

sailsのGet Startedページを参考にしつつ、npm及びsailsコマンドが使える状況であれば...

$ sails new testapp
info: Created a new Sails app `testapp`!

testapp というディレクトリが掘られてアプリケーションの雛形が作成されます。

今回はMongoDBへ接続してみるのでそのディレクトリ内でsails-mongoを入れておきます。

$ npm install sails-mongo --save

接続設定

config/connections.js に接続設定を書きます。

既にサンプルの記述はsailsコマンドで出力されているので必要あらばホスト名, ポート番号, 接続用ユーザ名, パスワード, データベース名を書き換えます。

  someMongodbServer: {
adapter: 'sails-mongo',
host: 'localhost',
port: 27017,
// user: 'username',
// password: 'password',
// database: 'your_mongo_db_name_here'
},

続けて、config/env/development.js に開発環境の場合にモデルから接続するデータベース接続を設定することができるので設定します。
connectionの箇所に上の config/connections.js で定義した接続名を記載。

module.exports = {
models: {
connection: 'someMongodbServer'
}
};

API生成(CRUDできるコントローラ + Modelの生成)

実際にMongoDBに作成されるコレクションに対応するModelとそれをCRUD操作できるコントローラを作成します。

サンプルとしてUserというモデルを作成してみます。sailsコマンドを入力。

$ sails generate api user
info: Created a new model ("User") at api/models/User.js!
info: Created a new controller ("user") at api/controllers/UserController.js!
info: REST API generated @ http://localhost:1337/user
info: and will be available the next time you run `sails lift`.

これで接続設定は完了したのでおもむろにsailsサーバを立ちあげます。新しくMongoDB上にデータベースを作成することになるのでalterを選択してsailsからMongoDB上にデータベースとコレクションを作成してもらいます。

$ sails lift
info: Starting app...
-----------------------------------------------------------------
Excuse my interruption, but it looks like this app
does not have a project-wide "migrate" setting configured yet.
(perhaps this is the first time you're lifting it with models?)
In short, this setting controls whether/how Sails will attempt to automatically
rebuild the tables/collections/sets/etc. in your database schema.
You can read more about the "migrate" setting here:

In a production environment (NODE_ENV==="production") Sails always uses
migrate:"safe" to protect inadvertent deletion of your data.
However during development, you have a few other options for convenience:
1. safe  - never auto-migrate my database(s). I will do it myself (by hand)
2. alter - auto-migrate, but attempt to keep my existing data (experimental)
3. drop  - wipe/drop ALL my data and rebuild models every time I lift Sails
What would you like Sails to do?
info: To skip this prompt in the future, set `sails.config.models.migrate`.
info: (conventionally, this is done in `config/models.js`)
warn: ** DO NOT CHOOSE "2" or "3" IF YOU ARE WORKING WITH PRODUCTION DATA **
prompt: ?:  2
Temporarily using `sails.config.models.migrate="alter"...
(press CTRL+C to cancel-- continuing lift automatically in 0.5 seconds...)
info:
info:
info:    Sails              <|
info:    v0.10.4             |\
info:                       /|.\
info:                      / || \
info:                    ,'  |'  \
info:                 .-'.-==|/_--'
info:                 `--'-------'
info:    __---___--___---___--___---___--___
info:  ____---___--___---___--___---___--___-__
info:
info: Server lifted in `/Users/usr0600170/pj/testapp`
info: To see your app, visit http://localhost:1337
info: To shut down Sails, press <CTRL> + C at any time.
debug: --------------------------------------------------------
debug: :: Sat Aug 30 2014 15:12:56 GMT+0900 (JST)
debug: Environment : development
debug: Port        : 1337
debug: --------------------------------------------------------

sailsだけにヨットです。

実際にAPIを呼び出してみる。

この時点で先に作成したUserモデルをCRUDすることができるAPIが動作しています。*2

簡単に確認してみるときは、curlでも良いのですがHTTPieが便利なのでこれを使って試してみます。

POSTをすればCreate

$ http -f POST localhost:1337/user name=hideack
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 145
Content-Type: application/json; charset=utf-8
Date: Sat, 30 Aug 2014 06:15:36 GMT
X-Powered-By: Sails <sailsjs.org>
{
"createdAt": "2014-08-30T06:15:36.825Z",
"id": "54016c080a473e0000da6837",
"name": "hideack",
"updatedAt": "2014-08-30T06:15:36.825Z"
}

POSTした際に得られたidでGETすればRead

$ http GET localhost:1337/user/54016c080a473e0000da6837
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 145
Content-Type: application/json; charset=utf-8
Date: Sat, 30 Aug 2014 06:15:54 GMT
X-Powered-By: Sails <sailsjs.org>
{
"createdAt": "2014-08-30T06:15:36.825Z",
"id": "54016c080a473e0000da6837",
"name": "hideack",
"updatedAt": "2014-08-30T06:15:36.825Z"
}

同様にそのidでDELETEすればDelete

$ http DELETE localhost:1337/user/54016c080a473e0000da6837
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 145
Content-Type: application/json; charset=utf-8
Date: Sat, 30 Aug 2014 06:19:12 GMT
X-Powered-By: Sails <sailsjs.org>
{
"createdAt": "2014-08-30T06:15:36.825Z",
"id": "54016c080a473e0000da6837",
"name": "hideack",
"updatedAt": "2014-08-30T06:15:36.825Z"
}
$ http GET localhost:1337/user/54016c080a473e0000da6837
HTTP/1.1 404 Not Found
Connection: keep-alive
Content-Length: 40
Content-Type: text/html; charset=utf-8
Date: Sat, 30 Aug 2014 06:19:20 GMT
X-Powered-By: Sails <sailsjs.org>
No record found with the specified `id`.

sails + MongoDBの構成でシンプルなCRUDするAPIであれば上記の様な手順でサクッとできるので便利ですね。

ただ、これは序章でここからいろいろ試みていくと多々踏み抜いたので追々書いていこうかと思います。

*1:特に指定をしないとJSONベースで格納されるファイルベースのデータストアが利用されます

*2:MongoDBなので...

sailsでsocket.ioを無効にする方法

最近sailsを使ってAPIサーバを書いて見る試みをしていたりするのですが、sailsでは sails new コマンドでプロジェクトを作成するとデフォルトでsocket.ioが有効になるので、これを無効にする方法を調べたのでメモ。

結論としては .sailsrc というファイルをプロジェクトルートに置いてあげることで制御することが出来ます。

{
"hooks": {
"sockets": false,
"pubsub": false
}
}

TOKYOAMEDAYO – 東京が雨予報ならYo

YoのAPIキーを取ってみたので試しに何か作ってみようということで、
東京地方に雨の予報が出ていたらYoしてくれるアカウントを作ってみました。

TOKYOAMEDAYO というアカウントに利用中の端末からYoしてもらえれば以降、毎朝7時30分に東京地方に雨の予報が出ていれば、このアカウントからYoされます。

スクリプトが行っているのは、livedoor天気の東京地方の本日の予報から「雨」が含まれていたらYoするだけです。今回試してみた様な「雨が降る/降らない」といった2値情報であれば、Yoで気軽にpush出来て良いなと思いました。

とりあえずnpm化したので、Yo API Keyと先のlivedoor天気の予報地域先のIDを渡せば、日本国内の他の地域の雨予報もYoできる。と思います。

NPM

Yo APIを利用するためにAPI Keyを取得する

2周り遅れぐらいでYo APIを利用してみたので、API Keyを取得する際に行った作業のメモ。

Yoをインストールする

tokenの取得にはYoのアカウントが必要なので、iPhoneにYoをインストール。
ここで取得したアカウント名を利用してAPI経由でYoはできないので、その辺りのアカウント名は要注意。*1

トークンの取得

http://yoapi.justyo.co/ へアクセスすると、API Key取得するために必要な情報の入力を求められるので、入力します。

メールを確認する

先のフォームで入力したメールアドレス宛にAPI Keyがメールで送られてくるので確認します。*2
メールは以下の様な文面。

Yo,
Thanks for taking an interest in our API!
We created an API key for your username, (作成されたユーザ名).
Your unique API key is ******************************.
Please keep your key secret.
To learn more about how to use the API visit https://bit.ly/yoapi.
We're excited to see what you create! You can submit what you create
at the Yo API Services Directory - http://submit.justyo.co
Create a Yo button: http://button.justyo.co
Let us know if you have any issues.
Thanks,
Yo API Team
http://justyo.co

http://dev.justyo.co/login.html を開くことで、ダッシュボードからも確認できますね。

APIの呼出

フォローしている全員にYoをするのは、

$ curl --data "api_token=(取得したAPI Key)" http://api.justyo.co/yoall/

特定のユーザにYoするのは、

curl --data "api_token=(取得したAPI Key)&username=(Yoする先のアカウント)" http://api.justyo.co/yo/

の様にします。要はPOSTするだけですね。

Rubyで呼び出すのであれば、

require 'faraday'
conn = Faraday.new(:url => 'http://api.justyo.co') do |builder|
builder.request  :url_encoded
builder.adapter  :net_http
end
conn.post '/yoall/', {:api_token => YO_API_KEY}

といった具合にすればよさそう。

注意としては、APIの利用制限が1分間に1回だけなことくらいでしょうか。いろいろなトリガでYoさせて遊べそうです。

APIの使い方は以下のページでも紹介されています。

*1:例えば、 APIを利用してhogeというアカウントからYoさせたいのにiPhoneアプリhogeというアカウントを取ってしまうと通知できません。

*2:API Keyがメールで飛んでくるのがアレな気もしますが...

Coffee scriptでstep.jsを使う (Hubotで3秒毎に発言させる)

Hubotに3秒おきに発言させるため setTimeout 使う時、あれこれ試してみて以下の様に書いてみた。step.jsを利用してコールバックのネストが深くならなくて多少見やすいかなと思ったのですが、もっと良い書き方があったら是非知りたい。

cronJob = require('cron').CronJob
step = require('step')
new cronJob('0 15 * * *', () ->
step(
() ->
robot.send {room:'sample'}, "3時です"
setTimeout @, 3000
return
() ->
robot.send {room:'sample'}, "お茶の時間です"
setTimeout @, 3000
return
() ->
robot.send {room:'sample'}, "休みましょう"
return
)
).start()

lxc-cpu-usageというツールを作った

VPSの上で複数のLXCを作ってRubyやnode.jsのアプリケーションサーバを動かしたり、DBサーバを動かしたりしているのですが、
稀に「このコンテナでどのくらいCPUが利用されているのだろう?」*1と調べることがあって、その時には

  1. 該当のLXCのinitプロセスを見つける
  2. そこにぶら下がっているプロセスを確認
  3. それらのプロセスがどのくらいCPUを使っているか知る

といったことをやっていたので、その辺りをさっくりできるツールを作ってみました。nodeで作ってます。初npm登録。

lxc-cpu-usage lxc-cpu-usage lxc-cpu-usage このエントリーをはてなブックマークに追加

$ npm install -g lxc-cpu-usage でLXCが稼働しているホストにインストール。

その上でコンテナ名を引数で渡してやるとそのコンテナで動いているプロセスを列挙してCPU利用率を取得してくれます。

例えば、manage001というコンテナが稼働しているのであれば

$ lxc-cpu-usage -n manage001 -v
lxc-cpu-usage - Target LXC:manage001, Target PID:9844
------------------
PID     %CPU
10002    0.01
21669    0.03
(snip)
23340    0.06
23349    0.00
------------------
Processes: 16, CPU usage: 0.16

といった具合で指定したコンテナ内で稼働しているプロセスの一覧とその合計を得ることができます。

誰得感ありますが、コンテナの稼働率見ると、動いているアプリケーションの働いている感を感じられて幸せになれます。

参照

*1:深い意味は無くて、本当に興味でみることが大半...

Hubotを定期的に動かして発言させる

Hubotでcronでスクリプトを定期的に実行させる様に定期的に動かして例えばサービスのパラメータ等を通知させるのはどうするとよいのか調べたのでその際のメモ。

node-cronというnpmパッケージがあるので、これを利用するとcronと同じ書式でスケジューリングすることができます。

以下、REMPチームで利用しているHubotを毎時0分に利用状況を通知させる様にした際の例(を少し変えたもの)です。

cronJob = require('cron').CronJob
module.exports = (robot) ->
new cronJob('0 * * * *', () ->
http = robot.http('http://remp001/****').get()
http (err, res, body) ->
if(!err)
json = JSON.parse body
robot.send {room:'remp'}, "再生回数: #{json.today_play_count}"
robot.send {room:'remp'}, "検索回数: #{json.today_search_count}"
).start()

流れとしてはソースコードそのままですが

  1. node-cronで毎時0分に稼働させるジョブを作成
  2. REMP に用意しているサービスステータス取得のAPIへGETリクエスト
  3. JSONが返ってくるのでparseする
  4. Hubotに指定したチャットルームにそのパラメータを喋らせる

これだけ。

こうするとで定期的にHubotを動かしてチャットルームにREMPの利用状況を1時間毎に通知できる様になりました。いままで、こういった通知をちょっとしたスクリプトを書いてcronに登録して通知させたりしていたのですが徐々にHubotに寄せて行こうと思います。

併せて読みたい

hideack/remp_hubot · GitHub hideack/remp_hubot · GitHub hideack/remp_hubot · GitHub このエントリーをはてなブックマークに追加

npm_lazyを使ってみた – A lazy local cache for npm

CastoSTORYBOARDSでnode.js周りのアプリケーションのデプロイをする際にnpm installされる際、稀にnpm自体が重くなっていたりすることもあるので、ローカルにnpmのキャッシュサーバ的なものを立てられないかと調べていたらお手軽にできるものを見つけたので試してみた。

これを立ちあげた上で、

$ npm install --registry http://(npm_lazyを立ちあげたアドレス指定)

とnpmコマンドを実行する際にregistryを指定することで、npm_lazy を経由してnpmコマンドを実行することができます。

npm_lazy自体は、npmを丸ごとキャッシュする訳ではなく、必要とされたライブラリ毎にキャッシュを行うのでキャッシュ期限内のものが手元にあればそれを返し、無ければnpm本体を参照して新たに特定ディレクトリ下にファイルキャッシュを生成するといったプロキシ的な動き方をしてくれるので、特別にキャッシュ用にDBを用意したりする必要もなく気軽に利用できます。

今回、CastoSTORYBOARDS の場合、VPS配下のLinuxコンテナでそれぞれアプリケーションが動いているので別コンテナにいるmanageサーバと呼んでいるコンテナで npm_lazy を動かしています。

この様な構成で動かすときは npm_lazy の設定を少し変更する必要があるので、設定を修正してそこを参照して稼働させます。

まずは設定用のファイルを出力。

$ npm_lazy --init > ~/npm_lazy.config.js

出力したconfigファイル中の

  // external url to npm_lazy, no trailing /
externalUrl: 'http://10.0.***.***:10080',
  // bind port and host
port: 10080,
host: '10.0.***.***',

辺りを適宜環境に併せて修正した上で npm_lazy を立ちあげます。立ち上げる際に --config を指定して上で修正した設定ファイルを読み込み。

$ npm_lazy --config ~/npm_lazy.config.js

立ちあげた上で、*1

$ npm install --registry http://10.0.***.***:10080/

とすることで、CastoSTORYBOARDSのアプリケーションコンテナからも手元で立ちあげた npm_lazy を参照できる様になります。

*1:実際はこれだけだとアレなので、supervisorで立ちあげて永続化しています

minaで過去にデプロイしたreleaseディレクトリを整理する

2回調べたので記録。

mina でデプロイを行った場合、以下の様にデプロイ先のホストでは管理されます。

lrwxrwxrwx  current -> releases/86/
-rw-rw-r--  last_version
drwxrwxr-x  releases/
drwxrwxr-x  scm/
drwxrwxr-x  shared/
drwxrwxr-x  tmp/

で、この releases ディレクトリには1から始まる数字のディレクトリが掘られて、デプロイする度に順次増えていきます。

単純にデプロイを繰り返しているとこれが増える一方なので、

$ bundle exec mina deploy:cleanup
-----> Cleaning up old releases (keeping 5)
Connection to 10.0.***.*** closed.
Elapsed time: 14.00 seconds

deploy:cleanup というタスクを使うことで直近の5世代分のデプロイ結果を残してそれ以外のものをデプロイ先から削除することができます。