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

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

2015年10月

今回のエントリ内容は、このエントリの続編的な内容です。実際に作業する準備の手順も含まれているので、実際に試す前に一度内容をご確認ください:
Bluemix 上の Node.js を使う


Express は Node.js 上で使う MVC フレームワークです。いわゆる "MEAN"(mongoDB + Express + AngularJS + Node.js) スタックを構成する要素の1つです。

また EJS は Node.js 上で利用可能なテンプレートエンジンです。HTML ベースのテンプレートにルック&フィールの UI をデザインし、その中身を変数化して動的に埋めていくことで Web アプリケーションの見栄えを作っていくことができます。

設計とデザイン(見た目)とを別チームに分けて開発するようなスタイルでは、こういったテンプレートエンジンと MVC フレームワークを活用して、デザインチームにテンプレートを、設計チームに MVC を含むプログラミング部分を担当してもらう、といった体制で開発することは一般的です。


Express も EJS も、Node.js ではそれなりにメジャーなコンポーネントで、通常はパッケージマネージャーである npm を使ってインストールして利用するものです。では npm 環境が使えない Bluemix では、これらをどのようにして導入して使えばいいのか? という疑問が出てきます。

その答は、上記の前エントリ内でも述べていますが、package.json ファイルを使って必要なモジュールを動的に指定して用意する、ということになります。Express も EJS もこの方法で Bluemix 内の Node.js ランタイム内に導入可能です。以下にその具体的な手順を紹介します。


今回作成するファイルはこの3つです:
|- public/
|    |- hello.ejs
|- app.js
|- package.json

まずはテンプレートを用意します。 public というフォルダを作り、その下に hello.ejs というテキストファイルを以下の内容で(UTF-8 で)作ります。これが見栄えの部分です:
<html>
<h1><%= title %></h1>
<div><%- content %></div>
<div><%= n %> views</div>
</html>

title, content, n という3つの変数が定義されていて、それらの値を使った HTML が記述されています。EJS ではテンプレート内での変数の書き方は2種類あって、<%= ~ %> で括られた部分はエスケープされて出力され、<%- ~ %> で括られた箇所はエスケープされません(HTML タグが HTML タグとして有効になります)。今回の例では content 変数に HTML タグが含まれている場合は HTML タグが有効になります。


次はシステム設計です。 前回の server.js を元に、app.js を以下の内容で作成します:
var express = require( 'express' );
var app = express();

var fs = require( 'fs' );
var ejs = require( 'ejs' );
var template = fs.readFileSync( __dirname + '/public/hello.ejs', 'utf-8' );

var cfenv = require( 'cfenv' );
var appEnv = cfenv.getAppEnv();

var n = 0;

app.get( '/', function( req, res ){
  n ++;
  var data = ejs.render( template, {
    title: "ハローワールド!",
    content: "ようこそ <strong>Express</strong> + <i>EJS</i> の世界へ",
    n: n
  });
  
  res.writeHead( 200, { 'Content-Type': 'text/html' } );
  res.write( data );
  res.end();
});

app.listen( appEnv.port );



Express を使って http アプリケーションが作られ、ドキュメントルート('/')へのリクエスト時に表示する内容は EJS のテンプレートをベースに、その変数部分だけが動的に指定されて表示されるようになっています(EJS のテンプレートパスを指定する際に fs モジュールも使っています)。変数 title, content, n は変数 data 内にまとめて指定しています。なお変数 content の内容は HTML タグを含むテキストです。また変数 n はイクンリメント変数なので、リロードする度に1つずつ値が増えるようにしています。 cfenv モジュールに関しては上述の前回の内容を参照してください。

最後に package.json を以下の内容で用意します:
{
  "scripts": {
    "start": "node app.js"
  },
  "dependencies": {
    "cfenv": "1.0.x",
    "express": "4.12.x",
    "ejs": "2.3.x",
    "fs": "0.0.x"
  }
}

"scripts" の中で app.js を起動するよう指定している箇所は前回と同様です。加えて、今回は Express, EJS(とファイルシステム利用のための FS )のモジュールを利用したいので、"dependencies" の中でこれらのモジュールを動的にロードするよう指定しています。


これら3つのファイルが用意できたら、cf ツールで Bluemix 上の Node.js ランタイムにプッシュして、ブラウザでドキュメントルートにアクセスしてみます:
2015100705


hello.ejs テンプレートをベースとしたページが表示されており、また変数 content に含まれている HTML タグが有効になっていることがわかると思います。また同じページをリロードすると変数 n の値がインクリメントされて、1つずつ増えていくのがわかります:
2015100706


npm でインストールしていたモジュールを package.json で指定して動的にインストールする、という違いが分かってしまえば、Bluemix 上でもそんなに違和感なく Node.js 環境が使えるようになります。







 

IBM Bluemix から提供されている Node.js ランタイムを使ってみました。Bluemix 上で Node.js を使う場合特有のクセとかコツとかも含めて、その時の様子をまとめてみました:
2015100701


なお、今回は Bluemix 上へのデプロイを cf ツールで行います。cf ツールがインストール済みであることが前提になるので、導入できていない場合はお使いの環境にあった cf ツールをダウンロードして、インストールしておいてください:
https://github.com/cloudfoundry/cli/releases


まず、(Bluemix でない)一般的な Node.js 環境でハローワールド的なアプリを書くと、そのコードはこんな感じになると思います:
var http = require( 'http' );
var server = http.createServer(); // HTTPサーバー

// リクエストを受けたらメッセージを返すだけの処理
server.on( 'request', function( req, res ){
  res.write( 'ハロー Node.js ワールド!' );
  res.end();
});

server.listen( 80 ); // 80 番ポートで起動する場合

仮にこの内容を server.js というファイルで用意したのであれば、以下のコマンドで HTTP サーバーを起動して待ち受けることができます:
# node server.js


そして、このサーバーに対してブラウザで(普通に80番ポートに)アクセスすると、こんな感じで、期待通りのメッセージが表示されます:
2015100702


これと同じことを Bluemix で実現するにはどうすればいいでしょうか?まずは違いが分かるようにちょっとだけ server.js のメッセージを変えておきます:
var http = require( 'http' );
var server = http.createServer(); // HTTPサーバー

// リクエストを受けたらメッセージを返すだけの処理
server.on( 'request', function( req, res ){
  res.write( 'ハロー Node.js ワールド on Bluemix!' );
  res.end();
});

server.listen( 80 ); // 80 番ポートで起動する場合

次に Bluemix 上に Node.js のランタイムを作成しておきます。この例では dotnsf-js という名称で Node.js ランタイムを作っています。今回はサービスは不要です:
2015100703


そして普通の Bluemix 感覚で、この server.js だけが存在しているディレクトリを用意した状態で cf ツールからプッシュして、、、

いやいやいや、これでは動くはずがありません。プッシュした後に
# node server.js
のコマンドを実行する必要があるのに、その指定をしていません。


Bluemix では、これらランタイムの挙動に関する指定は package.json というファイルを作って指定する必要があります。今回は起動時に "node server.js" を実行する、というコマンドを指定したいので、以下のような内容の package.json ファイルを(server.js と同じフォルダに)作成します:
{
  "scripts": {
    "start": "node server.js"
  }
}

この server.js と package.json の2つのファイルが存在している状態で、そのフォルダから cf コマンドで Node.js ランタイムにプッシュしてみます:
> cf push dotnsf-js(ここは作成した Node.js ランタイムの名称)
Updating app dotnsf-js in org dotnsf@jp.ibm.com / space dev as dotnsf@jp.ibm.com...
OK

Uploading dotnsf-js...
   :
   :(結構時間がかかります)
   :
0 of 1 instances running, 1 failing
FAILED
Start unsuccessful

TIP: use 'cf logs dotnsf-js --recent' for more information

>

なんとプッシュが失敗してしまいました。何が原因なのでしょう?

メッセージにあるように "cf logs dotnsf-js --recent' コマンドで最新ログを見てみるとこんな感じのログが出力されていました:
> cf logs dotnsf-js --recent
   :
   :
   :
2015-10-07T11:49:36.80+0900 [App/0]      OUT > node server.js
2015-10-07T11:49:36.96+0900 [App/0]      ERR events.js:85
2015-10-07T11:49:36.96+0900 [App/0]      ERR       throw er; // Unhandled 'error' event
2015-10-07T11:49:36.96+0900 [App/0]      ERR             ^
2015-10-07T11:49:36.96+0900 [App/0]      ERR Error: listen EACCES
2015-10-07T11:49:36.96+0900 [App/0]      ERR     at exports._errnoException (util.js:746:11)
2015-10-07T11:49:36.96+0900 [App/0]      ERR     at Server._listen2 (net.js:1139:19)
2015-10-07T11:49:36.96+0900 [App/0]      ERR     at listen (net.js:1182:10)
2015-10-07T11:49:36.96+0900 [App/0]      ERR     at Server.listen (net.js:1267:5)
2015-10-07T11:49:36.96+0900 [App/0]      ERR     at Object. (/home/vcap/app/server.js:13:8)
2015-10-07T11:49:36.96+0900 [App/0]      ERR     at Module._compile (module.js:460:26)
2015-10-07T11:49:36.96+0900 [App/0]      ERR     at Object.Module._extensions..js (module.js:478:10)
2015-10-07T11:49:36.96+0900 [App/0]      ERR     at Module.load (module.js:355:32)
2015-10-07T11:49:36.96+0900 [App/0]      ERR     at Function.Module._load (module.js:310:12)
2015-10-07T11:49:36.96+0900 [App/0]      ERR     at Function.Module.runMain (module.js:501:10)
   :
   :
   :

"node server.js" を実行した後に Server.listen が失敗しているっぽい。それってソースでいうと最後の
   :
   :
   :
server.listen( 80 ); // 80 番ポートで起動する場合

の部分ってこと? あれ、80 番ポートじゃないんだっけ?


実は Bluemix (CloudFoundry)上の Node.js は 80 番では起動しません。特定のあるポート番号で起動させて、それを 80 番ポートから転送しているのです(つまり直接 80 番ポートで起動させることが間違い)。

では何番のポートを指定して起動すればいいのでしょうか? この答は「環境変数から取得する必要がある」のでした。

というわけで、server.js を以下のように書き換えます:
var http = require( 'http' );
var server = http.createServer();

var cfenv = require( 'cfenv' );
var appEnv = cfenv.getAppEnv();

server.on( 'request', function( req, res ){
  res.write( 'ハロー Node.js ワールド on Bluemix!' );
  res.end();
});

server.listen( appEnv.port );

"cfenv" というモジュールを使って、CloudFoundry としてのランタイムの環境情報を取り出しています。そして listen の引数として指定するポート番号を、この環境情報のポート番号を参照して指定するように書き換えました。

また、cfenv モジュールを動的にロードするため、package.json ファイルにも変更を加えます。このアプリケーションの dependencies に cfenv を加えます:
{
  "scripts": {
    "start": "node server.js"
  },
  "dependencies": {
    "cfenv": "1.0.x"
  }
}

cfenv は 1.0.* というバージョンを使いたいのですが、↑このように指定しておくと、1.0.x の最新版を探して加えてくれます:


これで準備が整いました。改めてこの2つのファイル(server.js と package.json)がカレントフォルダにある状態で cf ツールでプッシュすると成功して、Bluemix 上のランタイムにアクセスできるようになります:
2015100704



この「必要なモジュールを package.json の dependencies に指定してロードして使う」というのは、Node.js の便利なモジュール(Express とか ejs とか)を Bluemix 上で使う場合に必要になるので、覚えておくといいと思いました。


発表済み Bluemix サービスの中で、唯一サービスが開始されていなかった Bluemix Local のサービス提供が開始されました:
http://www.zdnet.com/article/ibm-launches-bluemix-local-aims-to-bridge-public-private-clouds-with-data-center/

3つの Bluemix サービスをわかりやすく比較すると、こんな感じになります:
Bluemix サービス提供形態説明
Publicパブリッククラウド多くの人がマルチテナントで使っている、普通の "Bluemix" はこれ
DedicatedプライベートクラウドIBM のデータセンター上に、シングルテナントの専用 Bluemix 環境を構築して、専有して利用する
Localオンプレミスお客様のデータセンター上に、シングルテナントの専用 Bluemix 環境を構築して、専有して利用する


例えばですが、アプリケーションの開発・テスト・研究段階は気軽に使えるパブリッククラウド版(Public)の Bluemix を使って開発して、テストして、改良して・・・ そして本格的なサービスとして稼働する場合はプライベートクラウド版に移行して、というシナリオがこれまでもご利用できましたが、これからは「本格稼働後はオンプレミス環境内で」という選択肢が増えたことになります。

この Bluemix Local (と Bluemix Dedicated)環境は、お客様の専有環境としての Bluemix ですが、いずれも IBM がリモート管理を行うものです。例えばサービスカタログの管理は IBM が行います。またバージョン管理や Bluemix Public との同期などは "Relay" と呼ばれる同期アップデート機能によって自動化されます:
2015100601
https://developer.ibm.com/bluemix/2015/10/01/introducing-bluemix-local-relay/


パブリックに提供されている Bluemix の環境を専有したり、オンプレミスに用意したり、またはそれらを組み合わせたり・・・ という、「ハイブリッドなクラウド」が IBM の特徴の1つといえますね。


2日ほど泥沼にハマって抜け出せなくなっていた問題が解決できたので、その謎を共有します。

元々はこんなシステムを PHP で作ろうとしていました:
 - 画像をアップロードできる
 - アップロード画像は ID を付与した上で MySQL の BLOB (バイナリ・ラージ・オブジェクト)として格納する
 - ID を指定して、アップロード済みの画像を表示させることができる

自分自身でも Java では何度も作ったことのある仕組みで、その PHP 版を作ろうとしただけでした。

いざ作って動かしてみると、アップロードは成功します。でも表示時に画像が壊れてしまい、うまく表示できないのです。この原因究明と解決に時間を使ってしまいました。


もう少し詳しく状況を説明します。調べていくうちに同じ現象が MySQL を使わなくても再現できることがわかったので、話をシンプルにするために MySQL なしで説明します(実際にはこの切り分けも簡単ではなかった)。

ソースコードはこんな感じ。同一ディレクトリにある bmxug148.png というファイルのバイナリコンテンツ(元々のコードでは MySQL の BLOB に格納されていたもの)を取り出して、PNG 画像用の HTTP ヘッダを指定して echo でバイナリをそのまま出力しています:
<?php
$img = file_get_contents("./bmxug148.png");
header('Content-Type: image/png');
echo $img;
?>

この PHP ファイルをブラウザから呼び出すと、画像が表示・・・されることを期待していたのに、画像が壊れてしまっている旨のメッセージが表示されてしまいます(FireFox で確認した場合):
2015100202


こういう状況だと、表示側の PHP スクリプトに問題があるのか、それともアップロードしたファイルバイナリデータの格納時に既に壊れてしまっているのかすらわかりません。まあ、上記のシンプルなスクリプトでも再現するということは前者ということになるのですが、それが分かったのは相当後になってのことです。


で、とりあえずこの「壊れた画像バイナリ」を無理やり取り出してファイルとして保存し、バイナリエディタで開いてみました。そこで分かったのは画像の最初3バイトに PNG 画像としてふさわしくない情報が追加されていた、ということです:
2015100203


0xEF, 0xBB, 0xBF という3バイトが余分です(4バイト目から本来の PNG 画像のヘッダが始まっています)。4バイト目から表示されていれば正しく表示できそうなのですが、ではこの最初の3バイトはどこで付与されてしまったのでしょう? DB への格納時の処理を疑うこともできたのですが、この EF BB BF という3バイトの組みあわせ、どこかで見たことありました。。

ちょっと調べると分かるのですが、これは BOM(Byte Order Mark) と呼ばれる符号で、テキストが Unicode で記述されていることと、その符号化の種類を示すものです。で、UTF-8 の場合の BOM がまさにこの 0xEF 0xBB 0xBF の3バイトなのです。


つまり、実はもとの PHP ファイルそのものが UTF-8 で記述されていて、しかも BOM 付きでファイルが保存されていたことが直接の原因だったのでした。このため、ブラウザでこのファイル実行した場合も、最初に 0xEF 0xBB 0xBF の3バイトは(PHP ファイルの一部として)返ってきて、次に PHP で処理した結果の画像バイナリが続いていた、ということになります:
2015100201


画像の情報は正しく返っていたのに、その前に画像とは関係のない3バイトが送られてきていたため、「画像としては壊れている」と判断された、ということです:
2015100202


したがって、解決策は PHP ファイルを BOM なしで保存することです:
2015100204


この PHP をブラウザから実行すれば、本来の正しい画像が表示されるようになります:
2015100205


あー、よかった。
 

無料の 3D CG ツールである Blender を CentOS にインストールして使ってみます。まあ自分自身はこの手のソフトを業務利用するわけではないので、本当に触りの導入方法だけ:
2015100201


インストール前の前提として、Blender の利用には X Window System が必須です。Gnome などの X Window 環境のあるシステムを事前に用意してください。

導入手順ですが、CentOS であれば yum で普通にインストールできます:
# yum install blender

インストールが成功すると、Gnome のメニュー以下の「グラフィクス」カテゴリから Blender が起動できるようになります:
2015100201


起動直後はこんな感じ:
2015100202


ここから先は専門ではないので、解説は控えます。ともあれ導入は簡単そうでした。



このページのトップヘ