webネタ

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

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のが一番よさそう。

JavascriptでSingletonパターン

Singletonで実装したい要件があったが、ググッっても良いのがなくて本読んでも残念なものしかなかったので自分で実装してみた。ちなみに本は、JavaScriptパターン ―優れたアプリケーションのための作法

要件

  • newできないようにする。
  • 同一オブジェクトを返す。

クラス名は本にあったものと同じUniverseにしたが別になんでもok。

最初にテストコード

newできないか
try {
    new Universe()
} catch(e) {
    console.info("ok", e);
}

new したら例外が吐かれるようにする。

同一オブジェクトか
var u1 = Universe.getInstance();
var u2 = Universe.getInstance();
console.info(“true?”, u1 === u2);
console.info(“true?”, u2.constructor === Universe);

厳密な等価 + constructorも同じかチェックするる

いつもはjasmineでテスト書くけど今回は準備がめんどうなのでベタ書き。

実装

/**
 * Description of this class.
 * Singleton Class.
 * 
 * @class Universe
 */
var Universe = (function() {
	
	var _fromGetInstance = false;
	var _instance;
	
	function _construct() {
	    if (_fromGetInstance !== true) {
	        throw new Error("must use the getInstance.");
	    }
	    _fromGetInstance = false;
	}
	
	/**
         * @method getInstance
         * @return {Object}
         */
	_construct.getInstance = function() {
	    if (_instance) {
	        return _instance;
	    }
	    _fromGetInstance = true;
	    return _instance = new this();
	}
	return _construct;
})();

/**
 * func1.
 * 
 * @method func1
 */
Universe.prototype.func1 = function() {
    console.log("called func1");
}

使い方

var universe = Universe.getInstanse();
universe.func1();


まず、privateにしたいプロパティがあったのでクロージャにした。
returnされるfunctionをnewできないようにしたいので、getInstanceメソッドからでなければ呼べないようにしてしまう。判定方法は、変数_fromGetInstanceがtrueなのかfalseかなのかというだけ。getInstanceでは一度生成したら次からはそのインスタンスを返すだけ。phpの実装と似た感じ。あとはprotptypeに実装したいメソッドを書いていけばてつもどおりになる。

本の場合

ちなみに本に載ってたsingletonは下記のようにnew Universe()するが、返すものは同じという謎な仕様だった。

var u1 = new Universe();
var u2 = new Universe();
console.info(“true?”, u1 === u2); // true

なんぞこれ。カオスすぎる。


次はfactory書く。

クロスドメインでcookie書き込む方法 +クロスブラウザで

あるサイトから別ドメインのクッキーを書き込む。こういうクッキーは、サードパーティクッキーと呼ばれる。FirefoxChromeはデフォルトでサードパーティクッキーが書き込めるようになっているが、IEとSafariが問題になる。IEはコンパクトポリシーというものを設定すればいけるが、Safariは出来ない。Safariはデフォルトで”知らないとサイトや広告のみCookieをブロック”となっている。でも、GoogleAdsenseとかは書き込めている。なので調べた。

目的

localhostにアクセスしたときsample.comのクッキーを書き込みたい。
もちろんクロスブラウザで。
(sample.comはhosts書き換えやるといい)

ポリシーの設定 (P3P) (以下IE対策用)

webサイトで個人情報などを取り扱う場合、ブラウザで設定されたポリシー設定とアクセスしているサイトのポリシーがマッチするか確認する仕組みがある。詳しくは参考サイトを参照。


Platform for Privacy Preferences 1.0 (P3P1.0) 仕様書
http://www.iajapan.org/trans2japanese/w3c/rec-p3p-20020416j.html

【絵で分かるキーワード】P3P
http://ascii.jp/elem/000/000/338/338065/

P3Pコンパクトポリシーをコピペするのが流行らないことを祈る
http://bakera.jp/ebi/topic/3594


今回は下記をHTTPヘッダーに加える。

header("P3P: CP='UNI CUR OUR'");

phpでセッションに書き込むサンプル (以下Safari対策用)

test-cookie-send.php

<?php
session_start();
header("P3P: CP='UNI CUR OUR'");
var_dump($_SESSION);
$_SESSION['writetest'] = 'waaaa';

できない方法

iframeでクロスドメインのURLにアクセスする。
セッションを張るために、HTTPヘッダーでSetCookie: PHPSESSION=****が返ってくるがクロスドメインなので書き込めない。

test-cookie-sample.html

<!DOCTYPE html>
<html lang="ja" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Cookie write test of Cross Domain.</title>
</head>

<body>
<iframe name="ifr" src="http://sample.com/test-cookie-send.php"></iframe>
</body>
</html>

できる方法

iframeの中にform送信すると、クロスドメインのクッキーも書き込める。詳細は不明だが、GoogleAdsenseがこのロジックで書き込んでいる(実際はもっと複雑な行程だが)。つまりGoogleAdsenseは、Safariで広告のクッキーを書き込まないとしているにも関わらず書き込んでいるのだ。良い子は真似しないように。

test-cookie-sample.html

<!DOCTYPE html>
<html lang="ja" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Cookie write test of Cross Domain.</title>
</head>

<body>
<form id="postform" method="GET" action="http://sample.com/test-cookie-send.php"target="ifr">
<input type="hidden"  name="cookiewrite" value="cookiewritevalue" />
</form>

<iframe name="ifr"></iframe>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
$(function(){
    $("#postform").submit();
});
</script>

</body>
</html>

確認ブラウザ

iPhoneアプリでNode.js

iphoneアプリでnode.jsと通信する。
ネイティブアプリでリアルタイムな通信ができるので幅が広がりそう。
環境はMacOSX10で、今回は全部ローカル作業。


f:id:R-H:20110929084724p:image

objective-cでWebSocket通信

iPhoneアプリとnode.jsで双方向通信するのは、もちろんWebSocketで行う。node.jsをインストールしたことある人はわかると思うが、WebSocket通信はsocket.IOモジュールで行っているのでクライアントサイド(objective-c)もsocket.IOにしたい。ライブラリはsokect.IO-objcを使う。

インストール

xcode

socket.IO-objcの依存ライブラリをインポート

  • cocoa-websocket
  • RegexKitLite
  • json-framework
  • ASIHTTPRequest

cocoa-websocket

https://github.com/erichocean/cocoa-websocket
git cloneでダウンロードし、以下をプロジェクトにインポート。
AsyncSocket/
SocketIO.h
SocketIO.m


RegexKitLite

http://regexkit.sourceforge.net/RegexKitLite/
tarでダウンロードし、以下をプロジェクトにインポート。
RegexKitLite.h
RegexKitLite.m
あとFrameworksに以下をインポート
libicucore.dylib


json-framework

https://github.com/stig/json-framework/
git cloneでダウンロードし、以下をプロジェクトにインポート。
Classes/


ASIHTTPRequest

http://allseeing-i.com/ASIHTTPRequest/
git cloneでダウンロードし、以下をプロジェクトにインポート。
ここに詳しい説明がある
http://allseeing-i.com/ASIHTTPRequest/Setup-instructions


socket.IO-objcをインポート

https://github.com/pkyeck/socket.IO-objc
git cloneでダウンロードし、以下をプロジェクトにインポート。
SocketIO.h
SocketIO.m

node.js(サーバー側)

node.jsとsocket.ioモジュールのインストール

nodeのインストールはnvmコマンドで行う。

git clone https://github.com/creationix/nvm.git ~/.nvm
. ~/.nvm/nvm.sh
nvm install v0.4.7
nvm use stable

npm install socket.io

サンプル

説明

iPhone側から接続し、接続完了後にnode.js側へobjcイベント発火。(もちろんこのobjcという名前はてきとうにつけたイベント名)
node.js側でobjcイベント受信したら3秒後にiPhone側へメッセージを送信。

xcode

まずxcodeで空のプロジェクトを作る。
名前はSocketIoSampleとした。


SocketIoSampleViewController.h

SocketIODelegateをデリゲート。

#import 
#import "SocketIO.h"
@interface SocketIoSampleViewController : UIViewController
@end


SocketIoSampleViewController.m

viewDidLoadメソッドに下記を追記。

 - (void)viewDidLoad
{
    [super viewDidLoad];
    
    // WebSocket接続
    SocketIO *socketIO = [[SocketIO alloc] initWithDelegate:self];
    [socketIO connectToHost:@"localhost" onPort:3000];
}

// WebSocket接続完了した場合
 - (void) socketIODidConnect:(SocketIO *)socket
{
    NSLog(@"-- socketIODidConnect()");

    // objcイベント発火。(node.js側でon('objc', function() {...})でキャッチ)
    [socket sendEvent:@"objc" withData:[NSDictionary dictionaryWithObject:@"hoge" forKey:@"Key"]];
    NSLog(@"-- sendEvent()");
}

// node.jsからWebSocketでメッセージが送られてきた場合
 - (void) socketIO:(SocketIO *)socket didReceiveEvent:(SocketIOPacket *)packet
{
    NSLog(@"-- didReceiveMessage() >>> data: %@", packet.data);
}
node.js側

conn-objc.js

var io = require('socket.io').listen(3000);
var timer = require('timers');

// WebSocket接続時
io.sockets.on('connection', function (socket) {
  console.log('CONNECTION ok');

  // iPhoneからのobjcイベント受信時
  socket.on('objc', function (data) {
    console.log('CATCH EVENT ok', data);

    // iPhoneからのメッセージ受信3秒後に、node.js側からメッセージ送信
    timer.setTimeout(function() {
        socket.emit('messages', { echo: 'from node.js!' });
        console.log('SEND MESSAGE ok');
    }, 3000);
  });
});


node.jsを起動

node conn-objc.js

xcodeでビルド

xcode側のログ
SocketIoSample[19935:b303] Opening ws://localhost:3000/socket.io/1/websocket/222774812681844920
SocketIoSample[19935:b303] Connection opened.
SocketIoSample[19935:b303] onData 1::
SocketIoSample[19935:b303] connect
SocketIoSample[19935:b303] onConnect()
SocketIoSample[19935:b303] -- socketIODidConnect()
SocketIoSample[19935:b303] send()
SocketIoSample[19935:b303] send() >>> 5:::{"args":{"Key":"hoge"},"name":"objc"}
SocketIoSample[19935:b303] -- sendEvent()
SocketIoSample[19935:b303] doQueue() >> 0
SocketIoSample[19935:b303] setTimeout()
SocketIoSample[19935:b303] onData 5:::{"name":"messages","args":[{"echo":"from node.js!"}]}
SocketIoSample[19935:b303] setTimeout()
SocketIoSample[19935:b303] event
SocketIoSample[19935:b303] -- didReceiveMessage() >>> data: {"name":"messages","args":[{"echo":"from node.js!"}]}
node.js側のログ
   info  - handshake authorized 222774812681844920
   debug - setting request GET /socket.io/1/websocket/222774812681844920
   debug - set heartbeat interval for client 222774812681844920
   debug - client authorized for 
   debug - websocket writing 1::
CONNECTION ok
   debug - websocket received data packet 5:::{"args":{"Key":"hoge"},"name":"objc"}
CATCH EVENT ok { Key: 'hoge' }
   debug - websocket writing 5:::{"name":"messages","args":[{"echo":"from node.js!"}]}
SEND MESSAGE ok
   debug - emitting heartbeat for client 222774812681844920
   debug - websocket writing 2::
   debug - set heartbeat timeout for client 222774812681844920
   debug - websocket received data packet 2::

特に問題なく通信できた。
これでリアルタイム操作が可能なアプリ作れますね。(・・`。