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

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

タグ:express

以前に express-ipfilter ライブラリを使って、Node.js アプリの IP アドレスフィルタリングを行うサンプルを紹介しました:
http://dotnsf.blog.jp/archives/1066182158.html

↑ここで紹介したサンプルは一応動くものですが、アプリケーションを IBM Cloud の Cloud Foundry アプリとしてデプロイすると( IP アドレスフィルタリングが)正しく動かないことがわかりました。原因は Cloud Foundry 内のルーティングで x-forwarded-for ヘッダの情報が変わってしまい、正しい IP アドレスを取得できなくなってしまうようでした。

IBM Cloud の Cloud Foundry 環境でもこの IP アドレスフィルタリングを有効にするには、フィルタリングを行う前に Express() の use メソッドを使って、
app.use( 'trust proxy', true );

を呼び出してからフィルタリングを行う必要があります。

(解説)
http://expressjs.com/ja/api.html



 

Node.js を使っていて 404 エラーや 500 エラーなどが発生した場合に表示されるエラーページをカスタマイズできないか、と思って挑戦してみました。

今回挑戦した環境では Node.js にフレームワークの Express と、テンプレートエンジンに EJS を使いました。エラーページを EJS で作ることを想定しています。

まず、JavaScript のコードはこんな感じです:

app.js
// app.js
var express = require( 'express' );
var app = express();

//. EJS テンプレートエンジン
app.set( 'views', __dirname + '/templates' );
app.set( 'view engine', 'ejs' );

//. / へのアクセスは正常にできる
app.get( '/', function( req, res ){
  res.render( 'index', {} );
});

//. /err へのアクセスは 500 エラーとする
app.get( '/err', function( req, res ){
  res.render( 'index', { value: novalue } ); //. novalue 変数が未定義なので 500 エラーが発生する
});

//. 有効なルーティングを上記に記述
//. /, /err 以外のパスは 404 エラー

//. 404 エラーが発生した場合、
app.use( function( req, res, next ){
  res.status( 404 ); //. 404 エラー
  res.render( 'err404', { path: req.path } ); //. 404 エラーが発生したパスをパラメータとして渡す
});

//. 500 エラーが発生した場合、
app.use( function( err, req, res, next ){
  res.status( 500 ); //. 500 エラー
  res.render( 'err500', { error: err } ); //. 500 エラーの内容をパラメータとして渡す
});

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

上記を補足すると、Express でルーティングを2つ定義しています。1つめがドキュメントルート(/)へのアクセスで、この場合は正常な処理として(後述の)index.ejs を表示します。

2つめは /err へのアクセスで、こちらの場合はわざと 500 エラーを発生させています(index.ejs でページを表示させるような記述にしていますが、実際にはこの中で使われている novalue という変数が未定義なので、そこで 500 エラーが発生するようにしています)。

ルーティングとしてはこの2つ(/ と /err)だけを定義しているので、これら以外のパス(例えば /home)にアクセスがあった場合は 404 エラーが発生します。404 エラーが発生した場合の処理としては err404.ejs というテンプレートを使って、アクセスしたパス(/home)が存在していない、という旨のエラーページを表示します。

最後に 500 エラーが発生した場合の処理を定義しています。ここでは err500.ejs というテンプレートを使って、エラーの内容をあわせて表示します。上記で /err ページにアクセスすると 500 エラーが発生するので、この err500.ejs のページが表示されることになります。


次に上記で使うことにした3種類のテンプレート(index.ejs, err404.ejs, err500.ejs)を用意します。それぞれ以下のような内容にしました:

index.ejs
<html>
<head>
<title>index</title>
</head>
<body>
<h1>index</h1>
</body>
</html>
↑単に index と表示されるだけのシンプルなページ


err404.ejs
<html>
<head>
<title>err404</title>
</head>
<body>
<h1>err404</h1>
<%= path %> is not defined nor found.
</body>
</html>
↑ "(エラーがあったパス)is not defined nor found." というエラーメッセージが表示されるページ


err500.ejs
<html>
<head>
<title>err500</title>
</head>
<body>
<h1>err500</h1>
Error: <%= error %>
</body>
</html>
↑ "Error: (エラー内容)" というエラーメッセージが表示されるページ


app.js ではこれらのテンプレートファイルが templates/ フォルダに存在していることを前提に参照するようにしています。したがって templates/ フォルダを作って、その中にこれら3つの .ejs ファイルを格納しておきます。これで準備OK。


実際に動かして試してみました。まずは正常系、/ へのアクセスは普通に問題なくできます:
2018070401


次に /home にアクセスして、わざと 404 エラーを発生させてみた時の画面です。err404.ejs で定義されたテンプレートを使って期待通りにエラーページが表示されています:
2018070402


最後に /err にアクセスして、わざと 500 エラーを発生させてみた時の画面です。こちらも err500.ejs で定義されたテンプレートを使って期待通りにエラー内容が表示されています:
2018070403


上記コードのサンプルをこちらに用意しました:
https://github.com/dotnsf/nodejserror


これで Node.js でもカスタムエラーページを作れることがわかりました。

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 を考えるべきなのでしょうが、今回の話の中では本質的でないので省略して手抜き簡易化しています。ごめんなさい。



「国際化」に対応したウェブアプリケーションを Node.js で作る方法を調べたので、メモ替わりに残しておきます。

ここでの「国際化(internationalization, i18n)」はウェブブラウザで設定した言語によって自動的に英語表記にしたり、日本語にしたり、・・・という切り替えを行えるようなものです。自動翻訳とかそういうものではありません。またブラウザで設定した言語は HTTP リクエスト時に "Accept-Language" ヘッダで送信されることになるので、後述の動作確認は curl コマンドでこのヘッダを指定して行っています。

このような国際化対応アプリケーションを Node.js で、正確には Node.js + Express + EJS の環境で作ってみました。

Node.js で国際化対応アプリケーションを作る場合、i18n というパッケージを使うのが手っ取り早いです:
https://www.npmjs.com/package/i18n

ソースコード(app.js)はこんな感じにしました。余計な部分を削ぎ落として、最小限必要な部分だけを残しています(赤字部分が i18n 関連の箇所です):
//. app.js

var express = require( 'express' ),
    fs = require( 'fs' ),
    ejs = require( 'ejs' ),
    i18n = require( 'i18n' ),
    request = require( 'request' ),
    session = require( 'express-session' ),
    app = express();

var port = 3000;

app.set( 'views', __dirname + '/public' );
app.set( 'view engine', 'ejs' );

i18n.configure({
  locales: ['en', 'ja'],
  directory: __dirname + '/locales'
});
app.use( i18n.init );

app.get( '/', function( req, res ){
  res.render( 'index' );
});

app.listen( port );
console.log( "server starting on " + port + " ..." );

今回は英語(en)と日本語(ja)に対応したアプリケーションにしました。

また '/' にアクセスした時に ejs の index テンプレートを使った画面が表示されるような内容にしています。ちなみに index テンプレート(public/index.ejs)の内容は以下のようになっています:
<html>
<head>
<title><%= __('subject') %></title>
</head>
<body>
<h1><%= __('subject') %></h1>
<hr/>
<%= __('body') %>
</body>
</html>


テンプレート内で subject と body という2つの変数を使った表記を行っています。実際にはこれらの部分に言語設定に合わせた内容が表示されることになります。

そして言語ファイルを以下のように用意します:

(英語用: locales/en.json)
{
  "subject": "subject",
  "body": "body"
}


(日本語用: locales/ja.json)
{
  "subject": "サブジェクト",
  "body": "本文"
}

英語設定で利用した場合、上記の subject 変数部分は "subject", body 変数部分は "body" と表示されます。また日本語設定の場合、それぞれ "サブジェクト" と "本文" となります。


これで準備できました。 npm install して実行(node app)します:
$ npm install
$ node app

確認は別の端末から curl で行いました。まずは Accept-Language を en(英語)にしてアクセス:
$ curl http://localhost:3000/ -H 'Accept-Language: en'

<html>
<head>
<title>subject</title>
</head>
<body>
<h1>subject</h1>
<hr/>
body
</body>
</html>
>

次は日本語設定でアクセスした場合:
$ curl http://localhost:3000/ -H 'Accept-Language: ja'

<html>
<head>
<title>サブジェクト</title>
</head>
<body>
<h1>サブジェクト</h1>
<hr/>
本文
</body>
</html>


期待通りに動いています!


アプリケーションの国際化そのものはこれだけで出来ました。そして問題になるのは「どうやって色んな言語用の JSON リソースファイルを用意するか?」です。1つ1つ翻訳サービスなどを使いながら作る、という方法もありますが、そんな言語リソースファイルの翻訳作業は IBM Cloud の Globalization Pipeline サービスを使うと英語のリソースファイルから各言語に翻訳したリソースファイルをまとめて作ることができてとても便利です。このサービスについては以前のブログで使い方も含めて紹介しているので参照ください:
Globalization Pipeline サービスがリリースされました!


と、最後は宣伝でしたw

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...' );
});




 

このページのトップヘ