読者です 読者をやめる 読者になる 読者になる

成らぬは人の為さぬなりけり

エンジニアライフをエンジョイする為のブログ

Faradayを触ってみた

実はこれが、2014年最初のエントリー。サボりすぎ。

WebAPIを呼ぶスクリプト書くのにFaradayを使ってみたので、使い方などをメモっておこうと思います。 Faraday自体は前から知ってたんですが、リダイレクトとかめんどくさい事考えなくて良い場合に、 特にHTTPクライアントのライブラリ入れて使うより、Net::HTTPで十分じゃない?と思って使った事ありませんでした。 なんとなーく、Ruby書きたくなって、使ったことないgemを使ってみたかったので、使ってみた次第です。

ちなみに、読み方は ふぁらでい なんでしょうかね?

環境

使用するライブラリのバージョン

gem 'faraday', '~> 0.9.0'
gem 'sinatra', '~> 1.4.5'
gem 'sinatra-contrib', '~> 1.4.2'

今回のコードはこちら

Github yagince/faraday-sample

今回の流れ

  • まずはWebAPI作っとく
  • Faradayの基本的な使い方
  • Middlewareを使ってみる
  • Middlewareを作ってみる

まずはWebAPI作っとく

とりあえず、簡単にJSONでも返してくれるAPIが欲しいので、何でも良いのですが、 サーバサイドのデバッグもしてみたいので、自前で用意してみます。 といってもすっごい簡単な物で良いので、Sinatraさんにご協力頂き、さくっと作ります。

require "sinatra"
require "sinatra/reloader" if development?
require "sinatra/json"

get "/test" do
  json name: :hoge, age: 100
end

post "/test" do
  json params
end

post "/test.json" do
  json JSON.parse(request.body.read)
end

ポイント

  • reloder入れとくと、コード変えた時に再起動の必要なくなります。(Sinatra::Contrib)
  • 単純にrequire "json" して { key: value }.to_json 返しても良いんですが、それだけだとresponseのcontent-typeがtext/htmlになるので、Sinatra::Contribsinatra/jsonを使います
    • provides: :jsonすれば良いのですが、めんどくさいので

Faradayの基本的な使い方

先程作ったSinatraアプリは起動しておきます。

GETしてみる

require "faraday"
require "json"
require "pp"

res = Faraday.new(:url => "http://localhost:4567").get("/test")
body = JSON.parse(res.body)
pp body

※status:200以外の場合の事はまったく考えてません

とまぁ、こんな感じでgetする訳なんですが、これだと、Net::HTTPでもたいして変わらないんですよね。

require "net/http"
require "uri"
require "json"
require "pp"

res = Net::HTTP.get URI("http://localhost:4567/test")
body = JSON.parse res
pp body

URLを分割してなくて良い分、こちらの方が楽かも?。 ただ、Faradayの良い所はこれだけじゃわからないので、先に進みます。

POSTしてみる

Faraday

client = Faraday.new(:url => "http://localhost:4567")
res = client.post "/test", { hoge: 100, foo: :bar }
body = JSON.parse res.body
pp body

Net::HTTP

res = Net::HTTP.post_form URI("http://localhost:4567/test"), { hoge: 100, foo: :bar }
body = JSON.parse res.body
pp body

と、まぁ、ここまでもあまりメリット感じませんね。

Content-Type変えてみる

jsonをpostしてみます。

Faraday

client = Faraday.new(:url => "http://localhost:4567")
res = client.post do |req|
  req.url '/test.json'
  req.headers['Content-Type'] = 'application/json'
  req.body = { hoge: 100, foo: :bar }.to_json
end
body = JSON.parse res.body
pp body

Net::HTTP

uri = URI("http://localhost:4567/test")
req = Net::HTTP::Post.new uri, initheader = {'Content-Type' =>'application/json'}
req.body = { hoge: 100, foo: :bar }.to_json
res = Net::HTTP.start(uri.host, uri.port, use_ssl: false) do |http|
  http.request req
end
body = JSON.parse res.body
pp body

少し楽になりましたね。 実はもっと楽になります。

Middlewareを使ってみる

FaradayにはMiddlewareという仕組みがあります。(Rackと同じような仕組みになってる) こいつを使う事で、拡張できるようになってます。 ここには、一番最初に書いてあるんですけどね。

request/responseをログ出力してみる

client = Faraday.new(:url => "http://localhost:4567") do |faraday|
  faraday.response :logger
  faraday.adapter  Faraday.default_adapter
end
res = client.get "/test"
body = JSON.parse res.body
pp body

ちなみにmiddleware使う場合、adapterは必ず指定しないとエラーになるっぽい??です。 ソースまで追ってないですが。

出力結果

I, [2014-05-10T23:45:34.114079 #12029]  INFO -- : get http://localhost:4567/test
D, [2014-05-10T23:45:34.114147 #12029] DEBUG -- request: User-Agent: "Faraday v0.9.0"
I, [2014-05-10T23:45:34.117054 #12029]  INFO -- Status: 200
D, [2014-05-10T23:45:34.117099 #12029] DEBUG -- response: content-type: "application/json"
content-length: "25"
x-content-type-options: "nosniff"
server: "WEBrick/1.3.1 (Ruby/2.1.1/2014-02-24)"
date: "Sat, 10 May 2014 14:45:34 GMT"
connection: "close"
{"name"=>"hoge", "age"=>100}

こんな感じで出力されます。

FaradayMiddlewre

faraday_middlewareっていうgemが用意されていて、使えるmiddlewareが既に結構用意されています。 これを使うと、さっきのjsonパターンは数倍楽になります。 bundler使っている場合は、Gemfileに以下を追加してインストールしておきます。

gem 'faraday_middleware', '~> 0.9.1'

使ってみます。

require "faraday"
require "faraday_middleware"
require "json"
require "pp"

client = Faraday.new(:url => "http://localhost:4567") do |faraday|
  faraday.request :json # ここ
  faraday.adapter Faraday.default_adapter
end
res = client.post "/test.json", { hoge: 100, foo: :bar }
body = JSON.parse res.body
pp body

middlewareでrequestのmiddlewareとして:jsonを入れとくだけで

  • postする時のbodyをjsonエンコード
  • Content-Typeヘッダをapplication/jsonにする

っていう事をやってくれます。

この辺でハマったこと

こんな事をするとContent-Typeapplication/jsonになりません。

client = Faraday.new(:url => "http://localhost:4567") do |faraday|
  faraday.request :url_encoded
  faraday.request :json
  faraday.adapter Faraday.default_adapter
end

この辺で、ちゃんとソース読んだ方が良さそうなので、見てみます。 まずは、

https://github.com/lostisland/faraday/blob/master/lib/faraday/rack_builder.rb#L87

この辺。

requestにmiddlewareの設定を渡すと use_symbol -> use という流れでここに来ます。 @handlersというインスタンス変数にpushしていってますね。

次は、

https://github.com/lostisland/faraday/blob/master/lib/faraday/rack_builder.rb#L162

この辺。

app -> to_app という流れでここに来ると、先程の@handlersreverseしてinjectしてます。 その中でbuildしているので、Hanlderbuildを見ると、

https://github.com/lostisland/faraday/blob/master/lib/faraday/rack_builder.rb#L47

klassをnewして(引数にapp渡してる)返してますね。

つまり、最初useした時は

という順番で追加しているので、これをreverseしてbuildすると

  • jsonをnew
    • 大本のapp(to_appに渡してるlambda)を持ってる
  • url_endoeをnew
    • jsonのappを持ってる

https://github.com/lostisland/faraday/blob/master/lib/faraday/rack_builder.rb#L150

結果、↑ここの@appはurl_encodeのオブジェクトになる為、

  1. url_encode
  2. json
  3. 大本のapp

という順番で実行されるわけですね。

Middlewareを作ってみる

というわけで、とりあえず簡単なMiddlewareを作ってみたいと思います。 デフォルトのloggerだとbodyを出力してくれないので、bodyを出力するmiddlewareを書いてみます。

require "faraday"
require "json"
require 'logger'

class BodyDumper
  def initialize app
    @app = app
    @logger = Logger.new(STDOUT)
  end

  def call env
    dump_body("request", env)
    @app.call(env).on_complete do
      dump_body("response", env)
    end
  end

  private
  def dump_body phase, env
    @logger.info("#{phase} : #{env.body}") if env.body
  end
end

client = Faraday.new(:url => "http://localhost:4567") do |faraday|
  faraday.request  :url_encoded
  faraday.use BodyDumper
  faraday.adapter  Faraday.default_adapter
end
res = client.post "/test", { hoge: 100, foo: :bar }

まとめ

FaradayはMiddlewareなどでの拡張性(柔軟性)がうりな気がします。

(どっかのWebAPIを呼ぶクライアントを作ったりとか向いてるかも)

adapterも結構用意されていて、em_httpもあるみたいですね。

あとはfaraday_middlewareoatuh2のmiddlewareがあって、 Authorizationヘッダー埋め込んでくれたりとか、地味に便利でした。

もう少し踏み込んで、拡張して使ってみたいと思います。

次回は気が向いたら、parameterのescape周りを修正してみたい、、、かも。 デフォルトだと半角スペースが+にescapeされるので。

VMWareからVirtualBox+Vagrantに移行する時の話

普段、VMWareFusionを使って開発してるのですが、 特にVMWareに依存していないので 「Vagrant+VirtualBoxにしてみようかな」と思い立ち、 年末にちょこちょこ作業したのでやったことをメモしておきます。

PackerやVeeweeを使って1からVagrantのBoxを作る方法は ググれば大量に出てきますが、 既存のvmdkから作る方法はあまり情報がなかったです。 なのでそんなに需要はないのかもしれませんね、、、

環境

VirtualBoxのインストール

https://www.virtualbox.org/wiki/Downloads

今回は4.3.6を選択しました。

vmdkをvdiに変換する

VirtualBoxを入れるとVBoxManageコマンドが使えるようになるので、 以下の感じのコマンドでvmdkvdiに変換します。

VBoxManage clonehd --format VDI XXXX.vmwarevm/仮想ディスク.vmdk output.vdi

※vmdkのパスは各自の環境のパスを設定

output.vdiの名前はなんでも良いです

とりあえず起動してみる

  • VirtualBoxを起動(GUI
  • 先程作成したoutput.vdiを起動ディスクにして新規作成
  • 起動できる事が確認できたら、とりあえずシャットダウン

ネットワーク設定

VirtualBox上のゲストOSの設定

Settings > Network

Adapter1 => NAT

Adapter 1Attached toNATにしておきます。

Terminal等からsshでログインして作業したい場合は、advancedでPortフォワーディングの設定をしておきます。 host:2222 -> guest:22

ゲストOS内の設定

eth0 (NAT)

/etc/sysconfig/network-scripts/ifcfg-eth0 こいつを変更しておきます

DEVICE=eth0
ONBOOT=yes
HWADDR=08:00:27:8F:56:EE
TYPE=Ethernet
BOOTPROTO=dhcp
NM_CONTROLLED=yes
UUID=6d8d1ced-b056-42cd-b7a3-195eff064f1d

ポイントは

  • ONBOOTyes
  • HWADDR(MACアドレス)はVirtualBox上のゲストOSの設定のアダプタの所(Advanced)に表示されている値を指定

networkをrestart

$ sudo service network restart
$ ifconfig

eth0が上がっている事を確認する

あとはping等で外部にアクセスできる事を確認しておきます。 (yumでインストールしたりするので)

ローカルにvagrantをインストールする

http://www.vagrantup.com/downloads.html

現時点での最新版1.4.1を入れる事をおすすめします。

あとは以下の通りにやる

http://futurismo.biz/archives/1678

※元のイメージのサイズが大きいと、boxを作ったり、box addしたり、結構時間がかかります。

上記手順の注意点

Rubyのバージョン

現在最新のchefはRuby1.9系以上じゃないと入らないので、Rubyのバージョンを上げておかないといけないです。 rpmを作るか、自分でビルドしてPATH通すか、rbenvもしくはRVM入れるか、方法はなんでも良いかと思います。

Vagrantfileの編集

名前解決がめっちゃ遅い

http://shibayu36.hatenablog.com/entry/2013/08/12/090545

ということらしいので、以下を入れておきます

Vagrant.configure("2") do |config|
  config.vm.provider :virtualbox do |vb|
    ...
    vb.customize ["modifyvm", :id, "--natdnsproxy1", "off"]
    vb.customize ["modifyvm", :id, "--natdnshostresolver1", "off"]
  end
end

CLI for Redmine - climine (クリミネ) を書いてみた

Rubyを書きたくなったので、gemを作りました。

普段、タスク管理はRedmineを使ってるのですが、 わざわざRedmine見る為にブラウザにCommand+TABするのがめんどくさくて さくっと、コマンド打ってチケット確認したいなぁ、という思いがあったので、 ひとまず、チケット見れる所まで書いてみました。

※中身は単純にRedmineAPIを叩いているだけです。

http://www.redmine.org/projects/redmine/wiki/Rest_api

https://github.com/yagince/climine

https://rubygems.org/gems/climine

使い方

インストール

bundlerを使う場合

Gemfile

gem 'climine'

で、インストール

$ bundle install

まずはinitします

$ climine init -u [RedmineURL] -k [APIKey]

これでローカルにconfig.ymlが作成されます。

チケットを参照

$ climine issue get [TICKET_NO]

<<機能>> [新規] #225 Test
----------------------------------------------------------------------------------------
[PROJECT ] Test
[created ] 2013-11-10 01:06:34
[Author  ] natsuki yagi
[Assigned] natsuki yagi

Hogehoge
----------------------------------------------------------------------------------------

その他の使い方は、 yagince/climine · GitHub こちらを参照してください。

現状の機能

現状、以下の機能があります。

  • チケット
    • 一覧表示
    • 詳細表示
    • 登録
  • プロジェクト
    • 一覧表示
  • プロジェクトメンバー
    • 一覧表示
  • トラッカー
    • 一覧表示
  • チケットステータス
    • 一覧表示
  • ユーザー
    • 一覧表示(検索可能)

特徴

今回やりたかった事で、入れた特徴的(?)な機能が以下の2つです。

  • テンプレートの切替(ERB)
  • 週指定の検索

テンプレート切替

自由なフォーマットでチケットの一覧なり内容なりを表示したかったので、 各自が自分で作ったERBテンプレートを使って、表示できるようしました。

$ climine issue get 100 -t template/issue.erb

のような感じで、テンプレートのpathを指定すれば使えます。

例えば、textile形式で一覧を出力したい場合、こんな感じになります。 templates/issues.erb

<% res.issues.each{|issue| -%>
* <%= issue.tracker.name %> [<%= issue.status.name %>] #<%= issue.id %> <%= issue.subject %>
<% } -%>

これを使って以下を実行

$ climine issue get -t templates/issues.erb
* 機能 [新規] #225 Test
* バグ [新規] #224 hoge
* バグ [新規] #223 hoge
* バグ [新規] #222 Test

週指定の検索

先週なにやったんだっけ? みたいな事をまとめるために、 自分が担当で一週間以内に更新したチケットを一覧で知りたい みたいな事がやりたい時があったので、週指定で検索できるようにしました。

自分が担当で一週間以内に更新したチケットを一覧で知りたいを行うためには

$ climine user -n yagi

でユーザーIDを調べ

$ climine issue -w 1 -u [USER_ID]

を実行します。 ユーザーIDは一回調べてしまえば変わらないので、上記のコマンドをaliasにでもしてしまえば、 毎週すぐに調べる事ができます。

課題

helpがちゃんと表示されない

今回はThorを使ったのですが、

$ climine issue new

みたいに取得・登録・更新などのコマンドが必要な為、全てサブコマンドにしました。 ただ、サブコマンド指定しなかったらデフォルトで一覧表示して欲しいと思っていて、 default_commandを使ってサブコマンドなし時のコマンドを指定しているのですが、 そうすると、今度はhelpがちゃんと表示されないのです。

$ climine help member
Usage:
  climine member [COMMAND]

subcommands for member
$ climine member help
Usage:
  climine help [COMMAND]

Describe subcommands or one specific subcommand
$ climine member help get
Usage:
  climine help [COMMAND]

Describe subcommands or one specific subcommand

default_command使わないとちゃんと見れるので、使い方がわるいのか、、、、 まだ、ちゃんと調べてないので、次のバージョンで直したいと思います。

今日はこのへんで。。。

Chef-solo+knife-solo+Vagrantでサーバ構築を自動化してみる - その4 redis

今回は、githubのcookbookを使って、redisをインストールしてみます。

※環境は前回と同じです。

今回の流れ

  1. Berksfileでgithubのリポジトリを指定する
  2. サーバに適用してみる
  3. forkして修正する
  4. サーバに適用してみる

超シンプル。

続きを読む

Chef-solo+knife-solo+Vagrantでサーバ構築を自動化してみる - その3 nginx

さて、今日は前回に引き続き nginxのインストール&設定をやってみたいと思います。

※環境は前回と同様です。

今回の流れ

  1. Berksfileをいじる
  2. jsonファイルに設定を書く
  3. サーバに適用してみる
  4. 自前のconfを用意する
  5. サーバに適用してみる

今日もシンプル。

続きを読む

Chef-solo+knife-solo+Vagrantでサーバ構築を自動化してみる - その2 sudoers

さて、前回に引き続き、
今日はsudoersをいじってみようと思います。

※環境は前回と同じです。

今回までのコードは、以下においてあります。

yagince/chef-sample · GitHub

今回の流れ

  1. Berksfileをいじる
  2. berks install
  3. jsonファイルで設定を書く
  4. サーバに適用する

今日はシンプル。

続きを読む

Chef-solo+knife-solo+Vagrantでサーバ構築を自動化してみる - その1 ユーザー追加

本日(8/24)、LL祭りで、
Inftastructure as LLというセッションを見てきました。
最近、Chef-solo+knife-soloをいろいろいじっていたので、今日は一番興味深いセッションでした。

いい機会なので、最近自分がChef+Vagrantでやった事を、メモしていきたいと思います。

続きを読む