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

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

タグ:express

Node.js / Express を使って API を作成する場合は、こんな感じの記述で実装することになります(3000 番ポートで listen する POST /mypost を実装する場合の例):
var express = require( 'express' );
var app = express();

  :

app.post( '/mypost', function( req, res ){
   :
   :
});

  :

app.listen( 3000, function(){
  console.log( 'server running on port 3000...' );
});

これで POST /mypost を 3000 番ポートで待ち受ける API ができました。同一アプリケーション内の HTML などからであれば、こんな感じでリクエストを受けることができるようになります:
(jQuery を使って AJAX でポストする例)
  :
$.ajax({
  type: 'POST',
  url: '/mypost',
  data: { a: 1, b: 2, c: 3 }
}).done( function( result ){
  console.log( result );
});
  :

さて、ではこの POST /mypost を外部アプリケーションの HTML からリクエストするにはどうすればいいでしょうか?深く考えずに URL にサーバー名やポート番号も指定して、
(jQuery を使って AJAX でポストする例)
  :
$.ajax({
  type: 'POST',
  url: 'http://servername:3000/mypost',
  data: { a: 1, b: 2, c: 3 }
}).done( function( result ){
  console.log( result );
});
  :

こんな感じ↑にすればいいかというと、たしかに AJAX 部分はこれでいいのですが、結論としてはまだ不充分です。これを実行すると、ブラウザのコンソールには以下のようなメッセージが表示され、期待通りの挙動にはなりません:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://servername:3000/mypost. (Reason: CORS header 'Access-Control-Allow-Origin' missing).

CORS(Cross Origin Resource Sharing) というメカニズムによって、このリクエストはブロックされている、というエラーメッセージです。つまり「 servername:3000 上で動いている POST /mypost という API を外部から呼ぶことはポリシーに反しているのでできない」ということになります。

この外部リクエストを許可する方法はないでしょうか? CORS の設定の問題なので理論上はできるはずですが、その中でも比較的簡単な方法が cors ライブラリを使う方法です:
2017112401


この cors ライブラリを使うと CORS のデフォルトポリシーを変更して、柔軟に API のクロスオリジン対応が可能になります。以下に例を紹介しますが、まずは cors をインストールしておきます:
$ npm install cors

実際にクロスオリジンリクエストを許可するには以下のようにします。まずは全ての API をクロスオリジンで許可する場合です。API の定義を行う前に app.use() で cors を指定します:
var express = require( 'express' );
var app = express();
var cors = require( 'cors' );

app.use( cors() );   //. ここから下に定義する全ての API にクロスオリジン実行を許可する

  :

app.post( '/mypost', function( req, res ){  //. クロスオリジン実行が許可される
   :
   :
});

  :

app.listen( 3000, function(){
  console.log( 'server running on port 3000...' );
});

また、特定の API に対して個別にクロスオリジン実行を許可する場合は、以下のように指定します:
var express = require( 'express' );
var app = express();
var cors = require( 'cors' );

  :

app.post( '/mypost', cors(), function( req, res ){  //. POST /mypost のクロスオリジン実行は許可される
   :
   :
});

  :

app.listen( 3000, function(){
  console.log( 'server running on port 3000...' );
});




 

Node.js でウェブアプリを作って、
$ node app.js

みたいな感じで実行する際に、
listen EACCES 0.0.0.0:443
  :
  :

というエラーが出て実行できないことがあります。この原因と回避方法について紹介します。


この "listen EACCES 0.0.0.0:XXX"(XXX 部分は数字)というエラーは node サーバー起動時の指定ポート番号に1024以下の小さい数字が指定されている場合に発生します。一般的には管理者権限を持っていないユーザー権限で 1024 番以下のポートを listen することはできません。

上記エラーの場合は HTTPS(HTTP+SSL) を使いたくて 443 番ポートを指定して実行したケースでした。このポート番号が小さすぎることに加え、管理者権限を持たないユーザーが実行したことで発生していました。

このエラーを回避するには、管理者権限で node サーバーを起動すればよいので、
$ sudo node app.js

といった感じで、sudo を付けて実行することで回避できます。


(参考)
Node.js + Express で SSL を使う


 

IBM Bluemix(Cloud Foundry) のプラットフォームが現在持っている制約の1つが「IPアドレスによるアクセス制限」に関するものです。残念ながら現時点ではベースとなっている Cloud Foundry にこの機能がなく、IBM Bluemix でも実装されていません。

というわけで、現状この機能を実現するにはプラットフォーム側ではなくアプリケーション側で用意する必要があります。Node.js アプリケーションでこれを実現する方法の1つとして、Express-IpFilter があります:
https://www.npmjs.com/package/express-ipfilter

2017060601



名前の通りの機能です。Node.js の Express フレームワークの中で IP アドレス制限(許可/拒絶)を簡単に実現することができます。

Express-IpFIlter をインストールするには npm で以下を実行します:
$ npm install express-ipfilter
(実際には express のインストールも必要です)

例えば、以下のような Node.js + Express のシンプルなアプリケーションを例に IP アドレス制御をかける例を紹介します。まずアプリケーション(app.js)は以下のような内容のものを使います:
//. app.js

var express = require( 'express' );
var app = express();

app.use( express.static( __dirname + '/public/' ) );

app.listen( 3000 );

アプリケーションの中でスタティックディレクトリを /public/ に指定しています。そこで /public/index.html というファイルを用意し、中身を以下のようなものにします:
<html>
<head>
<title>Hello</title>
</head>
<body>
<h1>はろーわーるど</h1>
</body>
</html>

これを普通に Node.js で実行します:
$ node app

3000 番ポートを listen するように指定しているので、このポートを指定して同一マシンからウェブブラウザでアクセスすると、スタティックディレクトリに用意した index.html を見ることができます:
2017060602


ではこのアプリにアクセス制御をかけてみます。まずは '127.0.0.1' からのアクセスを拒絶するようなフィルターをかけてみます。app.js の内容を以下のように変更します(赤字部分を追加):
//. app.js

var express = require( 'express' );
var ipfilter = require( 'express-ipfilter' ).IpFilter;
var app = express();

var ips = [ '127.0.0.1' ];
app.use( ipfilter( ips ) );

app.use( express.static( __dirname + '/public/' ) );

app.listen( 3000 );

この例では ips という配列変数を用意し、その中に対象とする IP アドレスを文字列配列の形式で代入します。そして ipfilter を有効にしています。

この状態で app.js を起動し、先程と同じように同一マシンからウェブブラウザでアクセスすると以下のようになります:
2017060603


IP アドレス制御が有効になり、アクセスは拒否されました。

では今後は逆に '127.0.0.1' からのアクセスのみ許可するようなフィルタをかけてみます。app.js の内容を以下のように変更します(青字部分を追加):

//. app.js

var express = require( 'express' );
var ipfilter = require( 'express-ipfilter' ).IpFilter;
var app = express();

var ips = [ '127.0.0.1' ];
app.use( ipfilter( ips, { mode: 'allow' } ) );

app.use( express.static( __dirname + '/public/' ) );

app.listen( 3000 );

この状態で再度アプリケーションを起動し、同様にウェブブラウザでアクセスすると、今度は元のように表示されます(つまり IP Filter はデフォルトだと拒絶、'allow' モードを指定すると許可のフィルタをそれぞれ有効にします):
2017060604


これでアプリケーションレベルでの IP アドレス制限が実現できます。

Node.js + Express の環境で SSL を使う(https でアクセスできるようにする)方法を調べたのでまとめました。


まず SSL を使うための鍵ファイルと証明書ファイルを用意します。公式なドメインを所有していて、本物の鍵/証明書ファイルを持っているのであればそれを使っても構いません。試験的に試すのであれば、いわゆる「オレオレ証明書」を作成します。Linux 環境であれば openssl コマンドを使って、以下のように入力します:
$ openssl genrsa -out server_key.pem 2048
Generating RSA private key, 2048 bit long modulus
...............+++
..........................+++
e is 65537 (0x10001)

$ openssl req -batch -new -key server_key.pem -out server_csr.pem -subj "/C=JP/ST=Chiba/L=Funabashi/O=Jugeme/OU=Dev/CN=juge.me"

$ openssl x509 -in server_csr.pem -out server_crt.pem -req -signkey server_key.pem -days 73000 -sha256
Signature ok
subject=/C=JP/ST=Chiba/L=Funabashi/O=Jugeme/OU=Dev/CN=juge.me
Getting Private key

$

↑この例では juge.me というホスト名で運用する前提での、サーバーの鍵ファイル(server_key.pem)と証明書ファイル(server_crt.pem)を作成しています。

この2つのファイルと同じディレクトリに app.js というファイル名で、Node.js のソースコードを以下の内容で作成しました。アプリケーションそのものはドキュメントルート(/)にアクセスがあった場合に「ハローワールド」と表示するだけの単純なものです:
//. app.js

var express = require( 'express' ),
    http = require( 'http' ),
    https = require( 'https' ),
    cfenv = require( 'cfenv' ),
    fs = require( 'fs' ),
    app = express();
var appEnv = cfenv.getAppEnv();

//. 鍵ファイルと証明書ファイル var options = { key: fs.readFileSync( './server_key.pem' ), cert: fs.readFileSync( './server_crt.pem' ) };
//. 鍵ファイルと証明書ファイルを指定して、https で待受け var server = https.createServer( options, app ).listen( appEnv.port, function(){ console.log( "server stating on " + appEnv.port + " ..." ); });
//. ドキュメントルートにリクエストがあった場合の処理 app.get( '/', function( req, res ){ res.write( 'ハローワールド' ); res.end(); });

このコードを実行すると待受ポート番号が動的に決定して表示されます(↓の例だと 6015 番ポートで https が待ち受けていることが分かります):
$ node app.js
server stating on 6015 ...

ではウェブブラウザで実際にアクセスしてみます。本当に使っている本物のドメイン/ホスト名であればそのままアクセスできると思いますが、試験的に実行している場合はこのホストに "juge.me" という名前でアクセスできる必要があります。必要に応じて hosts ファイルを編集するなどして、目的のホスト(node app.js を実行したホスト)に "juge.me" というホスト名でアクセスできるような準備をしておいてください。

そしてウェブブラウザで "https://juge.me:(ポート番号)" にアクセスします:
2017041301


オレオレ証明書名物「安全な接続ではない」という警告画面になると思います。ここからの手順はウェブブラウザの種類にもよりますが、FireFox の場合であれば「エラー内容」ボタンをクリックしてから「例外を追加」をクリックして、このサイトの警告を無視するための設定を行います。

そして「セキュリティ例外の追加」ダイアログにて、この URL の「セキュリティ例外を承認」します:
2017041302


すると警告が消えて、プログラムで用意したコードが実行され、「ハローワールド」というメッセージが表示されることが確認できます:
2017041303


IBM Bluemix のような PaaS 環境だと、このあたりも含めてアプリケーションサーバー環境が用意されるので楽ですが、素で Node.js 環境を構築する場合はアプリケーション側でも https 対応を実装する必要があり、意外と面倒ね。

2016 年の年初に「マンホライザー」というサービスを作って動かしていました:
http://dotnsf.blog.jp/archives/1048853473.html

マンホライザーはわかりやすく言うと「マンホール顔ハメ画像作成サービス」です(わかりやすいか??)。先日のマンホールサミット 2017 埼玉に参加した際にも、こんなアナログな顔ハメ写真サービスを提供させていただいたのですが、意外と人気があったのでした。それをウェブ上からもできるようにしよう、というわけで生まれたサービスでした。IBM Watson の画像認識機能を使って実装しており、「テクノロジーの無駄使い」な点も自己採点ポイントが高いものになっています(笑):
2017020701


上記リンク先の、2016 年に公開した当初は PHP で実装していました。その時のソースコードはこちらです:
https://github.com/dotnsf/Manholizer


このコードはこれはこれで現在も動く(注 Visual Recognition API の古いものを使っているので、顔ハメはうまく動きません)ものですが、いくつかの課題もありました。その1つが「スマホからの利用を想定していなかった」という致命的な点です。見た目でのスマホのウェブブラウザ最適化という意味では実現したつもりでしたが、想定外だったのは「スマホのカメラで撮影した画像の解像度が予想以上に高かった&今後は更に高くなることが想定される」ということです。

ある程度詳しい人には常識かもしれませんが、iPhone をはじめとする最近のスマートフォンのカメラはかなり高い解像度の写真を撮影することができます(特に設定を変更しない場合は、高解像度撮影が標準機能になっていることが多いです)。撮影した写真を低解像度にしてメールで送る、といったことは可能ですが、内部的には高解像度のまま保存されていることになります。これは1枚の写真画像のファイルサイズが非常に大きいということを意味しています。これは2つの点で問題になります。

1つはアプリケーションサーバー側の制約を受ける可能性があるという点です。例えば PHP を普通にインストールしたアプリケーションサーバーの場合、アップロードサイズは 2MB に設定されることが多いです。もちろんこの設定を変更できればいい話ですが、最近は PaaS などでミドルウェア設定を変更できないケースもあるので、そうなるとこの制約を受けてしまいます。

もう1つは API 側の仕様上の制約を受ける可能性です。このマンホライザーでは画像から顔の位置を認識・特定する必要があるのですが、その部分に IBM Watson の Visual Recognition API を使っています。この API の制約として現在は「画像は 2MB まで」という制約があるのでした。つまり上記のアプリケーションサーバー側の制約を取り除いただけでは解決にならないことも出てきてしまうのです。

上記で紹介した、以前作った PHP 版のマンホライザーにはこれら2つの課題があり、スマホからの利用を想定すると期待通りに動かないケースが出てきてしまいました。というわけで、ミドルウェアや設計段階から見直したマンホライザーを作り直すことにしたのでした。


上記2点を解決するため、まず PHP のアップロードファイルサイズ制約をうけないよう、アプリケーションサーバーは Node.js を使うことにしました。PHP から Node.js への移植を行いました。これによって 2MB を超える画像もアップロードできるようにしました。

また API 側のサイズ制約については、アップロードした画像を一旦内部的にリサイズし、API が実行できるレベルにまでサイズを減らしてから実行する、というロジックに変更することで対処しました。細かい点ですが、画像を小さくしてから実行するため、API からのレスポンスは「小さくなった画像に対する顔の位置」になります。アプリケーション側ではアップロードした画像のプレビューが表示されており、その画像にマンホールの位置とサイズを調整して「ハメる」わけですが、元の画像サイズに対するレスポンスにはなっていないので、その辺りも考慮する必要が生じます。

そんなこんなの変更を加えてできあがったのがこちらです:
https://manholizer.mybluemix.net/

2017020701

↑ちとズレてるが・・・


使い方は PC またはスマホのウェブブラウザでアプリケーションサーバー(https://manholizer.mybluemix.net/ とか)にアクセスしてください。で、「参照」ボタンをクリック:
2017020702

 

手元のローカルファイルシステムまたはフォトライブラリ等から画像ファイルを選択します。今回は「フリー素材アイドル」の Mika x Rika さんの画像を使わせていただきました:
顔4


この画像を指定して、しばらく待つと・・・
2017020703


こんな感じになりました:
2017020704


合成用マンホールのデザインとして使っているのは(マンホーラーであればおなじみの)東京都マンホールです:
2017020706


(2つの)顔の位置を識別し、うまく顔ハメになるようマンホールの位置と大きさが調整されて合成されています。またマンホールの色がピンクなのは「女性」であると認識されている様子です(男性の場合は青いマンホール画像を合成します)。また2人の上部に "-17" と表示されていますが、これは推定年齢でふたりとも「17才以下」であると推測されている、ということを意味しています(お二人の年齢は存じ上げませんが、全体的に日本人の顔は若く判定される傾向があるようです):
2017020705



なお、このサービスのソースコードはこちらで公開しています。興味ある方はご自身の環境でもどうぞ:
https://github.com/dotnsf/manholizerDemo


自分の環境に導入して使いたい場合の方法は README.md にも記述していますが、IBM Bluemix の Node.js ランタイムで使う場合であれば、まず Bluemix にログインして、SDK for Node.js ランタイムを1つ作成します(この時の名前を後で使います)。また Visual Recognition サービスインスタンスを生成し、その API KEY を取得しておきます(この値も後で使います)。

次に上記サイトからソースコードをダウンロード&展開するか git clone して、以下の2ファイルを編集します。

1つ目は settings.js です。この中の exports.vr_apikey の値に、上記で取得した自分の Watson Visual Recognition サービスの API Key の値を設定します(以下は API Key が abcdabcdabcdabcdabcdabcd であった場合の例です):
(settings.js)

: exports.vr_apykey = 'abcdabcdabcdabcdabcdabcd'; :

もう1つは manifest.yml です。この中を実際に運用するランタイムの情報に設定します。例えば eu-gb リージョンを使って、my_manholizer という名前のランタイムで運用する場合は以下のようになります(ng リージョンの場合、domain は変更せずにそのままで構いません):
(manifest.yml)

: domain: eu-gb.mybluemix.net name: my_manholizer host: my_manholizer :

この状態で cf コマンドを使ってアプリケーションを push すると、作成した Node.js ランタイム上にマンホライザーがデプロイされます:
$ cf push

IBM Bluemix を使わずに運用する場合は・・・ まあ普通に Node.js をインストールして npm install して使ってください(適当)。


このページのトップヘ