はじめに
ブロックチェーンとソケット通信はトレンドですが、少しニッチに(?)、以下のような構成でモニターします。
ブロックチェーン: Stellar
クライアントサイド: socket.io
サーバーサイド: PHP(Phpws)
もう少し細かく、以下が登場人物になります。
OS: Ubuntu 16.04LTS
Stellar: 9.2.0
WEBサーバ: Nginx 1.10.1
PHP: 7.3.0 (phpenv)
Phpws: c2eee39 (13 Aug 2018)
ソケットサーバ: localhost:11180
サーバーエージェント: server.php
モニター画面: https://socket.example.com
ソケットURI: wss://wss.example.com
htmlクライアント: index.html
概要として、
常駐するサーバーエージェントがソケットのポートを開いて待ち受け、ここにhtmlクライアントからNginx経由でプロキシされたソケットURIに対してStellarの状態を一定間隔毎に返します。
実習
さっそく構築方法です。
server {
listen xxx.xxx.xxx.xxx:443 ssl;
ssl_certificate /etc/letsencrypt/live/socket.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/socket.example.com/privkey.pem;
server_name socket.example.com;
root /home/you/monitor;
location / {
index index.php index.html index.htm;
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
try_files $uri = 404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
upstream websocket {
server localhost:11180;
}
server {
listen xxx.xxx.xxx.xxx:443 ssl;
ssl_certificate /etc/letsencrypt/live/wss.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/wss.example.com/privkey.pem;
server_name wss.example.com;
location / {
proxy_pass http://websocket;
proxy_redirect http:// https://;
}
}
"upstream websocket"と"proxy_redirect"がポイントです。
ここで、httpsなインバウントアクセスをインターナルではhttp扱いでプロキシにつないでいます(SSL化はマストではないですが)。
次にサーバーエージェントです。
まず、PHPでソケットサーバーを立てるためのライブラリとして利用するPhpwsをcomposerでインストールておきます。
mkdir ~/monitor/
cd ~/monitor/
vi composer.json
{
"repositories": [
{
"type": "vcs",
"url": "https://github.com/Devristo/phpws"
}
],
"require": {
"devristo/phpws": "dev-master"
}
}
composer install
ここに以下を用意します。
<?php
require_once("vendor/autoload.php");
use Devristo\Phpws\Server\WebSocketServer;
$loop = \React\EventLoop\Factory::create();
$server = new WebSocketServer("tcp://localhost:11180", $loop);
foreach (['core-01', 'core-02', 'core-03', 'core-04'] as $core) {
$loop->addPeriodicTimer(2, function() use($server, $core){
foreach($server->getConnections() as $client) {
list ($num, $state) = check_core($core);
$client->sendString(json_encode([$core => compact('num', 'state')]));
}
});
}
$server->bind();
$loop->run();
function check_core($core) {
exec('curl -s ' . $core . ':11626/info', $res);
$num = null;
$state = null;
if (!empty($res)) {
foreach ($res as $row) {
strpos($row, '"num"') !== false and $num = trim(explode(':', $row, 2)[1]);
strpos($row, '"state"') !== false and $state = trim(str_replace(['"', ','], '', explode(':', $row, 2)[1]));
if (!is_null($num) && !is_null($state)) break;
}
}
return [$num, $state];
}
addPeriodicTimer()で、2秒毎にcheck_core()した結果を$client->sendString()で渡しています。
addPeriodicTimer()はcore-**毎にセットされて、それぞれ非同期でcheck_core()がコールバックされるようになっています。
core-**は、/etc/hosts内であらかじめ定義されたコンテナ(DockerやLXC)内に構築された複数台構成の分散型ブロックチェーンサーバです。
11626ポートはStellarがデフォルトで公開しているHTTPなエンドポイントなのですが、これをそのまま利用します。
Stellar以外を使う場合は、このあたりをそれぞれのブロックチェーンのインターフェースにあわせて置き換えればよいかと思います。
最後にhtmlクライアントです。
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Core monitor</title>
<link rel="stylesheet" href="index.css">
<script src="//code.jquery.com/jquery-1.11.1.js"></script>
<script>
$(function () {
function core_processor(core, data) {
var num = data['num'];
var state = data['state'];
if ($('#' + core + ' dd.state span').text() === '') {
$('#' + core + ' dd.state span').text('■');
}
switch (state) {
case 'Synced!':
$('#' + core + ' dd.state span').removeClass().addClass('green');
break;
case 'Catching up':
$('#' + core + ' dd.state span').removeClass().addClass('red');
break;
case 'Joining SCP':
$('#' + core + ' dd.state span').removeClass().addClass('black');
break;
default:
$('#' + core + ' dd.state span').removeClass().addClass('gray');
}
if (state === 'Synced!' && num !== $('#' + core + ' dd.num span')) {
$('#' + core + ' dd.num span').text(num);
} else {
$('#' + core + ' dd.num span').text('');
}
$('#initializer').hide();
}
var socket = new WebSocket("wss://wss.ketoha.xyz");
socket.onmessage = function(msg) {
data = JSON.parse(msg.data);
if (!data) return;
if (!!data['core-01']) core_processor('core-01', data['core-01']);
if (!!data['core-02']) core_processor('core-02', data['core-02']);
if (!!data['core-03']) core_processor('core-03', data['core-03']);
if (!!data['core-04']) core_processor('core-04', data['core-04']);
};
});
</script>
</head>
<body>
<div id="initializer"></div>
<dl id="core-01" class="core">
<dt>core-01</dt>
<dd class="state"><span></span></dd>
<dd class="num"><span></span></dd>
</dl>
<dl id="core-02" class="core">
<dt>core-02</dt>
<dd class="state"><span></span></dd>
<dd class="num"><span></span></dd>
</dl>
<dl id="core-03" class="core">
<dt>core-03</dt>
<dd class="state"><span></span></dd>
<dd class="num"><span></span></dd>
</dl>
<dl id="core-04" class="core">
<dt>core-04</dt>
<dd class="state"><span></span></dd>
<dd class="num"><span></span></dd>
</dl>
</body>
</html>
socket.onmessageで、サーバーエージェントから$client->sendString()を受け取る度に、それぞれの監視対象のブロックチェーンサーバに応じたDOMを更新させています。
index.cssは省略します。
ここまで来たら、いよいよ動かしてみましょう。
php -q server.php
ブラウザから https://socket.example.com にアクセスすると、以下のような画面になります。
2秒毎にブロックチェーンサーバの監視結果、ここでは同期状態とレジャ番号が画面でウォッチできるようなっています。
実際にはここでトレードや送金の状態を追加することで、実用的な運用をおこなうことができます。
最後に
最後に予断ですが、なぜこんな少しニッチ(?)な構成にしているかというと―――
Stellar
分散サーバ型のブロックチェーンなので、サーバーサイドで完結でき(プライベートチェーンとして使い勝手がよい!)、かつトランザクションもSCPという仕組みを使っているため、ビットコインやイーサリウムと比べて格段に早く安定していて、最高だからです。
PHP
同じものをnodejsで動かしてはいるのですが、サーバーサイドのコーディングはまだPHPの方が生産性が高いひとも多いのではないか(つまり自分...)、そういった意味で、まだあまり情報の少なそうなこちらの構成で書いてみました。