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

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

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

Aerospikeを試す #2 HeartBeat & ReverseProxy

前回から引き続き、Aerospikeネタ。 前回はheartbeatのmesh-seed-address-portに1つのインスタンスを指定しましたが、これが1個だとSPOFになってしまう。 mesh-seed-address-port複数指定できるっぽいので、いくつか指定しておけば良いのだろうけど、それはそれでめんどくさい。 インスタンスの追加・削除時に設定を撒かないといけないし。 というわけで、ReverseProxy挟んだらいけんじゃね?という仮説を検証してみようと思ったので、やってみました。 (どうやるのが正解なんだろうか?)

追記

やっぱりこの方法ダメっぽい。 Cluster Visibilityがずっとfalseだったりとか Repl'n Factor1のまま(2に設定しているのに)だったりする。

やっぱり公式に書いてある通り mesh-seed-address-port複数設定するのが正解なんだろうか。

今日のテーマ

  • Proxyサーバを立てる
  • heartbeatの向き先を変える
  • AMCから確認する

以上

環境

  • Mac OSX 10.10.3
  • Vagrant 1.7.2
  • Aerospike 3.5.14

Proxyサーバを立てる

前回同様vagrantインスタンスを立てる。 今回は超単純なReverseProxyができれば良いのでHAProxyを使います。

Vagarntfileの修正

...
  config.vm.define "proxy" do |server|
    server.vm.box = "proxy-base"
    server.vm.box_url = "http://developer.nrel.gov/downloads/vagrant-boxes/CentOS-6.4-x86_64-v20130731.box"
    server.vm.network :private_network, ip: "192.168.33.200"
  end
...
  • OSは特になんでも良い
  • VMインスタンス間で通信したいので、private_networkでprivate ipを固定しておく

インスタンス起動

vagrant up proxy

HAProxyのインストール&設定

まずはインストール

vagrant ssh proxy
sudo yum install -y haproxy

したら、設定を書き換える

...
frontend  forward_mesh *:3002
    mode tcp
    default_backend             aerospike_mesh

backend aerospike_mesh
    balance     roundrobin
    server  node1 192.168.33.101:3002 check
    server  node2 192.168.33.102:3002 check
...

3002ポートをroundrobinで前回立てたインスタンスに振り分けます

HAProxyを再起動

sudo /etc/init.d/haproxy restart

heartbeatの向き先を変える

以下、node1``node2両方に行います

vagrant ssh node1
sudo vi /etc/aerospike/aerospike.conf
network {
...
        heartbeat {
                mode mesh
                port 3002
                address 192.168.33.101
                mesh-seed-address-port 192.168.33.200 3002

                interval 5
                timeout 10
        }
...
}

mesh-seed-address-portproxyサーバのアドレスに変更する

aerospikeも再起動

sudo /etc/init.d/aerospike restart

AMCから確認してみる

http://localhost:8081

にアクセスします。

img

無事、2台いることが確認できました。 めでたしめでたし(?)

on EC2にするときは、PrivateELB的な物を立てるんだろうか。

けど、なんかAMCを見てるとRepilicationがうまくいって無い気がする... もうちょい調査する。

まとめ

  • heartbeatはreverse proxy通してもいけたけど、なんか怪しい
  • 次回以降
    • プログラムからCRUD
    • UDF

Aerospikeを試す #1 環境構築

最近ちょくちょく見るようになったAerospikeです。 OSSになってからほんとよく見かけますが、あんまり日本語の情報が多くないですね。

WEB+DB PRESS Vol.87

WEB+DB PRESS Vol.87

これのAerospike入門を読んで触発されて触ってみることにしたので、 環境構築からやってみたいなと思います。

今日のテーマ

  • VagrantでAerospike謹製のboxを起動する
  • AMC (Aerospike Management Console)を見てみる
  • 2台のクラスター環境を構築する

環境

VagrantでAerospike謹製のboxを起動する

http://www.aerospike.com/docs/operations/install/vagrant/mac/

ここに書いてある通りなんですけど、今回は2台でクラスタ環境つくりたいので、 vagrant initした後にちょいと書き換えます

vagrant init aerospike/centos-6.5

してVagrantfileをこんな感じに

# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure(2) do |config|
  aerospike_box = "aerospike/centos-6.5"

  config.vm.define "node1" do |server|
    server.vm.box = aerospike_box
    server.vm.network :private_network, ip: "192.168.33.101"
    server.vm.network "forwarded_port", guest: 8081, host: 8081
    server.vm.network "forwarded_port", guest: 3000, host: 3000
  end

  config.vm.define "node2" do |server|
    server.vm.box = aerospike_box
    server.vm.network :private_network, ip: "192.168.33.102"
  end
end
  • ゲスト間通信したいので、private_networkでPrivateIPをふる
  • AMCにアクセスしたいのでnode18081ポートをフォワーディング
  • Aerospikeにホストからアクセスしたいので、node13000ポートをフォワーディング

では起動します。

vagrant up

起動したら、中に入って、ちょっと確認してみます。

$ vagrant ssh node1
$ asmonitor -e info
...
1 hosts in cluster: 127.0.0.1:3000
...

てなのが出てればとりあえずOKかな。

AMCを見てみる

Aerospike Management Consoleを見てみます。

この為に8081ポートでアクセスできるようにしたので、下記をブラウザで開きます。

http://localhost:8081/

なんかかっこいい画面が出てきましたね。

  • Disk Usage
  • RAM Usage
  • Cluster Summary
  • Cluster Throughput
  • etc ...

これは良い感じですね。 マジ運用で使う場合は、踏み台経由のポートフォワーディングとかだけ見れるようにしとけば楽そう。

2台のクラスター環境を構築する

さて、ここからが今回のメインです。 クラスター環境を構築してみます。 (今回はAWSではないですが、最終的にAWSに構築する事を前提にしているので、multicastは使わず、meshでやります)

それぞれ以下のようにconfを書き換え

  • node1
network {
        service {
                address any
                port 3000
                access-address 192.168.33.101
                network-interface-name eth1
        }

        heartbeat {
                mode mesh
                port 3002
                address 192.168.33.101
                mesh-seed-address-port 192.168.33.101 3002

                interval 5
                timeout 10
        }

        fabric {
                port 3001
        }

        info {
                port 3003
        }
}
  • node2
network {
        service {
                address any
                port 3000
                access-address 192.168.33.102
                network-interface-name eth1
        }

        heartbeat {
                mode mesh
                port 3002
                address 192.168.33.102
                mesh-seed-address-port 192.168.33.101 3002

                interval 5
                timeout 10
        }

        fabric {
                port 3001
        }

        info {
                port 3003
        }
}

ポイントは - service - access-adressをVagrantfileで指定したIPアドレスに書き換える - network-interface-nameeth1にする - heartbeat - modemeshにする - addressをVagrantfileで指定したIPアドレスにする - mesh-seed-address-portnode1を見るようにする(2台とも) - ここは、両方のconfに両方のIP書いても大丈夫でした

network-interface-nameを変えないといけないことに気づくのにめっちゃ時間かかった...

[vagrant@localhost ~]$ ifconfig
eth0      Link encap:Ethernet  HWaddr 08:00:27:78:30:4D  
          inet addr:10.0.2.15  Bcast:10.0.2.255  Mask:255.255.255.0
          inet6 addr: fe80::a00:27ff:fe78:304d/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:3323 errors:0 dropped:0 overruns:0 frame:0
          TX packets:2423 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:267581 (261.3 KiB)  TX bytes:579927 (566.3 KiB)

eth1      Link encap:Ethernet  HWaddr 08:00:27:CA:31:8D  
          inet addr:192.168.33.102  Bcast:192.168.33.255  Mask:255.255.255.0
          inet6 addr: fe80::a00:27ff:feca:318d/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:27960681 errors:0 dropped:0 overruns:0 frame:0
          TX packets:27846070 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:5863570470 (5.4 GiB)  TX bytes:5871987364 (5.4 GiB)

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:7999 errors:0 dropped:0 overruns:0 frame:0
          TX packets:7999 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:5992988 (5.7 MiB)  TX bytes:5992988 (5.7 MiB)

両方再起動して確認

$ sudo /etc/init.d/aerospike restart
$ asmonitor -e info

Enter help for commands

2 hosts in cluster: 192.168.33.101:3000,192.168.33.102:3000
===NODES===
2015-07-10 00:29:19.964079
Sorting by IP, in Ascending order: 
ip:port                 Build   Cluster      Cluster   Free   Free   Migrates              Node         Principal   Replicated    Sys
                            .      Size   Visibility   Disk    Mem          .                ID                ID      Objects   Free
                            .         .            .    pct    pct          .                 .                 .            .    Mem
192.168.33.101:3000    3.5.14         2         true     98     98   (1961,1)   BB98262CE270008   BB98D31CA270008      197,932     87
192.168.33.102:3000    3.5.14         2         true     98     95   (2008,1)   BB98D31CA270008   BB98D31CA270008      197,932     86
Number of nodes displayed: 2


 ===NAMESPACE===
Total (unique) objects in cluster for test : 197,932
Note: Total (unique) objects is an under estimate if migrations are in progress.


ip/namespace           Avail   Evicted    Master     Repl     Stop      Used   Used      Used   Used    hwm   hwm
                         Pct   Objects   Objects   Factor   Writes      Disk   Disk       Mem    Mem   Disk   Mem
                           .         .         .        .        .         .      %         .      %      .     .
192.168.33.101/test       98         0   101,982        2    false   84.42 M      2   36.89 M      2     50    60
192.168.33.102/test       98         0    95,950        2    false   84.42 M      2   84.23 M      5     50    60
Number of rows displayed: 2

2 hosts in cluster: 192.168.33.101:3000,192.168.33.102:3000 2台のクラスタになっていることがわかりますね

AMCから見てみる

img

img

ちゃんと2台いることがわかりますね。

まとめ

  • Aerospikeは手軽にクラスタ環境構築できる
  • AMC便利
  • meshで使う場合にmesh-seed-address-portが1個だとSPOFになってしまう気がする
    • どうするんだろうか?
    • ReverseProxyかませてRoundRobinで数台にしたりするんだろうか
  • 次回以降
    • ReverseProxyかませてみる
    • プログラムからCRUD
    • AQL
    • UDF

Rubiniusを試す #1 (Install,Benchmark)

だいぶ久しぶりの投稿です。

ちょいとふと思い立ってやってみたので、メモ。

Macにrbenv(ruby-build)でRubiniusをインストールする。

Rubiniusとは

http://rubini.us/doc/ja/what-is-rubinius/

Rubinius は プログラミング言語 Ruby の実装です。 Rubiniusには、バイトコード仮想マシンRuby構文解析器、バイトコードコンパイラ、世代別ガベージコレクタ、ジャストインタイム (JIT) ネイティブマシンコードコンパイラ、そして、Ruby のコアライブラリと標準ライブラリが含まれています。 Rubinius は Ruby 2.1.0 を目標としています。

とのこと。

https://github.com/rubinius/rubinius/tree/master/kernel/bootstrap 読みやすい(Rubyで実装されている)

昔見た時は「全部Rubyで書いてある」って聞いた気がするんだけどVMC++で書かれてるな。

といっても、当時ちゃんと知らなかったので、何か変わったのか、変わってないのかは、わからない。

一番の特徴はスレッドがGVL(Giant VM Lock)の制約を受けない為、マルチコアが活かせること。

つまり、PumaやSidekiqのようにマルチスレッドで動作する場合に優位性があるのではないかと思う。

環境

インストールマシン

  • OS: Mac OSX 10.10.3
  • CPU: 3.1 GHz Intel Core i7
  • Memory: 16 GB 1867 MHz DDR3

Rubinius

  • version: 2.5.7

Requirements

事前に必要なモノを入れておく

といっても、多分LLVMだけ入れておけば良い。

が、Homebrewで最新のLLVMを入れると3.6が入ってしまう。

Rubinius(rbx-2.5.7)はLLVM3.0-3.5じゃないと怒られてしまうので、

LLVM3.5を入れる(インストールしなくてもバイナリがあれば良い)

もしかしたら

gem install rubinius-melbourne

しといた方がよいのかも

ruby-buildを最新にする

※以下、ruby-buildbrewでinstallしている場合

ruby-buildのFormulaがまだ、2.5.7が入ってるrevisionを見ていないので、強制的にmasterのHEADを向ける

brew upgrade --HEAD ruby-build

Installation

さて、やっとインストールするが、ちょいと環境変数が多くなるので、整理

LDFLAGS=-L{解凍先}/lib \
CPPFLAGS=-I{解凍先}/include \
CC={解凍先}/clang \
CXX={解凍先}/clang++ \
RUBY_CONFIGURE_OPTS="--llvm-config={解凍先}/bin/llvm-config" \
rbenv install -v rbx-2.5.7

こんな感じで多分行ける

試してみる

❯ rbenv versions
  system
  2.0.0-p353
  2.2.1
* 2.2.2
  rbx-2.5.7
rbenv local rbx-2.5.7
❯ ruby -v
rubinius 2.5.7 (2.1.0 d4fdeaad 2015-06-28 3.5.2 JI) [x86_64-darwin14.3.0]

ベンチってみる

簡単なfibonacciの関数書いて試してみる

require 'benchmark'

def fib(n)
  if n == 0
    0
  elsif n == 1
    1
  else
    fib(n-1) + fib(n-2)
  end
end

Benchmark.bmbm do |x|
  x.report("fibonacci") { fib(40) }
end

負荷がかかれば何でも良いので、中身はあまり気にしない

Ruby 2.2.2

❯ ruby fib.rb
Rehearsal ---------------------------------------------
fibonacci  20.300000   0.050000  20.350000 ( 21.297663)
----------------------------------- total: 20.350000sec

                user     system      total        real
fibonacci  20.620000   0.060000  20.680000 ( 21.640249)

Rubinius 2.5.7

❯ ruby fib.rb
Rehearsal ---------------------------------------------
fibonacci   7.426329   0.117764   7.544093 (  7.756742)
------------------------------------ total: 7.544093sec

                user     system      total        real
fibonacci   5.223988   0.064434   5.288422 (  6.797919)

感想

  • パフォーマンス的には以外と差がでた
    • ただ、実行する度に割りと速度にばらつきがある
      • 5s〜10s
    • 原因までは追ってない
  • 厳密にマルチスレッドになると、ちゃんとマルチスレッドプログラミングできてないと危険
  • Rubiniusの運用ノウハウが無いので本番で使うことはきっと無いだろう
    • 日本で使ってるところあるんだろうか?
  • 最近も開発は割りと進んでいるようなので、もうすこしいろいろやってみたい

Goでテストを書く

久しぶりにちゃんとgolangを勉強していこうという事で、ログを残します。

今日のテーマ

  • testingパッケージを使ってテストを書いてみる
  • gomgospelをインストール
  • gospelでテスト書いてみる
  • 独自matcherを書いてみる

環境

構成

$ tree .
.
├── Gomfile
├── src
│   └── model
│       ├── user.go
│       ├── user_gospel_test.go
│       └── user_test.go
└── vendor

testingパッケージを使ってテストを書いてみる

まずは、標準のお作法にしたがってテストを書いてみます。

テスト対象はこんな感じです。

user.go

package model

import (
    "strings"
)

type User struct {
    FirstName string
    LastName string
    Divisions []string
}

func NewUser(name string) *User {
    names := strings.Split(name, " ")
    if len(names) >= 2 {
        return &User{FirstName: names[0], LastName: names[1]}
    } else {
        return &User{FirstName: names[0]}
    }
}

func (user *User) FullName() string{
    return user.FirstName + " " + user.LastName
}

func (user *User) AddDivision(division string) *User{
    user.Divisions = append(user.Divisions, division)
    return user
}

さて、テストを書いてみます。

user_test.go

package model

import (
    "testing"
)

func TestConstructorWithFullname(t *testing.T) {

    user := NewUser("hoge aaa")

    if user.FirstName != "hoge" {
        t.Error("user's first name should be hoge")
    }

    if user.LastName != "aaa" {
        t.Error("user's last name should be aaa")
    }

}

func TestConstructorWithFirstName(t *testing.T) {

    user := NewUser("hoge")

    if user.FirstName != "hoge" {
        t.Error("user's first name should be hoge")
    }

    if user.LastName != "" {
        t.Error("user's last name should be empty")
    }

}

func TestConstructorWithEmptyString(t *testing.T) {

    user := NewUser("")

    if user.FirstName != "" {
        t.Error("user's first name should be empty")
    }
    if user.LastName != "" {
        t.Error("user's last name should be empty")
    }

}

func TestDevision(t *testing.T) {
 
    user := NewUser("hoge aaa")

    if len(user.Divisions) != 0 {
        t.Error("default divisions is empty slice")
    }

}

func TestFullName(t *testing.T) {

    fullname := "hoge aaa"
    user := NewUser(fullname)

    if user.FullName() != fullname {
        t.Errorf("fullname should be %s, but %s", fullname, user.FullName())
    }
}

func TestAddDevision(t *testing.T) {
 
    user := NewUser("hoge aaa")
    division := "test"

    user.AddDivision(division)
    if user.Divisions[0] != division {
        t.Log(user.Divisions)
        t.Errorf("%s division was not added", division)
    }

}
  • xxx_test.goというファイル名にする
  • テスト関数はTestというprefixをつける
  • アサーションは自分でifなどでチェックしてt.Errorでfailさせる

testingにはアサートしてくれる関数がありません。 その辺の考え方的には

Go の Test に対する考え方 - Qiita

あたりを見るとよく分かります。 ふむ。

では、テスト実行してみます。

まずは、GOPATHをカレントディレクトリにしておきます。

$ export GOPATH=`pwd`

したら、実行します。

$ go test -v ./src/...
=== RUN TestConstructorWithFullname
--- PASS: TestConstructorWithFullname (0.00 seconds)
=== RUN TestConstructorWithFirstName
--- PASS: TestConstructorWithFirstName (0.00 seconds)
=== RUN TestConstructorWithEmptyString
--- PASS: TestConstructorWithEmptyString (0.00 seconds)
=== RUN TestDevision
--- PASS: TestDevision (0.00 seconds)
=== RUN TestFullName
--- PASS: TestFullName (0.00 seconds)
=== RUN TestAddDevision
--- PASS: TestAddDevision (0.00 seconds)
PASS
ok      model   0.010s

全部通りました。

gomgospelをインストール

依存パッケージはやっぱり設定ファイルとかで管理したいので、今回はgomを使ってみます。 (gondlerの方がスタンダードなのかな?依存パッケージのバージョンとか、あまり管理しないのかな?)

まずはgomをインストール

ここではgo getつかいます。

$ go get github.com/mattn/gom

Gomfileを書く

gom 'github.com/r7kamura/gospel', :commit => 'd575dd12c2eb84612ae5c84fab56ccb4ce156a1e'

Rubyっぽい... mattn/gom · GitHub とか見てもほぼGemfileですね。 tagも使えるし、groupも使える。

じゃあ、こんなのもいけるのか??

gom 'github.com/r7kamura/gospel', commit: 'd575dd12c2eb84612ae5c84fab56ccb4ce156a1e'

$ gom install
gom:  Syntax Error at line 1

怒られました... orz

気を取り直して、Gomfileを元に戻してインストールしなおします。

$ gom install
downloading github.com/r7kamura/gospel

無事完了。

gospelでテスト書いてみる

では、使い方を参考に書いてみます。

user_gospel_test.go

package model

import (
    . "github.com/r7kamura/gospel"
    "testing"
)

func TestUser(t *testing.T) {

    Describe(t, "NewUser", func() {

        Context("フルネーム(hoge aaa)を指定した場合", func() {
            user := NewUser("hoge aaa")

            It("FirstName should be hoge", func() {
                Expect(user.FirstName).To(Equal, "hoge")
            })

            It("LastName should be aaa", func() {
                Expect(user.LastName).To(Equal, "aaa")
            })

        })

        Context("空文字を渡した場合", func() {
            user := NewUser("")

            It("FirstName should be empty string", func() {
                Expect(user.FirstName).To(Equal, "")
            })

            It("LastName should be empty string", func() {
                Expect(user.LastName).To(Equal, "")
            })

        })

    })
    Describe(t, "Divisions", func() {
        user := NewUser("")

        It("default divisions is empty slice", func() {
            Expect(len(user.Divisions)).To(Equal, 0)
        })

    })

}

Rspec風に書けてBDDな感じで書きやすいですね。

実行する時はgom exec使います。 bundle execみたいな。

$ gom exec go test -v ./src/...
=== RUN TestUser
NewUser
  フルネーム(hoge aaa)を指定した場合
    FirstName should be hoge
    LastName should be aaa
  空文字を渡した場合
    FirstName should be empty string
    LastName should be empty string
Divisions
  default divisions is empty slice
PASS
ok      model   0.011s

※ 急に日本語で書いたのは気にしない ※ だいぶテストケースが減ってるのも気にしない

結果も見やすくて良い感じです。 (ちなみに、Describeをネストするとエラーになりました)

独自matcherを書いてみる

スライスとか配列のlengthをチェックするmatcherとかあっても良いかなぁという気がしていて、 この辺とか見ていたら、 自分で書けそうなので、書いてみました。

package model

import (
    . "github.com/r7kamura/gospel"
    "testing"
    "fmt"
    "reflect"
)

func EqualLength(values ...interface{}) (failureMessage string) {
    actualValue := reflect.ValueOf(values[0])

    if actualValue.Kind() != reflect.Slice && actualValue.Kind() != reflect.Array {
        failureMessage = fmt.Sprintf("`%v` is not slice or array", values[0])
        return
    }

    if actualValue.Len() != values[1] {
        failureMessage = fmt.Sprintf("Expected `%v` length `%v` to equal `%v`", values[0], actualValue.Len(), values[1])
    }
    return
}

func TestUser(t *testing.T) {

    Describe(t, "Divisions", func() {
        user := NewUser("")

        It("default divisions is empty slice", func() {
            Expect(user.Divisions).To(EqualLength, 0)
        })

    })

}

reflectパッケージ使って、TypeがSliceかArrayならlengthチェックするみたいな事をしています。

ためしに、

It("length", func() {
    Expect([]string{"hoge"}).To(EqualLength, 0)
})
It("length", func() {
    Expect([1]string{"hoge"}).To(EqualLength, 0)
})
It("length", func() {
    Expect(100).To(EqualLength, 0)
})

こんなんでfailさせてみます。

=== RUN TestUser
Divisions
  length
  Expected `[hoge]` length `1` to equal `0`
  /Users/natsuki/workspace/go-samples/test/unit-test/src/model/user_gospel_test.go:63
    62.    It("length", func() {
    63.      Expect([]string{"hoge"}).To(EqualLength, 0)
    64.    })
  length
  Expected `[hoge]` length `1` to equal `0`
  /Users/natsuki/workspace/go-samples/test/unit-test/src/model/user_gospel_test.go:66
    65.    It("length", func() {
    66.      Expect([1]string{"hoge"}).To(EqualLength, 0)
    67.    })
  length
  `100` is not slice or array
  /Users/natsuki/workspace/go-samples/test/unit-test/src/model/user_gospel_test.go:69
    68.    It("length", func() {
    69.      Expect(100).To(EqualLength, 0)
    70.    })
--- FAIL: TestUser (0.00 seconds)
FAIL
exit status 1
FAIL    model   0.009s
gom:  exit status 1

おぉ、悪くないかも。

まとめ

  • golangはテストを大事にしている
  • テストコードとディレクトリを分けない文化はめずらしい気がした
    • これもテスト重要っていう文化故になのか
  • GomfileはRubyっぽいだけでRubyではない(?)
  • go testベンチマークも取れるし、カバレッジも取れるっぽい
    • go test -hでいろいろ出てくる
    • -coverオプション付けるとなんか色々足りないって('go get`しろって)怒られる
  • gospelのMatcherは結構簡単に書ける

Scala勉強日誌 - Akka その3 application.conf使ってみる

さてさて、続けてその3です。

前回からの続きで、AkkaActorの話です。

今日のテーマ

  • configファイルを使ってみる

環境

  • MacOSX 10.9.4
  • Scala 2.11.1
  • sbt 0.13.5
  • akka-actor 2.3.4
続きを読む

Scala勉強日誌 - Akka その2 Future

さて、実は前回の(その1)からかなり時間が経っているのですが、また再開していきたいと思います。

今日のテーマ

  • 非同期でWebAPIを呼ぶ
  • 結果を受け取って処理する

環境

  • MacOSX 10.9.4
  • Scala 2.11.1
  • sbt 0.13.5
  • akka-actor 2.3.4
続きを読む

Faraday パラメータのエンコードを差し替えてみる

前回に引き続きFaradayネタです。

Faradayはquery_stringをescapeする時に半角スペースを+にescapeするようなので %20にescapeするようにERB::Util#url_encodeでescapeするようにしてみます。

参考 : http://stackoverflow.com/questions/21283414/how-to-avoid-faraday-request-to-encode-get-parameters

まずは普通に使って確認

前回同様にGETリクエストをしてみます。

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

res = client.get "/test", { "hoge hoge" => "foo bar" }
I, [2014-05-23T22:49:12.358290 #22807]  INFO -- : get http://localhost:4567/test?hoge+hoge=foo+bar
D, [2014-05-23T22:49:12.358373 #22807] DEBUG -- request: User-Agent: "Faraday v0.9.0"
I, [2014-05-23T22:49:12.363857 #22807]  INFO -- Status: 200
D, [2014-05-23T22:49:12.363918 #22807] DEBUG -- response: content-type: "application/json"
content-length: "23"
x-content-type-options: "nosniff"
server: "WEBrick/1.3.1 (Ruby/2.1.1/2014-02-24)"
date: "Fri, 23 May 2014 13:49:12 GMT"
connection: "close"

結果を見てみると hoge+hoge=foo+bar になってますね。

この辺を見ると、XXEncoderっていう子たちがいます。 これ見ると、

  • escape
  • unescape
  • encode
  • decode

っていうクラスメソッドを定義したクラス作れば使えそうな感じがします。 今回はとりあえず、encodeだけ実装したクラスを作ってやってみます。

class ParamsEncoder
  def self.encode(params)
    params.map {|(key, value)|
      "#{ERB::Util.url_encode(key)}=#{ERB::Util.url_encode(value)}"
    }.join('&')
  end

  def self.to_query
  end
end

client = Faraday.new(:url => "http://localhost:4567") do |faraday|
  faraday.response :logger
  faraday.adapter  Faraday.default_adapter
  faraday.options.params_encoder = ParamsEncoder
end

res = client.get "/test", { "hoge hoge" => "foo bar", "bar bar" => "hoge foo" }
I, [2014-05-23T22:56:42.243765 #23036]  INFO -- : get http://localhost:4567/test?hoge%20hoge=foo%20bar&bar%20bar=hoge%20foo
D, [2014-05-23T22:56:42.243848 #23036] DEBUG -- request: User-Agent: "Faraday v0.9.0"
I, [2014-05-23T22:56:42.248266 #23036]  INFO -- Status: 200
D, [2014-05-23T22:56:42.248340 #23036] DEBUG -- response: content-type: "application/json"
content-length: "44"
x-content-type-options: "nosniff"
server: "WEBrick/1.3.1 (Ruby/2.1.1/2014-02-24)"
date: "Fri, 23 May 2014 13:56:42 GMT"
connection: "close"

結果をみると hoge%20hoge=foo%20bar&bar%20bar=hoge%20foo になりました。

めでたしめでたし。