Socket.IOでクライアントのヘッダー情報 (IPアドレスやuser-agent等) を取得

Socket.IOで接続してきたクライアントの情報、特にIPアドレスやブラウザの種類を知りたい場合がありますので、コードを書いてみました。

簡単に言えばサーバー側の「socket.handshake」にクライアントの情報が格納されています。

具体的には以下のようなコードになります。

# 適当なディレクトリを作って移動
$ mkdir socketio-clientinfo
$ cd socketio-clientinfo

# サーバー側のコードを記述
# (このページの下の方にコードが載っています)
$ vi app.js

# クライアント側のコードを記述
# (このページの下の方にコードが載っています)
$ mkdir public
$ vi public/index.html

# node.jsを起動
# (express、socket.io等必要なライブラリをインストールしておいてください)
$ node app.js

# ブラウザでアクセス
# (サーバーのIPアドレスにブラウザでアクセスしてください)

# クライアントの情報が表示されます
# (↓ではx.x.x.xとy.y.y.yと伏せ字にしています)
{"headers":{"host":"x.x.x.x","connection":"keep-alive","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36","accept":"*/*","referer":"http://x.x.x.x/","accept-encoding":"gzip,deflate,sdch","accept-language":"en-US,en;q=0.8","cookie":"io=q__i7X_BElMqbzF8AAAA"},"time":"Mon Jan 19 2015 13:47:21 GMT+0900 (JST)","address":"y.y.y.y","xdomain":false,"secure":false,"issued":1421642841412,"url":"/socket.io/?EIO=3&transport=polling&t=1421642811567-20","query":{"EIO":"3","transport":"polling","t":"1421642811567-20"}}

{"headers":{"host":"x.x.x.x","connection":"keep-alive","user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.99 Safari/537.36","accept":"*/*","referer":"http://x.x.x.x/","accept-encoding":"gzip, deflate, sdch","accept-language":"en-US,en;q=0.8,ja;q=0.6","cookie":"io=i0EO-Tv4njOhQRnVAAAB"},"time":"Mon Jan 19 2015 13:47:24 GMT+0900 (JST)","address":"y.y.y.y","xdomain":false,"secure":false,"issued":1421642844308,"url":"/socket.io/?EIO=3&transport=polling&t=1421642687594-21","query":{"EIO":"3","transport":"polling","t":"1421642687594-21"}}

app.js
var express = require('express');
var app = express();
var http = require('http');
var server = http.createServer(app).listen(80);
var io = require('socket.io')(server);
app.use(express.static(__dirname + '/public'));
console.log('Access to http://localhost:80/');
 
io.on('connection', function (socket) {
    console.log(JSON.stringify(socket.handshake));
});

index.html (IPアドレスは、サーバーのIPアドレスに書き換えてください)
<body>

<h2>Socket.IOで接続してきたクライアントのヘッダー(IPアドレスやユーザーエージェント等)をサーバー側で取得</h2>
<p>サーバー側のターミナルで確認して下さい。このHTMLはSocket.IOに接続するだけの役割です。</p>

<!-- ==================== Socket.IO ==================== -->
<script src="socket.io/socket.io.js"></script>
<script>
var socket = io.connect("192.168.111.111:80");
</script>

</body>

cgroupをCentOSで使う覚書

【注意】以下に書いてある具体例を行うとメモリとスワップが枯渇して操作不能になることがあるので自己責任でお願いします!どうでもいいマシンorVMで試してみてください。私はこれをVMで行い、sshで入れなくなりたいへん困りました【注意】

cgroupの使い方@CentOSの覚書です。cgroupを適用させる方法はいくつかあるんですが(たとえば起動中のプロセスに対してコマンドで適用するなど)、以下の方法がもっとも分かりやすいかと思います。

リソースポリシーとしてcgconfig.confを編集

適用先としてcgrules.confを編集

cgconfigでリソースポリシーをファイルシステムに書き込んで、プロセス監視のcgredを起動

という流れです。

とは言え少々分かりにくいので、pidを指定して走らせるとその子プロセスまで見ていって制限できるようなコマンドが欲しいですね。

具体的な作業方法 (CentOSを前提としています)

# libcgroupをインストール (入っていなければ)
$ yum install libcgroup
$ yum install libcgroup-tools

# 次のようにcgconfig.confを編集
# cpu20grp → CPU20%使えるグループ
# mem300mbgrp → メモリ300MB、スワップ200MBの合計500MB使えて、OOM Killer無効のグループ
# という意味
$ cat /etc/cgconfig.conf 
mount {
	cpuset	= /cgroup/cpuset;
	cpu	= /cgroup/cpu;
	cpuacct	= /cgroup/cpuacct;
	memory	= /cgroup/memory;
	devices	= /cgroup/devices;
	freezer	= /cgroup/freezer;
	net_cls	= /cgroup/net_cls;
	blkio	= /cgroup/blkio;
}

group cpu20grp {
	cpu {
		cpu.cfs_quota_us = 20000;
	}
}

group mem300mbgrp {
	memory {
		memory.limit_in_bytes = 300M;
		memory.memsw.limit_in_bytes = 500M;
		memory.oom_control = 1;
	}
}

# 次のようにcgrules.confを編集
# ユーザ名 コントローラ名 グループ名/
# という形式。
# 今回ユーザ名はkawaとなっている (この部分は、環境に合わせてユーザ名を書いて下さい)
# 「%」は同上の意味 (同じuserを2つ書くと無効のよう)
$ cat /etc/cgrules.conf 
kawa cpu cpu20grp/
% memory mem300mbgrp/

# cgconfigとcgredを起動
# cgconfig: cgconfig.confの内容をシステムに反映させるデーモン
# cgred: プロセスを監視して、cgrules.confに記述されたものを反映させる
$ /etc/init.d/cgconfig restart
Stopping cgconfig service:                                 [  OK  ]
Starting cgconfig service:                                 [  OK  ]

$ /etc/init.d/cgred restart
CGroup ルールエンジンデーモンを停止中...                   [  OK  ]
Starting CGroup Rules Engine Daemon:                       [  OK  ]

# 該当ユーザーで負荷のかかるプロセスを実行してみる
$ su kawa
$ yes > /dev/null

# CPU使用率を確認 → 20%程度になっている
$ top

# 制御下にあるプロセス一覧を表示・確認
$ cat /cgroup/cpu/cpu20grp/tasks
26868
26869
26982
26983
27295
27350

# 該当ユーザーでメモリを使うプロセスを実行してみる
# → 別のタブでdstat -ms とすると、450MB程度確保したところで止まる
# → それは、メモリとスワップの合計500MBを確保してしまい、cgroupの影響でそれ以上取れないため
$ gcc memeater.c
$ ./a.out

memeater.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#define MSIZE 100 // Memory size per sec. 50 means 50MB/sec
#define MB 1024*1024

int main(int argc, char *argv[])
{
  char *p;
  int malloc_size = MSIZE * MB;
  int sum = 0;

  printf("This program just eats memory.\n");
  while(1)
  {
    p = (char *)malloc(malloc_size);
	memset(p, 0, malloc_size);
	sum += MSIZE;
	printf("%5d MB\n", sum);
	sleep(1);
  }
  return 0;
}

以下追記@2015-01-15

おまけ: 特定のシェルスクリプトに対してリソース制御

運用を考えると、特定のシェルスクリプトに対して自動的にリソースが制御されると嬉しいですね。具体的に言えば、hogeというシェルスクリプトのリソース制御を/etc/cgrules.confに書いておけば、hogeを走らせた時に自動的にリソース制御してくれる、という感じです。

試しにやってみたら出来たので書いておきます。任意のスクリプトに対して制限を掛けることができるので、そこそこ便利だと思います。

# 以下のようにCPU利用率を20%に制限するグループを作成
$ vi /etc/cgconfig.conf
$ cat /etc/cgconfig.conf 
mount {
	cpuset	= /cgroup/cpuset;
	cpu	= /cgroup/cpu;
	cpuacct	= /cgroup/cpuacct;
	memory	= /cgroup/memory;
	devices	= /cgroup/devices;
	freezer	= /cgroup/freezer;
	net_cls	= /cgroup/net_cls;
	blkio	= /cgroup/blkio;
}

group cpu20grp {
	cpu {
		cpu.cfs_quota_us = 20000;
	}
}

# 以下のようにcgrules.confを編集 (意味としては、rootユーザーで実行するhogeという名前のシェルスクリプトに対して、cpu使用率20%の制限を掛ける)
$ vi /etc/cgrules.conf
$ cat /etc/cgrules.conf 
root:hoge cpu cpu20grp/

$ cgconfigとcgredを再起動して上記の設定を反映
$ /etc/init.d/cgconfig restart
Stopping cgconfig service:                                 [  OK  ]
Starting cgconfig service:                                 [  OK  ]
$ /etc/init.d/cgred restart
CGroup ルールエンジンデーモンを停止中...                   [  OK  ]
Starting CGroup Rules Engine Daemon:                       [  OK  ]

# 次のようなシェルスクリプトを作る
$ cat hoge 
#!/bin/bash

yes 'LOOP' | cat > /dev/null

# 実行権限を付加
$ chmod +x hoge

# 実行してみる
$ ./hoge

# 別のttyでtop or htopしてCPU使用率を見てみる
# → yesとcatの合計で20%程度のCPU使用率となっている
$ top

# cgredを停止してみる
$ /etc/init.d/cgred stop
CGroup ルールエンジンデーモンを停止中...                   [  OK  ]

# 再度実行してみる (リソース制御されていないので100%の使用率になるはず)
$ ./hoge

# 注意点
# プロセスを起動させてからcgredをstartした場合、そのプロセスに対しては制御が行われるが、その子プロセスに対しては制御が及ばない!
# そのため、子プロセスに対しても制御したい場合は事前にcgrules.confに書いて、cgredを再起動させてから実行する必要がある

追記@2015-02-01

Tipsを書いておきます。

☆ Debianではcgroupのmemoryがデフォルトでは無効になっているらしい
ソースはここ→ http://stackoverflow.com/questions/21337522/trying-to-use-cgroups-in-debian-wheezy-and-no-daemons

☆ $ cgconfigparser -l /etc/cgconfig.conf すると、「Loading configuration file /etc/cgconfig.conf failed Cgroup, operation not allowed」と怒られる
→ service cgconfig restart で行ったほうが良さそう。どうやら正しいコンフィグファイルでも上記のエラーメッセージが出るようだ(出典: http://sourceforge.net/p/libcg/mailman/message/27906594/にて、「If we run the cgconfigparser manually, it fails alternatively. It is a little
weird behaviour. The problem surfaces when there is some cgroup already
mounted.」とある)。service cgconfig restartして怒られなければOKと考えて良いだろう。きちんと出来ているかはless /sys/fs/cgroup/〜あたりを見てみると良い。

☆ Debian系では、memory.memsw.limit_in_bytesを設定するとservice cgconfig restart がコケる。↑のメモリの問題かもしれない。。。memory.limit_in_bytesは設定しても大丈夫だった。

http://www.usupi.org/sysad/228.html←大変参考になります。$lssubsysや、$lssubsys -a、cgconfigのmountグループに書き込んでからlssubsysすると増えるとかなるほどです。

☆ RHEL系の方がDebian系よりやれることが多いしきちんと動く傾向。PDF注意だがこことか→Control Groups(cgroups) の概要 – Fedora People

PHPRedisにおけるLua Scriptingの書き方

PHPの有名なRedisライブラリPHPRedisのLuaの書き方をいつも忘れるので書き残しておきます。Luaを使えばアトミックになるのでそれなりに使う機会はあると思います。

コードを下記に置いておきました、一目瞭然かと思いますが、説明しますと、evalの引数は必須がひとつ、オプションが2つあります。必須はLuaスクリプト、オプションのひとつはarray、オプションのもう一つはarrayの要素数です。本来のRedisではKEYSだけでなくARGVも使えますが、どうやらPHPRedisだとKEYSだけ使用可能なようです (Redis Clusterを使わない限りは、RedisのLuaにおけるKEYSとARGVに違いはありません、詳しくはこの記事の前の記事を参照してください)。

コード

<?php

$redis = new Redis();
$redis->connect('localhost', 6379, 1.0);

$script1 = <<<EOT
	return {KEYS[1], KEYS[2], KEYS[3]}
EOT;

$keys = Array('key1', 'key2', 'key3');
$result1 = $redis->eval($script1, $keys, count($keys));
var_dump($result1);

$script2 = <<<EOT
	return KEYS
EOT;

$result2 = $redis->eval($script2, $keys, count($keys));
var_dump($result2);

結果
array(3) {
  [0]=>
  string(4) "key1"
  [1]=>
  string(4) "key2"
  [2]=>
  string(4) "key3"
}
array(3) {
  [0]=>
  string(4) "key1"
  [1]=>
  string(4) "key2"
  [2]=>
  string(4) "key3"
}

おまけ

内部でRedisを呼ぶには次のようにします。
<?php

$redis = new Redis();
$redis_status = $redis->connect(REDIS_HOST, REDIS_PORT, REDIS_TIMEOUT);

if ($redis_status === FALSE) {
	die("Internal Server Error.\n");
}

$luascript = <<<EOT
	local key1 = KEYS[1]
	local key2 = KEYS[2]
	local key3 = KEYS[3]
	local key1_exists = redis.call('EXISTS', key1)
	local key2_exists = redis.call('EXISTS', key2)
	return {key1_exists, key2_exists}
EOT;

$keys = array('value1', 'value2', 'value3');

$result = $redis->eval($luascript, $keys, count($keys));

var_dump($result);

出力例

array(2) {
  [0]=>
  int(1)
  [1]=>
  int(0)
}

HMSETについては、以下のようにやればできます。

<?php
$redis = new Redis();
$redis_status = $redis->connect(REDIS_HOST, REDIS_PORT, REDIS_TIMEOUT);

if ($redis_status === FALSE) {
	die("Internal Server Error.\n");
}

$luascript = <<<EOT
	local key1 = KEYS[1]
	local key2 = KEYS[2]
	local key3 = KEYS[3]
	redis.call('hmset', 'hmset1', 'f1', key1, 'f2', key2, 'f3', key3)
	
	return redis.call('hgetall', 'hmset1')
EOT;

$keys = array('one', 'two', 'three');

$result = $redis->eval($luascript, $keys, count($keys));

var_dump($result);

出力例

array(6) {
  [0]=>
  string(2) "f1"
  [1]=>
  string(3) "one"
  [2]=>
  string(2) "f2"
  [3]=>
  string(3) "two"
  [4]=>
  string(2) "f3"
  [5]=>
  string(5) "three"
}

RedisのLua ScriptingのKEYSとARGVの違いについて調査

結論から言いますと、通常の使い方の場合は違いを意識する必要はないようです。KEYSとARGVの2つを使う必要があるのはRedis Clusterなどのときのようです。

RedisのLua機能で、引数を使えるのですが、KEYSとARGVという2つの変数を渡すことが出来ます。それはそれでいいんですが、KEYSとARGVの違いが分かりません。使い分けなどあるのでしょうか。公式のドキュメントを読みますと↓

The first argument of EVAL is a Lua 5.1 script. The script does not need to define a Lua function (and should not). It is just a Lua program that will run in the context of the Redis server.

The second argument of EVAL is the number of arguments that follows the script (starting from the third argument) that represent Redis key names. This arguments can be accessed by Lua using the KEYS global variable in the form of a one-based array (so KEYS[1], KEYS[2], …).

All the additional arguments should not represent key names and can be accessed by Lua using the ARGV global variable, very similarly to what happens with keys (so ARGV[1], ARGV[2], …).

http://redis.io/commands/eval


こんな感じです。うーん、違いが分かりませんね。。。いろいろと探してみると、RedisのGoogle Groupにありました。

Any differences between “KEYS” and “ARGV” in scripting?

Yes, it is for Redis Cluster, and for sharding in general. If you will
never use Redis Cluster, or if you never plan on sharding your data,
you can effectively treat them the same.


なるほど、Redis Clusterやシャーディングのときに使うみたいですね。とは言えなぜそのときに2つの変数が必要になるのか私にはいまいち分かりませんが。。。