webネタ

Webエンジニアが業務に関係することをメモしていく

Part2 : ErlangVMで動くRuby風の関数型言語Elixir(エリクサー) : 基礎編

Part1はこちら

インターフェース

defprotocolでinterfaceのようなものが定義できる

defprotocol Checker do
  def ok?(data)
end

defimplでinterfaceの実装を定義できる

defimpl Checker, for: List do
  def ok?([]), do: "ng"
  def ok?([h | _]) when h == 1, do: "ok"
  def ok?([2 | _]), do: "ok2"
end
defimpl Checker, for: Number do
  def ok?(1), do: "ok"
  def ok?(_), do: "ng"
end
Checker.ok?([1,2,3]) # ok
Checker.ok?([2,3,4]) # ok2
Checker.ok?([3,4,5]) # ng
Checker.ok?(1) # ok
Checker.ok?(2) # ng

関数をキャプチャ

定義済みの関数を、別の関数に渡したいとき等に。

f = &Integer.odd?/1
Enum.map([1,2,3,4,5], f) # [true, false, true, false, true]

Erlangの関数をコール

:(コロン)をつけるとErlangのモジュールが呼べる。

:timer.sleep 1000 # 1秒sleep

プロセス生成

spawnで生成。receiveで外から値を受け取れる。

defmodule Process do
  def from do
    receive do
      i -> 
        IO.inspect i * i
    after
      100 ->
        IO.inspect "timeout.."
    end
  end
end
p = spawn Process, :from, []
p <- 2 # プロセスに値を送るとreceiveで受け取れる。

Stream

scalaにもある無限のリストを作るコレクション

IO.inspect Stream.map(1..1000000000, &(&1 * &1)) |> Enum.take(5) # [1, 4, 9, 16, 25]

File

ファイルをread。

case File.read("/etc/hosts") do
  { :ok, body } -> IO.inspect body
  { :error, reason } -> IO.inspect reason
end

!を付けると例外を飛ばすようになる。rubyっぽい...

body = File.read!("/etc/hosts")
IO.inspect body

例外をcatchしてみる

try do
  File.read! "/etc/hogeeeee"
rescue
  File.Error -> IO.inspect "catche!"
end

様々なファイル操作の関数がある

File.rm_rf! "/tmp/fuga"
File.mkdir! "/tmp/fuga"
File.write! "/tmp/fuga/elixir.txt", "elixir!"
IO.puts File.read! "/tmp/fuga/elixir.txt"
IO.inspect File.ls! "/tmp/fuga"
File.rm! "/tmp/fuga/elixir.txt"

pmap

Elixir関係ないけど、便利なErlangの関数だったので。

1ループを1プロセスで実行するmap。ものすごい並列感...

defmodule Par do
  def exec(i) do
    :timer.sleep 1000
    i * i
  end
end
:rpc.pmap {Par, :exec}, [], [1,2,3,4,5,6,7,8,9] # 1sかかる
Enum.map [1,2,3,4,5,6,7,8,9], &Par.exec/1 # 9sかかる

公式サイトを翻訳したほうが早いんじゃないかと思えてきた...

ErlangVMで動くRuby風の関数型言語Elixir(エリクサー) : 基礎編

Erlangの奇妙な文法を覚えることなく、Erlangの機能が使えるエリクサー。

Ruby風の文法なのでRubyistには馴染みの関数などが使えるが、以下のことを頭にいれておくべし。

楽天Technologyカンファレンスでセッションがあったり、Erlangを簡単に書ける言語としてこれから普及していくのではないでしょうか!

インストール

brew install elixir

iex

iex

irbのようにiexで、試し書きが簡単にできます。(iexはerlのラッパー)

基本文法

四則演算

1 + 1    # 2
1 - 1    # 0
1 * 1    # 1
1 / 1    # 1.0
div 1, 1 # 1
rem 2, 4 # 2
# 2 % 4 not work

"a"             # string
1               # integer
1.2             # float
:id             # atom
[1,2,3]         # list
{1,2,3}         # tuple
[a: "a", b: 2]  # keyword

文字列

s = "str"
"[" <> s <> "]"          # [str]
"[#{s}]"                 # [str]
String.reverse "str"     # rts
String.slice "str", 1, 2 # tr
String.first "str"       # f
String.last "str"        # r
String.at "str", 1       # t
String.graphemes("str") # ["s", "t", "r"]

変数

v = "v"
# v = "vv" 不変なので代入不可

デバッグ

IO.inspect [1,2,3]
IO.puts "foo"  # 文字列のみ(改行コード付き)
IO.write "foo" # 文字列のみ(改行コードなし) 

コレクション

[head | tail] = [1,2,3]
head # 1
tail # [2,3

Enum.map [1,2,3], fn(i) -> i * i end   # [1,4,9]
Enum.map [1,2,3], &(&1 * &1)           # [1,4,9]

Enum.filter [1,2,3], &(Integer.odd?(&1)) # [1,3]
Enum.find [1,2,3], &(&1 == 1)    # 1
Enum.all? [1,3], &(Integer.odd?(&1))     # true  [scala = forall]
Enum.any? [1,3], &(Integer.odd?(&1))     # true  [scala = exists]
Enum.at [1,2,3], 1               # 2
Enum.max [1,2,3]                 # 3
Enum.join [1,2,3], "-"           # 1-2-3

f = fn
  i when rem(i, 2) == 0 -> [i]
  _ -> []
end
Enum.flat_map(1..10, f)              # [2, 4, 6, 8, 10]
Enum.map(1..10, f) |> List.flatten # [2, 4, 6, 8, 10]

パイプ

[1,2,3,4,5] |> Enum.filter(&(rem(&1,2) != 0)) |> Enum.map(&(&1 * &1)) # [1,9,25]

"str" |> String.graphemes |> Enum.map(&("-#{&1}-")) |> Enum.join # "-s--t--r-"

気軽に繋げて書くことができる。

条件

if

if true do
  "ok" # ok
else
  "ng"
end

# 簡易版
if true, do: "ok", else: "ng" # ok
if true, do: 1           # 1
if false, do: 1, else: 2 # 2

unless

unless false, do: "unless"

case

case 1 do
  1 -> p "ok" # ok
  _ -> p "ng"
end

case [1,2,3] do
  [1 | _] -> p "ok" # ok
  _ -> p "ng"
end

case {1,2,3} do
  {1,2,3} -> p "ok" # ok
  _ -> p "ng"
end

case [1,2,3] do
  [_ | [2,3]] -> p "ok"
  _ -> p "ng"
end

case [1] do
  [h | []] -> p h # 1
  _ -> p "ng"
end

a = 1
case true do
  ^a -> p "not match"
  _ -> p "match"
end

関数

基本

defmodule Math do
  def sum(a, b) do
    a + b
  end
end
Math.sum(1,2) # 3 defmoduleの中でないとdefは定義できない
Math.sum 1,2 # rubyのように()省略可

# ネスト
defmodule Math do
  defmodule Foo do
    def sum(a, b) do
      a + b
    end
  end
end
Math.Foo.sum 1,2 # 3

# 短く
defmodule Math do
  def sum(a, b), do: a + b
end
Math.sum 1, 2 # 3

匿名関数

f = fn(x, y) -> x + y end
f.(1,2) # 3

# 短く
f = &(&1 + &2)
f.(1,2) # 3

高階関数

ff = fn(f) -> f.(2,3) + f.(4,5) end
ff.(f) # 14

ガード

f2 = fn
  x when x < 1 -> "ng"
  _ -> "ok"
end
f2.(1) # ok

サンプル

# フィボナッチ
defmodule Fib do
  def f(l), do: f(l,1,1)
  def f(l, a, b) do
    if l > b, do: [b] ++ f(l, b, a + b), else: []
  end
end
Fib.f(30) # [1, 2, 3, 5, 8, 13, 21]
# リバース
defmodule Reverse do
  def r([head | tail]), do: r(tail) ++ [head]
  def r([]), do: []
end
Reverse.r [1,2,3]    # [3,2,1]
Enum.reverse [1,2,3] # [3,2,1]

クラスのようなもの

defrecord Person, name: "", age: 0 do
  def say(mine) do
    "I am " <> mine.name <> "."
  end
end
tanaka = Person.new(name: "tanaka", age: 20)
tanaka.say # I am tanaka.

AS3で動画を扱ったり、JSと連携したり

ActionScript3で動画に関するメモ

動画再生

動画を再生するには以下のいずれか。

  1. FLVPlaybackを使う。
  2. flexのspark.components.VideoPlayerを使う。
  3. Videoクラスなどを使い自前で作る。

1がスタンダード。

FLVPlayback

使い方

ウインドウ -> コンポーネント -> Videoを開く -> FLVPlaybackをステージに。

import fl.video.FLVPlayback;

var player = new FLVPlayback();
addChild(player);
player.source = "hoge.mp4";
player.autoPlay = true;

対応してる種類

デフォルトで用意されてるコンポーネント。flvと名前が付いてるが、mp4など色々いける。

http://help.adobe.com/ja_JP/ActionScript/3.0_UsingComponentsAS3/WS5b3ccc516d4fbf351e63e3d118a9c65b32-7fe9.html

Flash Player 9 Update 3 では、業界標準H.264 エンコーディングを利用する高解像度の MPEG-4 ビデオ形式がサポートされ、FLVPlayback コンポーネントの機能が向上しています。サポートされる形式は、MP4、M4A、MOV、MP4V、3GP および 3G2 です。

スキン

Macだと以下にスキンがある。同じ位置にflaもあるので自由にカスタマイズできる。

"/Applications/Adobe Flash CC/Adobe Flash CC.app/Contents/Common/Configuration/FLVPlayback Skins/MinimaSilverPlayBackSeekCounterVolMute.swf"

動画に字幕を付ける

字幕を付けるにはFLVPlaybackCaptioningが使える。

var captions = new FLVPlaybackCaptioning();
captions.autoLayout = false;
captions.flvPlayback = player_mc.player;
captions.source = "sample.xml";

残念なことxmlファイルを読み込んでしか使えない...。

xmlオブジェクトを読めるようにしてくれれば...。

JSとFlashの連携

連携に必要な設定

1. allowScriptAccessをalwaysに設定する必要がある

<object ... >
<param name="allowScriptAccess" value="always">
</object>

2. file://だとダメ

http://localhost/ とかでないと、動きません。

3. flashとjsが別ドメインの場合

flash.system.Security.allowDomain('example.com')

js -> flash

html

<object id="hoge" ...>

js

var swf = document.hoge || window.hoge;
swf.to_flash();

flash

if (ExternalInterface.available) {
  ExternalInterface.addCallback('to_flash', function() { test.text = 'receive it!'; });
}

Scalaでフィボナッチ数列のメモ

なんか面接とかでたまに書かされるらしいので

Listで再帰

// ListFib.f(100)
object ListFib {
  def f(lim: Int): List[Int] = {
    1 :: 2 :: f(1, 2, lim)
  }
  def f(a: Int, b: Int, lim: Int): List[Int] = if (b > lim) { Nil } else {
    b :: f(b, a + b, lim)
  }
}

Streamで再帰

// StreamFib.f().take(10).toList
object StreamFib {
  def f(a: Int = 1, b: Int = 1): Stream[Int] = b #:: f(b, a + b)
}

文字列内の数値をインクリメントする

/**
 * 文字列内の数値をインクリメントする
 * ex) TextIncrementer("abc123efg456hij").++() // abc124efg457hij
 * ex) TextIncrementer("あいう1 2 3えお").++()   // あいう2 3 4えお
 * @param t インクリメントしたい文字列
 * @param incrValue いくつインクリメントするか
 * @return インクリメントした文字列
 */
case class TextIncrementer(t: String, incrValue: Int = 1) {
  def ++(i: Int = 0, n: String = ""): String = {
    n match {
      case "" => ++(1, t.slice(0, 1))
      case _ if (i > t.length) => n // end
      case _ => {
        //println(i, n)
        n.takeRight(1).exists(_.isDigit) match { // abc1 1が数値か
          case true => {
            val c = sequenceNumberCount(i)
            val nn = n.dropRight(1) // abc1 -> abc
            val inc = t.slice(i - 1, c).toInt + incrValue
            ++(c + 1, nn + inc + t.slice(c, c + 1))
          }
          case false => ++(i + 1, n + t.slice(i, i + 1))
        }
      }
    }
  }
  private def sequenceNumberCount(i: Int): Int = {
    t.slice(i, i + 1).exists(_.isDigit) match {
      case true => sequenceNumberCount(i + 1)
      case false => i
    }
  }
}

オートマトンにしようかと思ったけどまぁ...

正規表現

"hoge1 = 9999;".gsub(/\d+/) {|i| i.to_i + 1 }
// "hoge2 = 10000;"

Fluentdまとめ

Fluentdとは?

ログを収集・集約するruby製のミドルウェアです。いままではrsyncやscpでログを全サーバーから回収したりしてたと思いますが、fluentdを使えば便利に色んな方法でログを集めることができます。

仕組み

Fluentdは、Input・Buffere・Outputというモジュールで構成されています。

  • Input
    • ログファイル・TCP・UNIXドメインソケット等で受け取ります。
  • Buffer
    • 受け取ったログを一時的に保管しておくところです。ファイルに保管するかメモリに保管するか選べます。
  • Outputは
    • Bufferに積まれたログをファイル・MySQL・HBase・MongoDBなどに書いたりします。別サーバーのFluentdに渡すこともできます。

概念図

f:id:R-H:20130224124122p:plain

インストール

yumリポジトリ設定

vi /etc/yum.repos.d/td.repo
cat /etc/yum.repos.d/td.repo
[treasuredata]
name=TreasureData
baseurl=http://packages.treasure-data.com/redhat/$basearch
gpgcheck=0

インストール

yum install td-agent

準備

mkdir /var/log/fluent
chown td-agent:td-agent /var/log/fluent

起動

service td-agent start

送信側サーバー

vi /etc/td-agent/td-agent.conf
<source>
  type forward # TCPで受け取る指定
  tag app.file # ここのtagが<mtach>で使われる
</source>

<match app.file>
  type forward
  host 192.168.56.101 # 集約サーバーのhost
  flush_interval 1s
</match>

受信側サーバー

vi /etc/td-agent/td-agent.conf
<source>
  type forward # 送信側サーバーから受け取る(TCP)指定
  tag app.file
</source>

<match app.file>
  type file # ファイルに書く指定
  path /tmp/fluentoo.log
</match>

気をつけるパラメータ

buffer_chunk_limit
buffer_queue_limit

chunkにログがたまります。

queueにchunkがたまります。

以下の設定だと、2Gなのでサーバーのメモリはそれ以下にしないといけない。

buffer_chunk_limit 8m
buffer_queue_limit 256
8MB * 256個 = 2048MB

パフォーマンス

てきとうにVM1からVM2に書くだけのシンプルな環境で計測してみた。

どっちのVMも2coreのメモリ4GのCentOS6。

  • 1ログ100Byteを100万回書いた。
    • 10,000QPSくらいでた。
    • 1書き込み1msもかからない。
  • 1ログ30KBを5万回書いた。
    • 1500QPSほど。
    • 1書き込み1msかからない。

非同期で書くのでかなり速い。

情報

まとめ

きっとこれからは各種ログ(/var/log)はFluentdで収集するようになるんだろうなぁと。

グラフ化もGrowthForecastで簡単にできるので、データの見える化が簡単にできますね。

Hbaseへ複雑に書く場合は、rubyだとHbaseのjarを呼んだりしないといけないので同じログ収集ツールであるJava製のFlumeを使うといいです。

有名フレームワークのCSRF対策方法を調べたまとめ

ZendFramework

流れ

  • 表示時 : token生成→hiddenセット + セッションにセット
  • 送信時 : 送られてきたtokenをセッションにあるものと同じかでチェック

token生成方法

ランダム値 + salt + 固定値 + ランダム値

md5(
 	mt_rand(1,1000000)
 	.  $this->getSalt()
 	.  $this->getName()
 	.  mt_rand(1,1000000)
);

まとめ

ランダム値をセッションにいれて、送られてきたものとチェック。

Symfony

流れ

  • 表示時 : token生成→hiddenセット
  • 送信時 : 送られてきたtokenを、再度生成したtokenと比較して同じかチェック

token生成方法

salt + 固定値 + セッションID

sha1($this->secret.$intention.$this->getSessionId());

まとめ

なるほどセッションID使うのか。

cakephp

流れ

  • 表示時 : token生成→hiddenセット + セッションにセット
  • 送信時 : 送られてきたtokenをセッションにあるものと同じかでチェック。有効期限設定可。

token生成方法

salt + ランダム値

sha1(Configure::read('Security.salt') . $uuid)

まとめ

ランダム値をセッションにいれて、送られてきたものとチェック。

fuelphp

流れ

  • 表示時 : token生成→hiddenセット + クッキーにセット
  • 送信時 : 送られてきたtokenをクッキーにあるものと同じかでチェック。有効期限設定可(=クッキーの有効期限)。

token生成方法

salt + ランダム値

md5(uniqid().time())

まとめ

jsにも対応してるのでjsでクッキー書いたりしたいからクッキー使ってるみたい。

ruby on rails

流れ

  • 表示時 : token生成→hiddenセット + セッションにセット
  • 送信時 : 送られてきたtokenをセッションにあるものと同じかでチェック

token生成方法

ランダムな base64 文字列

SecureRandom.base64(32)

まとめ

ランダム値をセッションにいれて、送られてきたものとチェック。

結論

あいだをとって

  • ランダム値をセッションにいれて、送られてきたものとチェック
  • 有効期限チェック
  • salt + shha1で生成

が一番よさそう。つまりcakeのが一番よさそう。