まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

2018年06月

僕自身はオープンソース製品である Hyperledger FabricHyperledger Composer を使ったブロックチェーンプラットフォームや、その対応アプリケーションを業務で開発する機会が多くあります。

ありがたいことに実際に作る機会も多いのですが、「作る」という決断に到達する前に方針を変更することも少なくありません。よくある理由としては「(現行の)ブロックチェーンに向かない要件」であるというケースです。パフォーマンス要件だったり、検索機能の要件だったり、現在のブロックチェーンでは本格運用に向かないケースが前提になっていたりすると、そのことを伝えた上で「それでも作るかどうか」を判断していただいています。そして「作らずに検討し直す」と判断されることもある、という意味です。

でも、これはブロックチェーンを使いたいと思いながらも「現在のブロックチェーン技術」では向かない、と判断されたからです。需要としては存在していることも意味しています。

そんな場面を多く見ている中で「だったらブロックチェーンといえるかどうかはともかく、同じような仕組みで耐改竄性を高めながら、この要件も満たせるようなデータストアの仕組みを作れないか?」と考えるようになりました。それが今回紹介するものです。


【「ブロックチェーン」の定義】
まず最初に、以下で紹介するアプリケーションは「ブロックチェーン」と呼べるものかどうかと言われると、私個人的には「呼べないのではないか」と考えています。これは「ブロックチェーンの定義」をどのようにするか、という問題にも関わってくるのですが、ここでは1つの目安として、JBA("Japan Blockchain Association" 日本ブロックチェーン協会)が定義した以下の2つの定義を満たすものをブロックチェーンと定義するものとします:

・ビザンチン障害を含む不特定多数のノードを用い、時間の経過とともにその時点の合意が覆る確率が0へ収束するプロトコル、またはその実装をブロックチェーンと呼ぶ。
・電子署名とハッシュポインタを使用し改竄検出が容易なデータ構造を持ち、且つ、当該データをネットワーク上に分散する多数のノードに保持させることで、高可用性及びデータ同一性等を実現する技術を広義のブロックチェーンと呼ぶ。

(参照)
http://jba-web.jp/archives/2011003blockchain_definition

2018062901



要するに「ブロックチェーンとは『ブロック』が『チェーン』のようにつながって格納される仕組み」というゆるい定義ではなく(注 私個人的にはこれがブロックチェーンの定義でもいいと思ってます)、「ハッシュポインタを用いて接続し、データを複数のノード上に分散して保持し、かつその複数ノードが耐ビザンチン障害性を持っている実装」のことを「ブロックチェーン」と定義する、ということです。 そしてこの定義の上で考えた場合、以下で紹介するものはブロックチェーンではない、ということになることを予め伝えておきます。

自分の考えは、上記のものだと耐ビザンチン障害性をもたせるためのコンセンサスアルゴリズムに問題が発生したり(最近だと「51%攻撃」と呼ばれる手法が話題になりました)、そのためにトランザクションのパフォーマンスを高く実現するのが難しかったりする、と思っています。なのでこの要件が必須でないケースでは、この条件を確保しないことでパフォーマンスや検索機能など他の要件機能を向上させることができるのではないかと考えました。


【色々出てきている】
現状のブロックチェーンにはパフォーマンス遅延や電力消費など当初は想定していなかったことが問題になっていることもあって、それらの苦手部分に対処した新しい仕組みも生まれています(それらが上記ブロックチェーンの定義を満たしているかどうかはともかく)。例えば全参加者のコンセンサスを得る上でのパフォーマンス遅延を解決するために、ランダムに選ばれた一部参加者のコンセンサスを得るように改良したものも出ているようです:
ブロックチェーンを超える?ハッシュグラフ(Hashgraph)とは?


【作ってみようと思ったもの】
今回、自分は以下4つの特徴を持つようなデータストアシステムを新たに開発してみようと思いました:
(1)ハッシュポインタを利用した、改竄検出が容易なブロックチェーンの仕組みはそのまま取り入れる
(2)データは中央集権管理型とする。これによりコンセンサスアルゴリズムや耐ビザンチン障害性の問題を発生させなくした上で、トランザクションパフォーマンスの向上を実現する
(3)データそのものは分散データストアに格納して(格納可能にして)高可用性を実現する。つまり単なる1ピア運用とは異なる
(4)バイナリデータやファイル添付を含む大きなレコードのデータストアへの格納や全文検索機能、データを読み書きするための REST API を標準装備する



(2)の時点で上記ブロックチェーンの定義を満たさなくなっています(ブロックチェーンは非中央管集権管理型)。(3)ただ単に1ピアにデータを格納するのではなく、論理的な1データストアが実際には複数のロケーションに分散できるようにしており、これにより特定ロケーションが単一障害点とはならないようにします。そして(4)ブロックチェーンが比較的苦手とする巨大オブジェクトの格納や検索機能もこのシステムでは標準装備することを目標としています。加えて標準で REST API を準備することで(導入すればすぐ使えるようになって)導入時の負担軽減も実現するつもりです。

2018062902



【作っているもの】
上記仕組みを実現するためのソフトウェア(というかプラットフォームというか、API 群というか・・)を開発中です。まだ開発中ですが Github にて MIT ライセンスで公開しています:
https://github.com/dotnsf/hashchainsolo


上記の事情もあり、開発コード名には「ブロックチェーン」は含めず「ハッシュチェーンソロ」という表現にしました。レコードのブロックをチェーンのようにつなげていくものではあるのですが、上述の定義を満たすようなものではなく(むしろそこを犠牲にしている)、自分も「ブロックチェーン」という表現にこだわるわけではないので「ハッシュチェーン」という表現にしたかったのですが、これはこれで別の意味に使われており、ややこしいことになりそうだったので(ブロックチェーンだと単一ピアに相当する設計になるという意味で)「ハッシュチェーンソロ」と命名しています。某○icrosoft Office 365 Solo の影響とかではありませんw

インフラとして、分散データストアには IBM Cloudant を採用しています。Apache CouchDB をベースとした NoSQL 型のマネージド DBaaS で、容量・トランザクションパフォーマンスに制約があるものの無料プランを選択することもできるので、「とりあえず動作確認してみる」ためのハードルは低く実現できると思っています。一方でプランを見直して容量やパフォーマンスを変えたり、要件によってはプライベートクラウド版やオンプレミスソフトウェア版を使うことも可能です。またこの Cloudant 自体がバイナリ(添付ファイル)格納や検索インデックスといった機能を内包しているので、ブロックチェーンでいうところの「ステート DB」に相当する仕組みや外部ファイルシステム等がなくても各種データを格納することができ、上述の4つの特徴を兼ね備えた単独の仕組みを比較的作りやすいと思っています。

#現時点では実装していない余談ですが、IBM Cloudant をデータストアに使っていることで、携帯版の PouchDB と組み合わせてモバイルプラットフォームへの移植も想定しやすい設計にしています。

ハッシュポインタを使って耐改ざん性を高める部分は全てスクラッチで実装しています。タイムスタンプと nonce を併用してマイニングを行い、特定条件(変更可)を満たした時だけハッシュを採用できるようにしています。

2018/Jun/30 の現時点での開発状況は「とりあえず一通りの REST API は動くようになっている」レベルです。時間を見つけてぼちぼちと改良中です。


【Call for Code】
この製品で Call for Code に参加予定です。自然災害の支援を目的としたソリューションのコンテストで、ブロックチェーンを使う事例は多く存在すると思いますが、そのブロックチェーン技術の選択肢の1つとして、既存技術では難しいものも場合によっては解決できる、と思っていて、その実装として提供・参加予定です。




 

Node.js + Express で作成する Web アプリケーションでセッション認証に対応したアプリケーションを作ってみます。今回はその実装のために Express Session パッケージJWT(Json Web Token)パッケージを使った例を紹介します。


【設計】
最初に、以下で紹介するソースコード(の最新版)をこちらで公開しています。よろしければこちらを参照/クローンして使ってください:
https://github.com/dotnsf/jwtsample


まず大まかな仕組みは以下のようにします:
・Node.js と Express で必要な REST API を用意する。今回は POST /login(ログイン)、GET /logout(ログアウト)、GET /items(一覧取得)、そして POST /item(新規作成)の4つの REST API を用意します。
・POST /item は認証済みのユーザーからでないと実行できないようにします。他の3つは認証前でも実行できるものとします。

(注 本来ログアウトは GET 以外のメソッドで実装するべきですが、今回はサンプルをシンプルにしたので URL のみで実行できるよう GET で実装しています。実際のアプリを作る際には POST で実装してください)


その上で、次のような流れを実現できるようにします:
(1) ユーザー ID とパスワードで認証(ログイン)する
(2) 正しい ID とパスワードが指定された場合はそのユーザーオブジェクトで JWT を作成し、セッションに格納する
(3) 新規作成の API 実行時には新たに ID とパスワードを要求するのではなく、セッションに正しいオブジェクトが存在している場合のみ実行を許可する。またこの際、セッション内のオブジェクトからユーザー名をデコードし、タイムスタンプと合わせて作成データに含める(この API をいつどのログインユーザーが実行したのか、の情報を作成データに含める)
(4) ログアウトの API 実行時にセッションをリセットする。そのため再度ログインしないと新規作成の API は実行できなくなる


上記流れの中で3箇所セッションを操作しています。逆にセッションの操作だけでログインの確認や実行権限の有無を判断できるようにしています。

で、この流れを実装すべく、以下の(単なるページロードも含めて) 6つの API を用意することにします:
#メソッドパス目的
1GET/(index.html)インデックスページのロード
2GET/login.htmlログインページのロード
3GET/logoutログアウトし、インデックページをロード
4POST/loginID とパスワードでログイン
5GET/items全アイテムのリストを取得
6POST/item新たにアイテムを作成し、リストに追加


【実装】
上記設計を実装する上で2つの静的ページ index.html と login.html が必要です。前者は一覧を表示した上で新規作成も行うページで、後者はログインのページです。まずこれらを先に作っておきます。

まずは 1. index.html 。こちらは 5. で作成する GET /items の API を使って全アイテムの一覧を取得して表示する機能と、6. の POST /item を使って新規にアイテムを作成する機能を作ります。今回は1つの表の中で一覧表示と、その最下行から新規作成できるような表を jQuery を併用して作ってみました:
<html>
<head>
<meta charset="utf8"/>
<script type="text/javascript" src="//code.jquery.com/jquery-2.0.3.min.js"></script>
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<title>LIST</title>
<script>
$(function(){
  getItems();
});
function getItems(){
  $('#items_table_tbody').html( '' );
  $.ajax({
    type: 'GET',
    url: '/items',
    success: function( items ){
      console.log( items );
      items.forEach( function( item ){
        var tr = '<tr><td>' + item.name + '</td><td>' + item.price + '</td><td>' + item.user.id + '</td></tr>';
        $('#items_table_tbody').append( tr );
      });
      var tr = "<tr>"
        + "<td><input type='text' id='name' placeholder='name'/></td>"
        + "<td><input type='text' id='price' placeholder='price'/></td>"
        + "<td><input type='button' class='btn btn-primary' value='Add' onClick='addItem();'/></td>"
        + "</tr>"
      $('#items_table_tbody').append( tr );
    },
    error: function( err ){
      console.log( err );
    }
  });
}
function addItem(){
  var name = $('#name').val();
  var price = parseInt( $('#price').val() );
  $.ajax({
    type: 'POST',
    url: '/item',
    data: { name: name, price: price },
    success: function( data ){
      console.log( data );
      getItems();
    },
    error: function( jqXHR, textStatus, errorThrown ){
      console.log( textStatus + ":" + errorThrown );
    }
  });
}
</script>
</head>
<body>

<div class="navbar navbar-default">
  <div class="container">
    <div class="navbar-header">
      <a href="/" class="navbar-brand">Items</a>
      <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
    </div>
    <div class="collapse navbar-collapse target">
      <ul class="nav navbar-nav navbar-right" id="navbar">
      </ul>
    </div>
  </div>
</div>

<div class="container" style="padding:20px 0; font-size:8px;">
  <table class="table table-hover table-bordered" id="documents_table">
    <thead class="table-inverse">
      <tr>
      <th>name</th>
      <th>price</th>
      <th>user</th>
      </tr>
    </thead>
    <tbody id="items_table_tbody">
    </tbody>
  </table>
</div>

</body>
</html>




次に 2. login.html 、こちらは id と password を入力して /login に POST するだけのシンプルなページです:
<html>
<head>
<meta charset="utf8"/>
<script type="text/javascript" src="//code.jquery.com/jquery-2.0.3.min.js"></script>
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<title>Login</title>
</head>
<body>
<div class="row title">
  <div class="container">
    <div class="col-md-4 col-md-offset-4">
    <form id="loginform" class="form-signin" method="POST" action="/login">
    <h2>Login</h2>
    <input type="text" class="form-control clear" id="id" name="id" placeholder="user id" required="" autofocus=""/><br/>
    <input type="password" class="form-control clear" id="password" name="password" placeholder="password" required=""/><br/>
    <button class="btn btn-lg btn-primary btn-block" type="submit">Login</button>
    </form>
  </div>
</div>
</body>
</html>


そして、3. ~ 6. は Node.js で実装します:
// app.js

var express = require( 'express' );
var bodyParser = require( 'body-parser' );
var jwt = require( 'jsonwebtoken' );
var session = require( 'express-session' );
var app = express();

var superSecret = 'weloveibmcloud';  //. 適当に変更してください

app.set( 'superSecret', superSecret );
app.use( express.static( __dirname + '/public' ) );  //. 静的コンテンツは /public 以下
app.use( bodyParser.urlencoded() );
app.use( bodyParser.json() );

//. Express-Session の設定
app.use( session({
  secret: superSecret,
  resave: false,
  saveUninitialized: false,
  cookie: {
    httpOnly: true,
    secure: false,           //. https で使う場合は true
    maxage: 1000 * 60 * 60   //. 60min
  }
}) );

//. ログイン処理(4.)
app.post( '/login', function( req, res ){
  res.contentType( 'application/json' );
  var id = req.body.id;
  var password = req.body.password;

  /*
   * 本来はユーザーディレクトリやマスター DB などを参照して id & password が正しいかどうかを判断する。
   * 今回は簡易的に id と password の中身が一致している場合はその id でログイン成功したものとする
   */
  if( id && id == password ){
    //. ログイン成功
    var user = { id: id, password: password };  //. トークンの素になるオブジェクト
    var token = jwt.sign( user, superSecret, { expiresIn: '25h' } );
    req.session.token = token; //. セッションに記錄

    res.redirect( '/' );
  }else{
    //. ログイン失敗
    res.redirect( '/login.html' );
  }
});

//. ログアウト(3.)
app.get( '/logout', function( req, res ){
  req.session.token = null; //. セッションをリセット
  res.redirect( '/' );
});

var items = [];  //. データ一覧(本来はデータベースなどで管理するもの、今回はメモリ処理だけで実装)

//. データ一覧取得(5.)
app.get( '/items', function( req, res ){
  res.contentType( 'application/json; charset=utf-8' );
  res.write( JSON.stringify( items, 2, null ) );
  res.end();
});

//. データ新規作成(6.)
app.post( '/item', function( req, res ){
  res.contentType( 'application/json; charset=utf-8' );
  var token = ( req.session && req.session.token ) ? req.session.token : null;
  if( !token ){
    res.status( 401 );
    res.write( JSON.stringify( { status: false, result: 'No token provided.' }, 2, null ) );
    res.end();
  }else{
    //. トークンをデコードして、ログイン時のユーザーオブジェクトを取りだす
    jwt.verify( token, app.get( 'superSecret' ), function( err, user ){
      if( err ){
        res.status( 401 );
        res.write( JSON.stringify( { status: false, result: 'Invalid token.' }, 2, null ) );
        res.end();
      }else{
        var item = req.body;                        //. ポストされたデータ
        item.user = user;                           //. セッションのユーザー情報をデータに含める
        item.timestamp = ( new Date() ).getTime();  //. タイムスタンプをデータに含める

        items.push( item );

        res.write( JSON.stringify( { status: true, item: item }, 2, null ) );
        res.end();
      }
    });
  }
});


var port = 3000;
app.listen( port );
console.log( 'server started on ' + port );

なお、上記の 3. ログイン処理と 4. ログアウト処理は(今回アプリケーション側を複雑にしたくなかったので)セッションの値を処理した後に redirect メソッドで直接ページを切り替えるようにしています。本来は他の処理と同様に結果を JSON で返す形にするのがいいと思っています。

個人的に肝になると思われる箇所を赤字にしています。まず今回はトークンの仕組みとして Json Web Token を利用しており、またそのトークンをセッションで管理するため、これら2つのパッケージをロードしています:
    :
var jwt = require( 'jsonwebtoken' );
var session = require( 'express-session' );
    :

4. ログイン処理(POST /login )時は本来ならば既存のユーザーディレクトリなどを参照して認証を行います。今回のサンプルではユーザーディレクトリを使わずに「入力した ID とパスワードが一致していれば正しい認証が行われた」とみなしています。この辺りは実装時に少し気をつけてください。

そして正しい認証が行われた後にその情報を使ってオブジェクトを作り、そのオブジェクトを JWT でトークン化して、セッションに記録するように処理しています:
    :
    //. ログイン成功
    var user = { id: id, password: password };  //. トークンの素になるオブジェクト
    var token = jwt.sign( user, superSecret, { expiresIn: '25h' } );
    req.session.token = token; //. セッションに記錄
    :

一方、3. ログアウト処理ではセッションの内容をクリアするだけでログイン情報が消えることになります:
    :
  req.session.token = null; //. セッションをリセット
    :

データは本来はデータベースなどに保存するべきものですが、今回は簡易的にメモリ内の items 変数で管理しています(そのためアプリケーションサーバーを再起動すると中身がリセットされます)。そして 5. データの一覧取得ではその items 変数をそのまま JSON 配列で返すようにしています。なお、このメソッドはトークンの有無に関係なく実行可能です。

そして 6. データの新規作成時にはまずセッション内のトークンの有無を確認します。トークンがあればログイン済み、なければログイン前と判断します:
    :
var token = ( req.session && req.session.token ) ? req.session.token : null;
    :

トークンが存在していた場合、そのトークンをデコード(verify)することで、トークン化前のユーザーオブジェクトを取り出すことができます:
    :
    //. トークンをデコードして、ログイン時のユーザーオブジェクトを取りだす
    jwt.verify( token, app.get( 'superSecret' ), function( err, user ){
    :

そして取り出したユーザーオブジェクトを使って、送信データ(req.body)に加えて、ユーザー情報とタイムスタンプを追加して1つのデータオブジェクトを生成しています。これを items 配列に加える形で一覧に追加しています:
          :
        var item = req.body;                        //. ポストされたデータ
        item.user = user;                           //. セッションのユーザー情報をデータに含める
        item.timestamp = ( new Date() ).getTime();  //. タイムスタンプをデータに含める
          :

これによって、5. のデータ一覧で取り出すデータにはデータを作成したユーザーの情報やそのタイムスタンプも合わせて含まれるように実現しています。

こうして作成した app.js と index.html、login.html を使ってアプリケーションを動かします。なお、実際に動かす場合は2つの HTML ファイル(index.html と login.html)を public/ フォルダ内に格納しておきます(app.js で静的コンテンツファイルは public/ フォルダ内にある、と定義しています)。


【動作確認】
Node.js で app.js を実行します:
$ node app

今回の app.js では 3000 番ポートでアプリケーションが稼働します。環境によって 3000 番ポートが使えない場合は app.js 内の該当箇所を編集してください。

ウェブブラウザで該当サーバーの 3000 番ポートを指定(例: http://localhost:3000/)してアクセスします。挙動を確認しやすくするため、開発コンソールも開いておきます。最初のロード時には index.html が表示されます。この中で GET /items が実行されており、その取得結果が開発コンソールに表示されています(最初は中身が存在しないので、空配列になっているはずです):
2018062801


まだログインしていないのでデータの作成はできないのですが、作成できないことを確認してみます。name と price に適当な商品名とその価格を入力し、Add ボタンをクリックします:
2018062802


すると開発コンソールに "error:Unauthorized" というメッセージが表示されます。「まだログインしてない」というエラーです。この表示がでればとりあえず未認証ではデータを作成できない、ということが確認できます:
2018062803


ではログインしてみます。ブラウザの URL パスを /login.html に指定して(例: http://localhost:3000/login.html)アクセスするとログイン画面が表示されます:
2018062804


上述のように今回のサンプルでは id とパスワードに同じものが指定された場合はログイン成功とみなすことにしているので、最初はわざと異なる値(例えば id: user1、password: user)を入力して "Login" ボタンをクリックしてみます。正しく動くと(ログインできないので)元の画面に戻ってきてしまいます:
2018062805


改めて id とパスワード欄両方に "user1" と入力して "Login" ボタンをクリックします。この場合は認証の条件を満たしているのでログインに成功し、元の一覧ページに移動します:
2018062806


外見上の違いがなくわかりにくいのですが、今回はログイン済みの状態で一覧ページを見ています(ちゃんと作る場合はログイン有無で外見上の違いもあるといいと思います)。改めて name と price を入力して "Add" ボタンをクリックします:
2018062807


さっきはログイン前だったので "Unauthorized" エラーになりましたが、今回はログイン済みなのでデータの新規作成に成功します。そして GET /items が実行された結果が開発コンソールに表示され、先程まで空配列だったところにデータが1つはいった配列が表示されていることがわかります。また画面にも入力したデータがテーブル表示されるようになり、そこには(入力していない)ユーザー名も表示されています。先程のログインの記録がセッションに残っており、その情報を使って API が実行されていることがわかります:
2018062808


もう1つデータを追加すると画面では2行に表示され、また開発コンソールにも2つのデータを持った配列が表示されます:
2018062809


ここでいったんログアウトしてみます。URL パスに /logout (例: http://localhost:3000/logout)を指定してアクセスしログアウト処理を実行します。画面はそのまま元の一覧ページに戻りますが、今度はログインされていない状態で表示されています。そのため3つ目のデータを作成しようと "Add" ボタンをクリックしても最初と同様に "error:Unauthorized" エラーが表示されてしまいます:
2018062810



と、まあこんな感じです。JWT でログイン情報をトークン化してセッションで保持/消去を管理することで、このようなセッション認証対応アプリを作ることができるようになります。

見栄えとかリンクとか、本来はもう少し凝った UI を考えるべきなのでしょうが、今回の話の中では本質的でないので省略して手抜き簡易化しています。ごめんなさい。



GitHub で作成してしばらく使っていたリポジトリが、当初の想定以上に盛り上がったりすると、最初に適当に付けたリポジトリ名からちゃんとした正式名称のリポジトリに変更したくなる(というか、した)、という経験をしました。その時の作業手順メモです。

まず最初の注意点として、GitHub リポジトリのリネームは GitHub サーバー側と、そのクローンを保持するローカル側の両方で行う必要があります。クローンを保持するローカルが複数ある場合は、その全てのローカル側で対応が必要になります。

今回は
 https://github.com/dotnsf/old_app.git

 https://github.com/dotnsf/new_app.git
にリネームする想定で以下を説明します。

【GitHub サーバー側】
サーバー側の変更は GitHub のリポジトリ画面内から行います。まずブラウザでリポジトリページを開き、"Settings" メニューを選択します:
2018061401


"Settings" メニューのすぐ下に "Repository name" フィールドがあり、ここに変更前のリポジトリ名(今回であれば "old_app")が入力されています:
2018061402


ここを新しいリポジトリ名称(今回であれば "new_app")に変更し、"Rename" ボタンをクリックして確定させます:
2018061403


サーバー側の変更はこれだけです。この時点で名称変更前の URL にアクセスしても自動的に新しい URL にフォワードされて、新しい名前のリポジトリが表示されます:
2018061404



【ローカル側】
クローンしたローカルリポジトリ内の .git/config ファイルを編集します:
  :
  :

[remote "origin"]
        url = https://github.com/dotnsf/new_app
        fetch = +refs/heads/*:refs/remotes/origin/*

  :
  :

[remote "origin"] 項目内の url の値を新しいリポジトリの URL に変更して保存します。ローカル側の変更もこれだけですが、複数のマシンにローカルリポジトリが存在する場合は全てのローカルリポジトリを変更します。



ここまでの作業でサーバー側&リモート側ともリポジトリのリネーム作業が完了しました。当然ですが、中身は変わってない(リネーム前のまま)ので、改めてリネーム後に変更が必要なファイル(README.md とか)を更新してください:
2018061405


 

Chart.js を使って円グラフを描くときによく悩まされることがあります。

「AとBの2つの値の比率を視覚的に比べる」ことを目的に円グラフを使うことがあるのですが、これを Chart.js で描こうとすると、以下のような感じになります:
  :
  :
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.1.4/Chart.min.js"></script>

  :
  :

<canvas id="myChart"></canvas>

<script>
var ctx = document.getElementById( 'myChart' ).getContext( '2d' );
var chart = new Chart( ctx, {
  type: 'pie',
  data: {
    labels: [ 'A', 'B' ],  //. ラベルは A, B の順
    datasets: [ {
      data: [ 10, 20 ],    //. A の値が10Bの値が 20 であるとする
      backgroundColor: [ '#ff0000', '#0000ff' ]
    } ]
  }
});
</script>


  :
  :

↑この例では A と B という2つの値を円グラフで表示しようとしています。A の値を 10 、B の値を 20 とした上で、 A を赤に、B を青で指定しています。

これをブラウザで表示するとこうなります:
2018060501


・・・違う、惜しい。そうじゃないんだ!

ラベル部分が左A右Bになっているので、円グラフも左側に赤いA右側に青いBを持ってきたい。。。 でもこれが難しい(苦笑)。

Chart.js の円グラフの場合、円の頂点を始点として時計回りにデータが描画されるので、どうしても右側に最初のデータ(A)が描かれてしまい、左側には2つ目のデータ(B)が描画されます。つまり円グラフとしてはラベルとは逆に右に赤いA左側に青いBが表示されてしまうのです。

ここ、なんとかして逆にできないかなあ・・・ と色々工夫しているのですが、これというアイデアが浮かばず、諦めています。

残念・・・  Chart.js 以外のグラフ描画ライブラリだとなんとかなるのかなあ・・・



最近気になっているプログラミング言語の1つが Julia です:
Julia_prog_language.svg



高速で、使いやすくて(これは意見が分かれると思うけど、確かに if elseif などのコードを短く書けることは事実)、最近流行りの Python の関数を呼び出して使えるので機械学習ライブラリが豊富に使える、とのこと。インデントを使ったコード記述も Python っぽくて、なかなかに魅力を感じます。

なお高速性については公式サイトにベンチマークの比較が載っていたのでここにも載せておきます。このベンチマークによるとたしかに C 並のパフォーマンスを叩き出していて、Java や JavaScript、Python などよりは速そう・・・:
2018060601


Julia のインストール方法は様々用意されていますが、最新バージョンのインストールは公式サイトからの command line version ダウンロードが確実なようです(Ubuntu の場合、 $ sudo apt-get install julia でも導入できましたがバージョンが古く、後述のウェブフレームワークが使えなかったりする問題がありました):
2018060602


↑上記表から自分の環境にあったものを選択してダウンロード&インストールします。自分は Ubuntu 環境で試したので、"Generic Linux Binaries for x86 - 64-bit" から tar.gz ファイルをダウンロードし、展開してパスを通しました:
$ wget https://julialang-s3.julialang.org/bin/linux/x64/0.6/julia-0.6.3-linux-x86_64.tar.gz
$ tar xzvf julia-0.6.3-linux-x86_64.tar.gz
$ echo export PATH="$PATH:~/julia-d55cadc350/bin" >> ~/.bashrc
$ source ~/.bashrc
$ julia -v
julia version 0.6.3

これで Julia は使えるようになりました。ついでに Julia のウェブフレームワークである Genie も導入してしまいましょう:
2018060604


まずは対話モードで Julia を起動します:
$ julia

2018060603


julia> というプロンプトが表示されれば、Julia を対話モードで起動できました。ここに Julia の構文を入力して実行することもできますが、今回はこの対話モードを使って Genie を導入していきます。

まず、最初の一回目だけはパッケージの更新を行っておきます:
julia> Pkg.update()

次に Genie の前提動作に必要な Flex パッケージを導入します:
julia> Pkg.add( "Flex" )

そして改めて Genie を以下のコマンドで導入します:
julia> Pkg.clone( "https://github.com/essenciary/Genie.jl" )

ここまでの作業で Genie が導入できました! では実際に Genie を使ってウェブアプリケーションを作ってみます。まずはこの対話モードのまま Genie を有効にします:
julia> using Genie

そしてアプリケーション名(以下では "my_genie")を指定して Genie アプリケーションを作成します:
julia> Genie.REPL.new_app( "my_genie" );

このコマンドを実行して成功すると、プロンプトが genie> に変わり、julia コマンドを実行した時のフォルダに my_genie フォルダが作られ、その中に Genie フレームワークに必要な一通りのファイルやコマンドが生成されます:
2018060607


そのファイルを確認する前にまず一回このまま実行してみます。プロンプトから以下のように入力してアプリケーションサーバーを起動します:
genie> AppServer.start()

これで 8000 番ポートでアプリケーションが起動します。ウェブブラウザからこのホストの 8000 番ポートを指定して HTTP リクエストし、ここまでの作業が成功してデフォルトの初期画面が表示されることを確認します:
2018060605


とりあえず動いているようです。では最後にちょっとしたカスタマイズを加えます。そのためには一度コマンドラインに戻る必要があるため、Genie と Julia の対話モードを終了します。いずれも quit() で終了します(Genie と Julia 両方の対話モードを終了するので2度実行する必要があります):
genie> quit()

julia> quit()

$

元のプロンプトに戻ったら Genie が生成したファイルを改良します。元のフォルダから my_genie/config/routes.jl ファイルをエディタで開き、以下の赤字部分を追加します:
using Router

route("/") do
  Router.serve_static_file("/welcome.html")
end

route("/hello") do
  "Hello Julia - Welcome to Genie!"
end

ルート( "/" )にアクセスした場合は上記の初期画面(/welcome.html)が表示されるよう設定されていますが、その下に "/hello" にアクセスがあった場合の処理を加えました。この例では "Hello Julia - Welcome to Genie!" という文字列が表示されるようにしています。

改めてカスタマイズしたアプリケーションを実行します。アプリケーションフォルダ(my_genie/)に移動して、今度は対話モードではなく直接アプリケーションサーバーを起動します:
$ cd my_genie
$ bin/server

2018060608


そしてウェブブラウザで、今度は /hello パスを指定して読み込みます。期待通りのメッセージが表示されれば成功です:
2018060606


テンプレートエンジンとか、ファイルアップロードをどう処理するかとか、データベース連携とか、まだ調べないといけないことはあるけど、ウェブフレームワークとしては使えそうです。


いくつか heroku 用の Julia ビルドパックが見つかる(Julia のバージョンは低そうだけど)ので、次はこいつを IBM Cloud 上で動かすことに挑戦予定です。


このページのトップヘ