Upload
kiyoshi-sawada
View
48
Download
0
Embed Size (px)
Citation preview
EWD 3トレーニング・コース #31
ewd-xpress で Web および REST
サービスを作るM/Gateway Developments Ltd.
Rob Tweed訳 : 日本ダイナシステム株式会社 嶋 芳成
GT.M 版編集 : 澤田 潔
※ 本稿オリジナルは Cache’ 向けとして編纂
2
REST のための EWD 3 バックエンド
• 2つのアプローチが可能• EWD 3 モジュールを用いて自分の REST を構築
する• ewd-xpress を拡張する
2016/9/9 EWD 3 トレーニング・コース #31
3
REST のための EWD 3 バックエンド
• EWD 3 モジュールを用いて独自の REST を構築する• 例えば ewd-qoper8-vistapc がどのように作ら
れたか• 利点
• EWD 3 の部品の内、本当に必要なものだけを利用し、構造を最適化することができます
• 互換性の問題を考えずに、サード・パーティのモジュールを容易に加えることができます
• 欠点• EWD 3 の部品のインターフェースや統合方法について、
十分に理解している必要があります
2016/9/9 EWD 3 トレーニング・コース #31
4
REST のための EWD 3 バックエンド
• ewd-xpress を拡張する• 利点
• 既に必要なものはすべてインストールされ、正しくインターフェースされ構成されているのでそれを利用できます
• 実際には、他のサード・パーティのモジュールは、非互換性の恐れをあまり考えずに追加することができます
• 単一のバックエンドで、 Web / REST サービスと対話的なアプリケーションを同時にサポートできます
• 欠点• REST / Web サービスのみが必要の場合、対話的なアプ
リケーション用のコードは利用されません ... しかしこれは大したことではないでしょう!
2016/9/9 EWD 3 トレーニング・コース #31
5
REST サービスのための ewd-xpress• 従って、ほとんどの場合、 REST / Web サー
ビスを作るには ewd-xpress を用いるのが最善です
2016/9/9 EWD 3 トレーニング・コース #31
6
ewd-xpress の起動ファイルの例var config = { managementPassword: 'keepThisSecret!', serverName: 'My EWD Server', port: 8080, poolSize: 2, database: { type: ‘gtm', params: { // snip } }};var ewdXpress = require('ewd-xpress').master;ewdXpress.start(config)
2016/9/9 EWD 3 トレーニング・コース #31
7
Express ミドルウェアを利用する必要性
• REST / Web サービスをサポートする要点は、ミドルウェアの Express を追加することです• REST / Web サービスは HTTP(S) 要求と応答を
用います• Express は特定の URL パスの前方部分を監視す
る必要があります• 対応する要求が来たときには、それを処理するた
めワーカー・プロセスに渡す必要があります
2016/9/9 EWD 3 トレーニング・コース #31
8
Express ミドルウェアを利用する必要性
• EWD 3 にはそのようなモジュールが既に用意してあります• ewd-qoper8-express• これは ewd-qoper8 を統合する組み込みのルー
タを含みます
• ewd-xpress はそれを含んでおり、 Ajaxメッセージのためにそれを用いています• REST / Web サービスにおいて直接それを利
用することはありません• しかし、直接利用することもできます
2016/9/9 EWD 3 トレーニング・コース #31
9
ewd-xpress の起動ファイルvar config = { managementPassword: 'keepThisSecret!', serverName: 'My EWD Server', port: 8080, poolSize: 2, database: { type: ‘gtm', params: { // snip } }};var ewdXpress = require('ewd-xpress').master;var exprss = ewdXpress.intercept();
ewdXpress.start(config)
2016/9/9 EWD 3 トレーニング・コース #31
10
ewd-xpress の起動ファイルvar config = { managementPassword: 'keepThisSecret!', serverName: 'My EWD Server', port: 8080, poolSize: 2, database: { type: ‘gtm', params: { // snip } }};var ewdXpress = require('ewd-xpress').master;var exprss = ewdXpress.intercept();
// これで、次のものにアクセスできるようになります// ewd-qoper8 object: exprss.q// ewd-qoper8-xpress: exprss.qx// Express middleware: exprss.app
ewdXpress.start(config)
2016/9/9 EWD 3 トレーニング・コース #31
11
単純な Web サービスvar config = { managementPassword: 'keepThisSecret!', serverName: 'My EWD Server', port: 8080, poolSize: 2, database: { type: ‘gtm', params: { // snip } }};var ewdXpress = require('ewd-xpress').master;var exprss = ewdXpress.intercept();
exprss.app.use('/testWebService', exprss.qx.router());
ewdXpress.start(config)
2016/9/9 EWD 3 トレーニング・コース #31
12
単純な Web サービスvar config = { managementPassword: 'keepThisSecret!', serverName: 'My EWD Server', port: 8080, poolSize: 2, database: { type: ‘gtm', params: { // snip } }};var ewdXpress = require('ewd-xpress').master;var exprss = ewdXpress.intercept();exprss.app.use('/testWebService', exprss.qx.router());
// 「 /testWebService 」 で始まる URL のリクエストを受信すると、それをワーカーに渡し// 「 testWebService 」 という名前のモジュールで処理します
ewdXpress.start(config)
2016/9/9 EWD 3 トレーニング・コース #31
13
試してみましょう
2016/9/9 EWD 3 トレーニング・コース #31
14
適切な HTTP クライアントを利用する• ブラウザは、 GET 要求をテストすることしか
できません• POST や他の要求 ( 例えば DELETE 、 PUT) を
テストする必要もあります
• Web サービスや REST のクライアントが利用できます• Postman• 私は Chrome の Advanced REST Client
(ARC) を用いています
2016/9/9 EWD 3 トレーニング・コース #31
15
試してみましょう
2016/9/9 EWD 3 トレーニング・コース #31
16
エラーを修正する
• もちろん、要求を処理するバックエンド・モジュールをまだ作っていません• モジュール名は URL パスと同じ名前です• ~/ewd3/node_modules/testWebService.js
2016/9/9 EWD 3 トレーニング・コース #31
17
REST を処理するモジュール
• ewd-xpress のメッセージを処理するモジュールに極めて似ています
2016/9/9 EWD 3 トレーニング・コース #31
18
REST を処理するモジュールmodule.exports = {
restModule: true,
handlers: { // 要求の type 毎に異なるハンドラ関数 }};
2016/9/9 EWD 3 トレーニング・コース #31
19
REST を処理するモジュールmodule.exports = {
restModule: true, // ewd-xpress にこれは REST ハンドラだと教えています
handlers: { // 要求の type 毎に異なるハンドラ関数 }};
REST だけでなく、すべての Web サービス要求
2016/9/9 EWD 3 トレーニング・コース #31
20
REST を処理するモジュールmodule.exports = {
restModule: true,
handlers: {
{type}: function(messageObj, finished) { // 受信したこの type の要求に対するハンドラ }
}};
2016/9/9 EWD 3 トレーニング・コース #31
21
REST を処理するモジュールmodule.exports = {
restModule: true,
handlers: {
myType: function(messageObj, finished) { // 受信した myType 要求に対するハンドラ }
}};
2016/9/9 EWD 3 トレーニング・コース #31
22
REST を処理するモジュール~/ewd3/node_modules/testWebService.js
module.exports = {
restModule: true,
handlers: {
myType: function(messageObj, finished) { // 受信した myType 要求に対するハンドラ }
}};
http://192.168.1.100:8080/testWebService/myType
2016/9/9 EWD 3 トレーニング・コース #31
23
REST を処理するモジュール~/ewd3/node_modules/testWebService.js
module.exports = {
restModule: true,
handlers: {
myType: function(messageObj, finished) { // 受信した myType 要求に対するハンドラ // URL の残りの部分は、 messageObj.properties でアクセス可能 }
}};
http://192.168.1.100:8080/testWebService/myType/xyz?a=245&b=rob
2016/9/9 EWD 3 トレーニング・コース #31
24
REST を処理するモジュール~/ewd3/node_modules/testWebService.js
module.exports = {
restModule: true,
handlers: {
myType: function(messageObj, finished) { // 受信したメッセージ・オブジェクトを処理し、応答オブジェクトを生成 // finished() 関数を呼び出して終了しなくてはなりません // - 応答オブジェクトをクライアントに送信 // - ワーカー・プロセスを利用可能なプールに戻す finished(responseObject); }
}};
http://192.168.1.100:8080/testWebService/myType
2016/9/9 EWD 3 トレーニング・コース #31
25
REST を処理するモジュール~/ewd3/node_modules/testWebService.js
module.exports = {
restModule: true,
handlers: {
myType: function(messageObj, finished) { console.log("*** myType messageObj: " + JSON.stringify(messageObj)); finished({ test: 'finished ok' }); } }};
http://192.168.1.100:8080/testWebService/myType
2016/9/9 EWD 3 トレーニング・コース #31
26
試してみましょう
2016/9/9 EWD 3 トレーニング・コース #31
27
ewd-xpress のログをみてみる*** myType messageObj: { "type":"ewd-qoper8-xpress", "path":"/testWebService/myType", "method":"GET", "headers":{ "host":"192.168.1.183:8080", "content-type":"application/json" }, "params":{ "type":"myType" }, "query":{}, "application":"testWebService", "expressType":"myType"}
2016/9/9 EWD 3 トレーニング・コース #31
28
ewd-xpress のログをみてみる*** myType messageObj: { "type":"ewd-qoper8-xpress", "path":"/testWebService/myType", "method":"GET", "headers":{ "host":"192.168.1.183:8080", "content-type":"application/json" }, "params":{ "type":"myType" }, "query":{}, "application":"testWebService", "expressType":"myType"}
2016/9/9 EWD 3 トレーニング・コース #31
受信した HTTP 要求のすべての要素は分解されて、受信したメッセージ・オブジェクトの中で利用可能です
これは ewd-qoper8-express のルータが行っていることです
29
簡単な Web サービスvar config = { managementPassword: 'keepThisSecret!', serverName: 'My EWD Server', port: 8080, poolSize: 2, database: { type: ‘gtm', params: { // snip } }};
var ewdXpress = require('ewd-xpress').master;var xprss = ewdXpress.intercept();exprss.app.use('/testWebService', express.qx.router());
ewdXpress.start(config);
2016/9/9 EWD 3 トレーニング・コース #31
30
ewd-xpress のログをみてみる*** myType messageObj: { "type":"ewd-qoper8-xpress", "path":"/testWebService/myType", "method":"GET", "headers":{ "host":"192.168.1.183:8080", "content-type":"application/json" }, "params":{ "type":"myType" }, "query":{}, "application":"testWebService", "expressType":"myType"}
2016/9/9 EWD 3 トレーニング・コース #31
REST の type は、 expressType というプロパティの中に定義されていますtype プロパティではないことに注意
31
ewd-xpress のログをみてみる*** myType messageObj: { "type":"ewd-qoper8-xpress", "path":"/testWebService/myType", "method":"GET", "headers":{ "host":"192.168.1.183:8080", "content-type":"application/json" }, "params":{ "type":"myType" }, "query":{}, "application":"testWebService", "expressType":"myType"}
2016/9/9 EWD 3 トレーニング・コース #31
REST の type は、 expressType というプロパティの中に定義されていますtype プロパティではないことに注意
しかし、 params.type 経由でも利用可能です
32
ewd-xpress のログをみてみる*** myType messageObj: { "type":"ewd-qoper8-xpress", "path":"/testWebService/myType", "method":"GET", "headers":{ "host":"192.168.1.183:8080", "content-type":"application/json" }, "params":{ "type":"myType" }, "query":{}, "application":"testWebService", "expressType":"myType"}
2016/9/9 EWD 3 トレーニング・コース #31
メソッドを知りたいということがしばしばあります例えば、 GET か POST か
33
ewd-xpress のログをみてみる*** myType messageObj: { "type":"ewd-qoper8-xpress", "path":"/testWebService/myType", "method":"GET", "headers":{ "host":"192.168.1.183:8080", "content-type":"application/json" }, "params":{ "type":"myType" }, "query":{}, "application":"testWebService", "expressType":"myType"}
2016/9/9 EWD 3 トレーニング・コース #31
また、ヘッダーの情報を使いたい場合もあります特に、セキュリティの処理などに使う場合です
34
REST を処理するモジュール~/ewd3/node_modules/testWebService.js
module.exports = {
restModule: true,
handlers: {
myType: function(messageObj, finished) { // 受信した myType 要求に対するハンドラ // URL の残りの部分は、 messageObj.properties でアクセス可能 }
}};
http://192.168.1.100:8080/testWebService/myType/xyz?a=245&b=rob
2016/9/9 EWD 3 トレーニング・コース #31
35
試してみましょう
2016/9/9 EWD 3 トレーニング・コース #31
36
ewd-xpress のログをみてみる*** myType messageObj: { "type":"ewd-qoper8-xpress", "path":"/testWebService/myType/xyz?a=245&b=rob", "method":"GET", "headers":{ "host":"192.168.1.183:8080", "content-type":"application/json" }, "params":{ "0":"xyz", "type":"myType" }, "query":{ "a":"245", "b":"rob" }, "application":"testWebService", "expressType":"myType"}
2016/9/9 EWD 3 トレーニング・コース #31
37
ewd-xpress のログをみてみる*** myType messageObj: { "type":"ewd-qoper8-xpress", "path":"/testWebService/myType/xyz?a=245&b=rob", "method":"GET", "headers":{ "host":"192.168.1.183:8080", "content-type":"application/json" }, "params":{ "0":"xyz", "type":"myType" }, "query":{ "a":"245", "b":"rob" }, "application":"testWebService", "expressType":"myType"}
2016/9/9 EWD 3 トレーニング・コース #31
完全なパスを得ることができます
38
ewd-xpress のログをみてみる*** myType messageObj: { "type":"ewd-qoper8-xpress", "path":"/testWebService/myType/xyz?a=245&b=rob", "method":"GET", "headers":{ "host":"192.168.1.183:8080", "content-type":"application/json" }, "params":{ "0":"xyz", "type":"myType" }, "query":{ "a":"245", "b":"rob" }, "application":"testWebService", "expressType":"myType"}
2016/9/9 EWD 3 トレーニング・コース #31
URL パスの 3 番目の部分は、 params['0'] に入っています
39
ewd-xpress のログをみてみる*** myType messageObj: { "type":"ewd-qoper8-xpress", "path":"/testWebService/myType/xyz?a=245&b=rob", "method":"GET", "headers":{ "host":"192.168.1.183:8080", "content-type":"application/json" }, "params":{ "0":"xyz", "type":"myType" }, "query":{ "a":"245", "b":"rob" }, "application":"testWebService", "expressType":"myType"}
2016/9/9 EWD 3 トレーニング・コース #31
URL の問い合わせ文字列の名前 /値のペアは、パースされて query オブジェクトに入ります
query.a query.b
40
さらに長い URL の場合は?
2016/9/9 EWD 3 トレーニング・コース #31
41
試してみましょう
2016/9/9 EWD 3 トレーニング・コース #31
42
ewd-xpress のログをみてみる*** myType messageObj: { "type":"ewd-qoper8-xpress", "path":"/testWebService/myType/how/about/this?a=245&b=rob", "method":"GET", "headers":{ "host":"192.168.1.183:8080", "content-type":"application/json" }, "params":{ "0":"how/about/this", "type":"myType" }, "query":{ "a":"245", "b":"rob" }, "application":"testWebService", "expressType":"myType"}
2016/9/9 EWD 3 トレーニング・コース #31
URL の追加部分は、すべてひとつにして params['0'] の中に入っています
43
ewd-xpress のログをみてみる*** myType messageObj: { "type":"ewd-qoper8-xpress", "path":"/testWebService/myType/how/about/this?a=245&b=rob", "method":"GET", "headers":{ "host":"192.168.1.183:8080", "content-type":"application/json" }, "params":{ "0":"how/about/this", "type":"myType" }, "query":{ "a":"245", "b":"rob" }, "application":"testWebService", "expressType":"myType"}
2016/9/9 EWD 3 トレーニング・コース #31
URL の追加部分は、すべてひとつにして params['0'] の中に入っています
var pathArr =params['0'].split('/');でパースすることができます
44
POST + 搭載物 (ペイロード ) の処理•搭載物 (ペイロード ) /本体 を送る他の
HTTP メソッドにも次のことが適用されます
2016/9/9 EWD 3 トレーニング・コース #31
45
POST + 搭載物 (ペイロード ) の処理• ewd-xpress はもともと JSONベースの
REST / Web サービスのために設計されました• 搭載物 (ペイロード ) /本体 は JSON でなくては
なりません
• ewd-xpress は実際には、どのような content-type でも処理することができます• しかし、他のバックエンドの NPM モジュールを
追加して構成する必要があります
• ここでは、 application/json のコンテンツに焦点を絞ります
2016/9/9 EWD 3 トレーニング・コース #31
46
試してみましょう
2016/9/9 EWD 3 トレーニング・コース #31
POST メソッド
コンテント・タイプに注意
JSON のペイロード注意 : これは適切な JSON
書式でなくてはならず、 JavaScript の文字列フォーマットではいけません
即ち、名前も値も「 " 」で囲みます
47
試してみましょう
2016/9/9 EWD 3 トレーニング・コース #31
成功した結果
48
ewd-xpress のログでは*** myType messageObj: { "type":"ewd-qoper8-xpress", "path":"/testWebService/myType/", "method":"POST", "headers":{ "host":"192.168.1.183:8080", "content-type":"application/json", "content-length":"30" }, "params":{ "type":"myType" }, "query":{}, "application":"testWebService", "expressType":"myType"}
2016/9/9 EWD 3 トレーニング・コース #31
コンテンツがあったと記録されています
49
ewd-xpress のログでは*** myType messageObj: { "type":"ewd-qoper8-xpress", "path":"/testWebService/myType/", "method":"POST", "headers":{ "host":"192.168.1.183:8080", "content-type":"application/json", "content-length":"30" }, "params":{ "type":"myType" }, "query":{}, "application":"testWebService", "expressType":"myType"}
2016/9/9 EWD 3 トレーニング・コース #31
しかし、本体/ペイロードはどこに?
50
Express のコンテントのパーシング• Express では、搭載物 (ペイロード ) /本体
の中の JSON をパースするには、そのように明確に指示しなくてはなりません• ミドルウェアはそのためにあるのです !
• body-parser という名前の NPM モジュールがあります
• これをインストールしなくてはなりません• 実際には、 ewd-xpress をインストールしたときに、
これは自動的にインストールされています• ~/ewd3/node_modules/body-parser を見てください• しかし、これをロードして利用するには、 ewd-xpress
のスタート・アップファイル内で明確に指定しなくてはなりません
2016/9/9 EWD 3 トレーニング・コース #31
51
ewd-xpress の起動ファイルを編集する
var config = { managementPassword: 'keepThisSecret!', serverName: 'My EWD Server', port: 8080, poolSize: 2, database: { type: ‘gtm', params: { // snip } }};var ewdXpress = require('ewd-xpress').master;var exprss = ewdXpress.intercept();var bodyParser = require('body-parser');exprss.app.use(bodyParser.json());express.app.use('/testWebService', express.qx.router());
ewdXpress.start(config)
2016/9/9 EWD 3 トレーニング・コース #31
52
ewd-xpress を再起動して再試行
2016/9/9 EWD 3 トレーニング・コース #31
53
ewd-xpress のログでは*** myType messageObj: { "type":"ewd-qoper8-xpress", "path":"/testWebService/myType/", "method":"POST", "headers":{ "host":"192.168.1.183:8080", "content-type":"application/json", "content-length":"30" }, "params":{ "type":"myType" }, "query":{}, "body":{ "c":"hello", "d":"world" }, "application":"testWebService", "expressType":"myType"}
2016/9/9 EWD 3 トレーニング・コース #31
本体 (body) の内容は、パースされて、messageObj.body の中で利用できます
54
本体 + 名前/値 のペア?
• POST された HTTP 要求に、本体/搭載物(ペイロード ) だけでなく• もっと URL パスの長いもの• 追加的な 名前/値のペア の問い合わせ文字列
をもつことはできるでしょうか?
2016/9/9 EWD 3 トレーニング・コース #31
55
試してみましょう
2016/9/9 EWD 3 トレーニング・コース #31
試す URL はココ
ペイロードを組み合わせる
正常に稼働している
56
また ewd-xpress のログをチェック*** myType messageObj: { "type":"ewd-qoper8-xpress", "path":"/testWebService/myType/how/about/this?a=123&b=rob", "method":"POST", "headers":{ "host":"192.168.1.183:8080", "content-type":"application/json", "content-length":"30" }, "params":{ "0":"how/about/this", "type":"myType" }, "query":{ "a":"123" "b":"rob" }, "body":{ "c":"hello", "d":"world" }, "application":"testWebService", "expressType":"myType"}
2016/9/9 EWD 3 トレーニング・コース #31
長い URL パスはこちら
57
また ewd-xpress のログをチェック*** myType messageObj: { "type":"ewd-qoper8-xpress", "path":"/testWebService/myType/how/about/this?a=123&b=rob", "method":"POST", "headers":{ "host":"192.168.1.183:8080", "content-type":"application/json", "content-length":"30" }, "params":{ "0":"how/about/this", "type":"myType" }, "query":{ "a":"123" "b":"rob" }, "body":{ "c":"hello", "d":"world" }, "application":"testWebService", "expressType":"myType"}
2016/9/9 EWD 3 トレーニング・コース #31
URL の残りの部分はパースされてここに
58
また ewd-xpress のログをチェック*** myType messageObj: { "type":"ewd-qoper8-xpress", "path":"/testWebService/myType/how/about/this?a=123&b=rob", "method":"POST", "headers":{ "host":"192.168.1.183:8080", "content-type":"application/json", "content-length":"30" }, "params":{ "0":"how/about/this", "type":"myType" }, "query":{ "a":"123" "b":"rob" }, "body":{ "c":"hello", "d":"world" }, "application":"testWebService", "expressType":"myType"}
2016/9/9 EWD 3 トレーニング・コース #31
問い合わせ文字列の 名前/値 のペアを保持する query オブジェクトはここに
59
また ewd-xpress のログをチェック*** myType messageObj: { "type":"ewd-qoper8-xpress", "path":"/testWebService/myType/how/about/this?a=123&b=rob", "method":"POST", "headers":{ "host":"192.168.1.183:8080", "content-type":"application/json", "content-length":"30" }, "params":{ "0":"how/about/this", "type":"myType" }, "query":{ "a":"123" "b":"rob" }, "body":{ "c":"hello", "d":"world" }, "application":"testWebService", "expressType":"myType"}
2016/9/9 EWD 3 トレーニング・コース #31
POST された搭載物 (ペイロード ) を含む body オブジェクトがここに
60
バックエンドの REST type ハンドラ• これで、バックエンドの type ハンドラ関数
には、受信した REST および Web サービスへの要求の処理必要な情報がすべて渡されました• これをどのように処理するかはあなた次第で
す•データベースへは次の方法でアクセスできま
す• this.db
• 例えば this.db.function() で従来の MUMPS コードを呼び出す
• this.documentStore2016/9/9 EWD 3 トレーニング・コース #31
61
例えば• ~/ewd3/node_modules/testWebService.js
module.exports = { restModule: true, handlers: { myType: function(messageObj, finished) { console.log('*** myType messageObj: ' + JSON.stringify(messageObj)); console.log(this.db.version()); var doc = new this.documentStore.DocumentNode('myDoc'); var myObj = { hello: 'world' } doc.setDocument(myObj); finished({ test: 'finished ok' }); } }};
2016/9/9 EWD 3 トレーニング・コース #31
62
バックエンド・モジュールの違いの要点• 次の2つの違い• 対話型アプリケーションにおけるメッセージの処
理に使われるもの• REST / Web サービスの要求の処理に使われるも
の
2016/9/9 EWD 3 トレーニング・コース #31
63
バックエンド・モジュールの違いの要点• ~/ewd3/node_modules/testWebService.js
module.exports = { restModule: true, handlers: { myType: function(messageObj, finished) { console.log('*** myType messageObj: ' + JSON.stringify(messageObj)); finished({ test: 'finished ok' }); } }};
2016/9/9 EWD 3 トレーニング・コース #31
send() 関数なし
EWD セッションなし
64
send 関数がない?
• REST と Web サービスは、 HTTPベース• ひとつの要求がひとつの応答を生成します
• 従って、中間メッセージは不適切です
2016/9/9 EWD 3 トレーニング・コース #31
65
send 関数がない?
• REST と Web サービスは、 HTTPベース• ひとつの要求がひとつの応答を生成します
• 従って、中間メッセージは不適切です• それがあっても良いですが、しかし、ある/すべ
てのブラウザにメッセージを送るハンドラ関数は、web-socket 経由です !
2016/9/9 EWD 3 トレーニング・コース #31
66
EWD セッションがない?
• REST と Web サービスは、ステート (状態 )レスです• アプリケーションにはリンクしません•暗黙的な EWD セッションは不適切です
2016/9/9 EWD 3 トレーニング・コース #31
67
EWD セッションがない?
• REST と Web サービスは、ステート (状態 )レスです• アプリケーションにはリンクしません•暗黙的な EWD セッションは不適切です
• しかし、独自のハンドラで EWD セッションを生成し維持することは可能です• このために ewd-session モジュールを使うこと
ができます• this.sessions
• これが何かというこを知っている必要はあります2016/9/9 EWD 3 トレーニング・コース #31
68
REST / Web サービスでセッションを使う• ログイン要求のハンドラを作ります• セキュリティのためには、 POST 要求である
べきでしょう•ユーザー名、パスワードは本体の搭載物 (ペ
イロード ) 内に定義されるべきです
2016/9/9 EWD 3 トレーニング・コース #31
69
REST / Web サービスでセッションを使うfunction checkLogin(username,password) { if (username !== 'rob') return {error: 'Invalid username'}; if (password !== 'secret') return {error: 'Invalid password'}; return {ok: true}};module.exports = { restModule: true, handlers: { login: function(messageObj, finished) { if (messageObj.method !== 'POST') { finished({error: 'login message must use POST method'}); return; } var username = messageObj.body.username; if (!username || username === '') { finished({error: 'You must provide a username'}); return; } var password = messageObj.body.password; if (!password || password === '') { finished({error: 'You must provide a password'}); return; } var status = checkLogin(username, password); if (status.error) { finished(status); return; } var session = this.sessios.create('testWebService', 3600); session.authenticated = true; finished({token: session.token}); } }}
2016/9/9 EWD 3 トレーニング・コース #31
70
REST / Web サービスでセッションを使うfunction checkLogin(username,password) { if (username !== 'rob') return {error: 'Invalid username'}; if (password !== 'secret') return {error: 'Invalid password'}; return {ok: true}};module.exports = { restModule: true, handlers: { login: function(messageObj, finished) { if (messageObj.method !== 'POST') { finished({error: 'login message must use POST method'}); return; } var username = messageObj.body.username; if (!username || username === '') { finished({error: 'You must provide a username'}); return; } var password = messageObj.body.password; if (!password || password === '') { finished({error: 'You must provide a password'}); return; } var status = checkLogin(username, password); if (status.error) { finished(status); return; } var session = this.sessios.create('testWebService', 3600); session.authenticated = true; finished({token: session.token}); } }}
2016/9/9 EWD 3 トレーニング・コース #31
セキュリティ上の理由で、 POST要求でなくてはなりません
71
REST / Web サービスでセッションを使うfunction checkLogin(username,password) { if (username !== 'rob') return {error: 'Invalid username'}; if (password !== 'secret') return {error: 'Invalid password'}; return {ok: true}};module.exports = { restModule: true, handlers: { login: function(messageObj, finished) { if (messageObj.method !== 'POST') { finished({error: 'login message must use POST method'}); return; } var username = messageObj.body.username; if (!username || username === '') { finished({error: 'You must provide a username'}); return; } var password = messageObj.body.password; if (!password || password === '') { finished({error: 'You must provide a password'}); return; } var status = checkLogin(username, password); if (status.error) { finished(status); return; } var session = this.sessios.create('testWebService', 3600); session.authenticated = true; finished({token: session.token}); } }}
2016/9/9 EWD 3 トレーニング・コース #31
ユーザ名とパスワードは、要求の本体 (body) から得られます
72
REST / Web サービスでセッションを使うfunction checkLogin(username,password) { if (username !== 'rob') return {error: 'Invalid username'}; if (password !== 'secret') return {error: 'Invalid password'}; return {ok: true}};module.exports = { restModule: true, handlers: { login: function(messageObj, finished) { if (messageObj.method !== 'POST') { finished({error: 'login message must use POST method'}); return; } var username = messageObj.body.username; if (!username || username === '') { finished({error: 'You must provide a username'}); return; } var password = messageObj.body.password; if (!password || password === '') { finished({error: 'You must provide a password'}); return; } var status = checkLogin(username, password); if (status.error) { finished(status); return; } var session = this.sessios.create('testWebService', 3600); session.authenticated = true; finished({token: session.token}); } }}
2016/9/9 EWD 3 トレーニング・コース #31
ユーザ名とパスワードの検証
もし正しくなければエラー応答を返す
73
REST / Web サービスでセッションを使うfunction checkLogin(username,password) { if (username !== 'rob') return {error: 'Invalid username'}; if (password !== 'secret') return {error: 'Invalid password'}; return {ok: true}};module.exports = { restModule: true, handlers: { login: function(messageObj, finished) { if (messageObj.method !== 'POST') { finished({error: 'login message must use POST method'}); return; } var username = messageObj.body.username; if (!username || username === '') { finished({error: 'You must provide a username'}); return; } var password = messageObj.body.password; if (!password || password === '') { finished({error: 'You must provide a password'}); return; } var status = checkLogin(username, password); if (status.error) { finished(status); return; } var session = this.sessios.create('testWebService', 3600); session.authenticated = true; finished({token: session.token}); } }}
2016/9/9 EWD 3 トレーニング・コース #31
認証が正しければ、 testWebService という名前のアプリケーション用に、新しい EWD セッションを作ります
このセッションの有効期限は 1 時間です
74
REST / Web サービスでセッションを使うfunction checkLogin(username,password) { if (username !== 'rob') return {error: 'Invalid username'}; if (password !== 'secret') return {error: 'Invalid password'}; return {ok: true}};module.exports = { restModule: true, handlers: { login: function(messageObj, finished) { if (messageObj.method !== 'POST') { finished({error: 'login message must use POST method'}); return; } var username = messageObj.body.username; if (!username || username === '') { finished({error: 'You must provide a username'}); return; } var password = messageObj.body.password; if (!password || password === '') { finished({error: 'You must provide a password'}); return; } var status = checkLogin(username, password); if (status.error) { finished(status); return; } var session = this.sessios.create('testWebService', 3600); session.authenticated = true; finished({token: session.token}); } }}
2016/9/9 EWD 3 トレーニング・コース #31
このセッションは認証済というフラグ
75
REST / Web サービスでセッションを使うfunction checkLogin(username,password) { if (username !== 'rob') return {error: 'Invalid username'}; if (password !== 'secret') return {error: 'Invalid password'}; return {ok: true}};module.exports = { restModule: true, handlers: { login: function(messageObj, finished) { if (messageObj.method !== 'POST') { finished({error: 'login message must use POST method'}); return; } var username = messageObj.body.username; if (!username || username === '') { finished({error: 'You must provide a username'}); return; } var password = messageObj.body.password; if (!password || password === '') { finished({error: 'You must provide a password'}); return; } var status = checkLogin(username, password); if (status.error) { finished(status); return; } var session = this.sessios.create('testWebService', 3600); session.authenticated = true; finished({token: session.token}); } }}
2016/9/9 EWD 3 トレーニング・コース #31
最後に、 /login の応答として、セッション・トークンを返します
76
試してみましょう
2016/9/9 EWD 3 トレーニング・コース #31
ログイン URL
間違ったパスワードを JSON のペイロードと
して POST する
エラー応答
77
試してみましょう
2016/9/9 EWD 3 トレーニング・コース #31
ログイン URL
エラー応答
GET を用い、問い合わせ文字列にユーザ名とパスワードを指定す
る
78
試してみましょう
2016/9/9 EWD 3 トレーニング・コース #31
今回はセッション・トークンが
返される
正しいユーザ名とパスワードを POST のペイ
ロードとして渡す
79
次は何?
• 次に続く要求は、通常ヘッダ内に、セッション・トークンを返します• 例えば、権限付与ヘッダー
• バックエンドの要求ハンドラは、要求を認証しなくてはなりません• トークンが正しく、期限切れでないことをチェッ
クします
• するとハンドラーは、ユーザーが続けている会話の一時データのためにセッションを用いることができます
2016/9/9 EWD 3 トレーニング・コース #31
80
REST / Web サービスでセッションを用いるmodule.exports = { restModule: true, handlers: { login: function(messageObj, finished) { // 以前に示した通り }, doSomethingNext: function(messageObj, finished) { var token = messageOgj.headers.authorization; if (!token || token === '') { finished({error: 'Missing authorization token'}); return; } var status = this.sessions.authenticate(token); if (status.error) { finished(status); return; } var session = status.session; // ユーザの要求の認証に成功した // ユーザーのセッションを適切に用いるためのアクセス権を持っています
// ここで要求のコンテンツ/ペイロードを用いて、すべきことを何でもすることができます // 最後に応答オブジェクト (responseObj) を作ります finished(responseObj); }}
2016/9/9 EWD 3 トレーニング・コース #31
81
REST / Web サービスでセッションを用いるmodule.exports = { restModule: true, handlers: { login: function(messageObj, finished) { // 以前に示した通り }, doSomethingNext: function(messageObj, finished) { var token = messageOgj.headers.authorization; if (!token || token === '') { finished({error: 'Missing authorization token'}); return; } var status = this.sessions.authenticate(token); if (status.error) { finished(status); return; } var session = status.session; // ユーザの要求の認証に成功した // ユーザーのセッションを適切に用いるためのアクセス権を持っています
// ここで要求のコンテンツ/ペイロードを用いて、すべきことを何でもすることができます // 最後に応答オブジェクト (responseObj) を作ります finished(responseObj); }}
2016/9/9 EWD 3 トレーニング・コース #31
要求に権限認証ヘッダが付いていることを確認する
82
REST / Web サービスでセッションを用いるmodule.exports = { restModule: true, handlers: { login: function(messageObj, finished) { // 以前に示した通り }, doSomethingNext: function(messageObj, finished) { var token = messageOgj.headers.authorization; if (!token || token === '') { finished({error: 'Missing authorization token'}); return; } var status = this.sessions.authenticate(token); if (status.error) { finished(status); return; } var session = status.session; // ユーザの要求の認証に成功した // ユーザーのセッションを適切に用いるためのアクセス権を持っています
// ここで要求のコンテンツ/ペイロードを用いて、すべきことを何でもすることができます // 最後に応答オブジェクト (responseObj) を作ります finished(responseObj); }}
2016/9/9 EWD 3 トレーニング・コース #31
セッション・トークンを認証する
83
REST / Web サービスでセッションを用いるmodule.exports = { restModule: true, handlers: { login: function(messageObj, finished) { // 以前に示した通り }, doSomethingNext: function(messageObj, finished) { var token = messageOgj.headers.authorization; if (!token || token === '') { finished({error: 'Missing authorization token'}); return; } var status = this.sessions.authenticate(token); if (status.error) { finished(status); return; } var session = status.session; // ユーザの要求の認証に成功した // ユーザーのセッションを適切に用いるためのアクセス権を持っています
// ここで要求のコンテンツ/ペイロードを用いて、すべきことを何でもすることができます // 最後に応答オブジェクト (responseObj) を作ります finished(responseObj); }}
2016/9/9 EWD 3 トレーニング・コース #31
トークンは正しく、期限切れでもない
従ってユーザーのセッションにアクセスすることができる
84
REST / Web サービスでセッションを用いるmodule.exports = { restModule: true, handlers: { login: function(messageObj, finished) { // 以前に示した通り }, doSomethingNext: function(messageObj, finished) { var token = messageOgj.headers.authorization; if (!token || token === '') { finished({error: 'Missing authorization token'}); return; } var status = this.sessions.authenticate(token); if (status.error) { finished(status); return; } var session = status.session; // ユーザの要求の認証に成功した // ユーザーのセッションを適切に用いるためのアクセス権を持っています
// ここで要求のコンテンツ/ペイロードを用いて、すべきことを何でもすることができます // 最後に応答オブジェクト (responseObj) を作ります finished(responseObj); }}
2016/9/9 EWD 3 トレーニング・コース #31
必要な処理をして、応答オブジェクトを返す
85
試してみましょう
2016/9/9 EWD 3 トレーニング・コース #31
/doSomethingNextという要求を送る
エラー応答
間違った、あるいは期限切れのトー
クン
86
試してみましょう
2016/9/9 EWD 3 トレーニング・コース #31
エラー応答
権限付与するヘッダーが定義されてい
ない
87
試してみましょう
2016/9/9 EWD 3 トレーニング・コース #31
今度は成功
権限付与ヘッダーに正しい token が
ある
88
セッションのログアウト?
•明示的な /logout 要求は必要ありません• セッションは自動的に期限切れになります• もしログアウトしたければ、セッションを非認証にセットすることができます• session.authenticated = false;
2016/9/9 EWD 3 トレーニング・コース #31
89
セッションのログアウト?
• 一旦トークンが認証されセッションが確立すれば、標準的なメッセージ・ハンドラ内でタイムアウトを変更し、有効期限を更新することができます• session.timeout = 600;• session.updateExpiry();
2016/9/9 EWD 3 トレーニング・コース #31
90
REST と Web サービス
• これであなたの ewd-xpress システム上で REST と Web サービスをサポートするために必要なものはすべて揃いました• 同じ ewd-xpress サーバー上で 対話的なアプリ
ケーションをサポートするのに加えてです
• ewd-xpress の起動ファイルに、 REST /Web サービスを、必要ならどれだけでも加え、定義することができます
2016/9/9 EWD 3 トレーニング・コース #31
91
ewd-xpress 起動ファイルの拡張var config = { managementPassword: 'keepThisSecret!', serverName: 'My EWD Server', port: 8080, poolSize: 2, database: { type: ‘gtm', params: { // snip } }};
var ewdXpress = require('ewd-xpress').master,var exprss = ewdXpress.intercept();var bodyParser = require('body-parser');exprss.app.use(bodyParser.json());exprss.app.use('/testWebService', exprss.qx.router());exprss.app.use('/accounts', exprss.qx.router());exprss.app.use('/stockControl', exprss.qx.router());
ewdXpress.start(config);
2016/9/9 EWD 3 トレーニング・コース #31
そして、対応するモジュールを/node_modules の中に作ります
- accounts.js - stockControl.js