星期二, 8月 09, 2016

Kendo Grid 教學 4 - 解決 REST Service 回應 HTTP 403

在先前「RestTemplate 無法呼叫 Rest Service」的文章中有提到,有些後台被 Spring Security 保護著,並啟用 Cross-site request forgery (CSRF) 的保護機制,並免他人因為知道服務的端點,就可以直接呼叫相關的服務。

Kendo UI 可以透過 Datasource 中 transport 的屬性設定,可以完成資料的讀取及寫入工作。針對新增、修改、刪除及查詢的部分,分別提供 create、update、destory 及 read 的 function 來進行相關作業的處理。
    transport:{
        create:{...},
        update:{...},
        destroy{...},
        read{...}
    }
開發人員可透過上述設定,完成 kendo grid 上 CRUD 的相關動作。

但是,當我們碰上後端具有 CSRF 保護的服務時,可能會出現 「HTTP 403 : Invalid CSRF Token 'null' was found on the request parameter '_csrf' or header 'X-CSRF-TOKEN'」的錯誤訊息。

這代表,我們正常地呼叫後端的服務。以整合 Spring Security 來說,當我們完成登入後,可以從 Request Header 中觀察到,HTTP Request 都會帶著兩個 Cookies 的資訊,分別是 JSESSIONID 及 XSRF-TOKEN 資訊。

然後,我們再看一下錯誤的訊息「HTTP 403 : Invalid CSRF Token 'null' was found on the request parameter '_csrf' or header 'X-CSRF-TOKEN'」。看起來,後端要的服務需要我們的 Header 送一個 X-CSRF-TOKEN 的資訊過去。上網找一下參考資料「Invalid CSRF Token 'null' 解決方案」看起來也是這樣說明沒錯。而且,X-CSRF-TOKEN 的內容跟 XSRF-TOKEN 一樣 (一樣的東西,底層竟然沒有自己處理掉 .... 看來還要找時間看看到底為什麼有這樣的限制)。 

所以,接著要思考的是如何在 Kendo Grid 呼叫後端服務時,將  X-CSRF-TOKEN 動態的塞入。後來,我在 「Transport and basic authentication」找到了解答。我們可以在 create、update、destroy 跟 read 等方法上加上 「beforeSend」的方法來加入 X-CSRF-TOKEN。

beforeSend: function(req){
    req.setRequestHeader('X-XSRF-TOKEN', $cookies.get('XSRF-TOKEN'));
}
如此一來,就可以正常使用後端服務了!

[參考資料]
1. 跨網站請求偽造
2. Invalid CSRF Token 'null' 解決方案
3. Transport and basic authentication

星期一, 8月 08, 2016

RestTemplate 無法呼叫 Rest Service

最近有人問我一個問題,現象是他的應用程式呼叫被 Spring Security 保護的服務時,如果未將 csrf disable,系統就無法正常運作。

Spring Security 啟用 CSRF 後可保護 Server Side 的 Resource,避免跨網域攻擊 (從別的機器發出 Request 以取得後端的資訊)。所以,當 Spring Security 啟用 CSRF 後,我們會在前端的頁面加上

   <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>

以便,證明我們是從同一個網域發送的請求。

然而,這個問題卻不是上述原因造成!第一時間的猜想錯誤,所以我就開始檢視程式碼內容,來看看到底那邊出了問題。

後來,發現在後端的程式中有一段使用 RestTemplate 去呼叫 REST 服務。基本上,如果在內部應該可以直接呼叫相關的物件,而不需要透過 RestTemplate 來進行呼叫 (多繞了一圈)。不過,依照範例來看是個教學程式,所以就先不討論合理性的問題。

RestTemplate 其實是一個增強化的 HTTP Client ,所以在使用 RestTemplate 時我們可以想像自己開了一個新的瀏覽器,然後存取該服務。此時,RestTemplate 中根本就沒有儲存相關的 Session。因為他的 Request 中沒有帶任何經過驗證的資訊。所以,會被 Spring Security 視為不合法的存取。

我們必須在 RestTemplate 中加入一些資訊,才可讓這個呼叫合法。相關資訊,可參考下列參考資訊的網址。

HttpHeaders headers = new HttpHeaders();
headers.add("cookie", req.getHeader("cookie"));
HttpEntity requestEntity = new HttpEntity(null, headers);
ResponseEntity rssResponse =
restTemplate.exchange( your_service_url,
HttpMethod.GET,
requestEntity, List.class);
1. 建立 HttpHeader 物件,並將 request header 中的 cookie 值取出,設定到自己建立的 HttpHeader 中。(因為 Session 就存在這裡)

2. 建立 HttpEntity 物件,並利用先前建立的 HttpHeader 進行初始化。

3. 使用 restTemplate 呼叫後端服務,取得 ResponseEntity 以進行後續邏輯運算


[參考資料]

星期三, 8月 03, 2016

Kendo Grid 教學 3 - 建立新增功能

在 「Kendo Grid 教學 2 - Inline Edit」的文章中提到怎麼樣進行 inline edit 的工作。但是,以先前提供的範例來看,並未交代如何進行新增的作業。

但是,如果有依據參考資料進入 「Kendo Grid Grid Inline Edit Demo」檢視範官方的範例程式的話,應該已經發現如何把「Add New Record」的按鈕加上去。

$scope.mainGridOptions = {
    dataSource: $scope.datasource,
    pageable: true,
         height: 300,
toolbar: ["create"],
    columns: [
     { field: "id",  title: "商品編號", width: "120px"},
     { field: "name", title: "商品名稱", width: "120px" },
      { field: "price", title: "商品價格", width: "120px"},
{ command: ["edit", "destroy"], title: "&nbsp;", width: "250px" }
          ],
editable: 'inline'
};
};
在原本 mainGridOptions 中,加入 toolbar 的元素,並指定要使用 create 功能,就可以像官方的範例,有一個 「Add New Record」的按鈕。

當這個按鈕按下去之後,以 inline 編輯模式來看,Kendo Grid 的第一行會進入編輯模式,讓我們輸入資料後進行新增作業。

[參考資料]
1. Kendo Grid Grid Inline Edit Demo

星期三, 7月 27, 2016

Kendo Grid 教學 2 - Inline Edit

在 「Kendo Grid 教學 1 - 建立列表」的文章中,有兩個主要的變數要設定,以便讓 Kendo Grid 可以運作起來,並透過 Rest Service 拿到列表資訊。分別是 datasource 與 mainGridOptions。

Datasource

其中,datasource 可以看作是 kendo grid 的資料來源。在先前的文章中,設定了 transport 與 schema  兩個主要屬性。

transport


透過 transport 的屬性設定,可以完成資料的讀取及寫入工作。針對新增、修改、刪除及查詢的部分,分別提供 create、update、destory 及 read 的 function 來進行相關作業的處理。
    transport:{
        create:{...},
        update:{...},
        destroy{...},
        read{...}
    }
開發人員可透過上述設定,完成 kendo grid 上 CRUD 的相關動作。

schema

Schema 在 Datasource 中扮演兩個重要的角色:

  1. 透過 schema.model.fields 定義 kendo grid 的資料模型,設定排序 (sorting)、篩選 (filtering),並確保使用對應的編輯元件,例如: 數字輸入框對應到數字型別的資料。
  2. 透過 schema.model.id 定義 kendo grid 中的 id 欄位,以確保新增、修改、刪除等工作可以正確執行。

schema: {
      model: {
           id: "id", 
           fields: {
                 id: { editable: false, nullable: true, defaultValue: null},
                 name: {validation: { required: true }},
                 price: { type: 'number', defaultValue: 0, validation: { required: true, min: 0}}
           }
       }
}



parameterMap

在 Kendo Grid 與後端服務整合的部分,透過設定可以採用 json, jsonp, odata 等不同資料模式。所以,在請求資料 (request) 真的往後送之前,我們需要透過 parameterMap function 幫我們處理資料的轉置問題。

下列為,parameterMap 中的範例程式。因為 read 方法不需要傳參數即可拿到資料,所以在範例程式中,我們把 read 方法過濾掉。然而,在 create, update 及 destory 的部分,我們將前端 Form 內的第一個元素取出 (因為 options.models 預設是 array),並將其當作參數往後面的服務拋送。

parameterMap: function(options, operation) {
if (operation !== "read" && options.models) {
        return JSON.stringify(options.models[0]);
     }
 }


mainGridOptions


在前一篇文章中,我們有看到下列 mainGridOptions 之設定。設定的內容可以進一步分成幾個區塊。


  1. 設定 datasource,作為 kendo grid 的資料來源
  2. 利用 pageable 設定是否分頁
  3. 透過 height 設定 kendo grid 的高度
  4. 透過 columns 設定 kendo grid 的表頭應該呈現什麼
    4.1. filed 的設定會與 datasource 底下 schema 內的 fields 做呼應,相同名稱的會對在一起,是一種透過設定抓相對應資料的概念。
  5. columns 中還有一個設定為 command,在每一列上會顯示編輯 (edit) 與刪除 (destory) 的按鈕
  6. editable 設定為 inline 代表該 grid 會使用 inline 的方式進行編輯作業
      其中,標紅色字樣的部分是本次要做 inline edit 新加的部分


$scope.mainGridOptions = {
    dataSource: $scope.datasource,
    pageable: true,
         height: 300,
    columns: [
     { field: "id",  title: "商品編號", width: "120px"},
     { field: "name", title: "商品名稱", width: "120px" },
      { field: "price", title: "商品價格", width: "120px"},
{ command: ["edit", "destroy"], title: "&nbsp;", width: "250px" }
          ],
editable: 'inline'
};
};

建立對應新增、修改、刪除、查詢的方法

在本文前面有提到 datasource 中的 transport 就是讓我們進行 create、update、destory 及 read 的 function 撰寫。

在本文中,以 update 為例:

1. url : 記錄遠端服務的端點
2. type:  使用 HTTP  通訊協定的方法 (例如:GET, PUT, POST, DELETE 等)
3. contentType 傳輸內容的類型
4. complete function: 服務呼叫成功後,要做的相關動作可以寫在此處
5. error function: 服務呼叫失敗後,要做的相關動作可以寫在此處

update: {
     url: "your service endpoint",
     type: "PUT",
     dataType: "json",
     contentType: 'application/json',
     complete: function (data) {
// 呼叫服務成功後,要做什麼事
     },
     error: function (xhr, error) {
         // 呼叫服務失敗後,要做什麼事
     }
}

其他,關於 destory 及 create 也是採用相同的方式進行撰寫。雖然,在這裡並沒有說明如何建立一筆新的資料。但,後面還會有其他文章提到。

備註:
我們做 Rest Service 的時候,可能經常將 create、update 及 delete 的 method 預設為不回傳值。以 Java 來說,就是宣告回傳的是 void。

但是,這樣可能會導致 kendo grid 不 work。所以,我們必須回傳一個 JSON 字串回來。針對,create 及 update 的方法,需要回傳新增或是更新的那個物件回來。避免 kendo grid 呈現一個空白列在那邊。

有關 inline edit 的實際 demo,可以進一步參考官網上 Inline Edit 的範例。

「參考資料」
1. Kendo Grid CRUD Data Operation
2. Kendo Grid Grid Inline Edit Demo

Kendo Grid 教學 1 - 建立列表

近年來,前端 javascript 的技術突飛猛進,有許多的 framework 出現,學都學不完。而在,公司的專案需求上,我們需要開始研究 Kendo UI 這套框架。

Grid View 是我們建置 Web 應用程式中相當常用的 UI 元件。記得十多年前,同時接觸 .NET 與 Java 時,總覺得為什麼 .NET 上做一個 Grid View 相當容易,而在 Java 上卻需要自己寫那麼多程式。一個 Page 一個 Page  一直寫著重覆類似的程式碼。

雖然知道,這裡應該要被元件化,但始終沒有下手去做這類整理的事。那時候,還是以 Server Page 為主的時代。AJAX 的興起是在那之後兩三年的事情。

而到了現在,檯面上已經有非常多這種前端的元件,而且透過 javascript 技術的發展。現在,前端的頁面已經鮮少使用 Server Page 相關的 Tag 。而改由 HTML5 + javascript + css 來進行所有的事情。至於後端採用什麼語言 (C#, Java or PHP) 似乎已經沒那麼重要,也徹底實現 Web 端的 MVC 概念。

底下,我們就建立一系列的文章,來探討使用 Kendo UI 的心路歷程:

1. 下載 kendo ui 的相關套件 Kendo UI Download Free Trial
 
解壓縮後,將套件內的 js 與  styles 資料夾放到專案目錄中。以我自己的範例來看,這兩個資料夾放在 [your project]/webapp/resources/lib/ui/ 底下。

2. 下載 kendo.all.min.js

我是認為 kendo.all.min.js 應該涵蓋在第一步驟中所下載的內容裡。但是,不知道為什麼沒有。可能這樣可以收一點顧問費,畢竟它是需要付費的!所以,只好從官網的範例偷偷把 js 檔載回來。一樣放在 [your project]/webapp/resources/lib/ui/ 底下。

此使,該目錄下有 js 及 styles 兩個資料夾再加上 kendo.all.min.js 這個檔案。

3. 建立後端 RESTFUL Services,讓 Service 回傳下列這串 JSON 訊息回來

[
  {"id":4,"name":"Starbucks","price":300},
  {"id":5,"name":"BrownCafe","price":200},
  {"id":6,"name":"CityCafe","price":100}
]
4. 撰寫前端頁面

4.1. CSS 與 JS 引用之宣告

<link rel="stylesheet" href="./resources/scripts/lib/ui/styles/kendo.common.min.css" />
<link rel="stylesheet" href="./resources/scripts/lib/ui/styles/kendo.default.min.css" />
<link rel="stylesheet" href="./resources/scripts/lib/ui/styles/kendo.default.mobile.min.css" />
<script src="./resources/scripts/lib/ui/js/jquery.min.js"></script>
<script src="./resources/scripts/lib/ui/js/angular.min.js"></script>
<script src="./resources/scripts/lib/ui/kendo.all.min.js"></script>
4.2. 在 HTML 頁面上,宣告要使用 Kendo Grid (以 AngularJS 為範例,官網上還有許多 JQuery 的範例)

<div id="example" >
    <div ng-controller="MyCtrl">
        <kendo-grid options="mainGridOptions">
        </kendo-grid>
    </div>
</div>
4.3. AngularJS

4.3.1. kendo UI 元件在 AngularJS 中被實作成 directives 所以當我們要使用 kendo ui 時需要注入 kendo.directives
var app = angular.module('myApp', ['kendo.directives']);
4.3.2. Controller

app.controller('MyCtrl', function($scope){
$scope.datasource = {
transport: {
read: {
url: [your service address]
dataType: "json"
}
},
schema: {  
model: {
                 fields: {
                    id: { editable: false},
                    name: {validation: { required: true }},
                        price: { type: 'number', defaultValue: 0, validation: { required: true, min: 0}}
                    }
                }
            }
};
$scope.mainGridOptions = {
    dataSource: $scope.datasource,
    pageable: true,
         height: 300,
    columns: [
     { field: "id",  title: "商品編號", width: "120px"},
     { field: "name", title: "商品名稱", width: "120px" },
      { field: "price", title: "商品價格", width: "120px"},
          ]
};
};
依據此方式,即可完成下列表格之設計。雖然,做了很多事才弄出這張表。但是,使用這個元件可以讓後續新增、修改跟刪除的 UI 操作快很多,只需要再加上一些設定。




[參考資料]
1. Kendo UI - Grid Example

星期一, 7月 25, 2016

IBM MobileFirst 7.1 DirectUpdate 失效


最近,有客戶反應他們的 MobileFirst Server 升級到 7.1 後,Direct Update 僅能更新一次,之後就無法再進行更新。

因為客戶說,他們當天要準備上線作業,這個功能必須能正常運作。所以,我跟同事就開始研究問題可能發生的原因。

根據客戶描述,一開始的時候是部署 wlapp 到 MobileFirst Console 時會出現警告訊息如下:


FWLSE3210W: Environment: android of application [YOUR Application] 
version 1.1 has been deployed with a different version of the native 
MobileFirst SDK. 

Direct updates will no longer be available for existing clients with 
other versions of the MobileFirst SDK. 

To Continue to use direct updates, increment the app version, publish it 
to the public app store, deploy to the server, and (optionally) block/notify 
older versions of the app to enforce customers to upgrade to the new version 
from the app store.

針對上述部分,需要修改 wlapp 的版本。因為 version 1.1 的時候是使用 MobileFirst 先前的版本進行打包。依據 IBM 對於 Direct Update 的定義來看,如果有 Native SDK 更新的話,是必須整個 App 重新下載安裝,而非僅更新 Web Content 就可以。

而升級的時候,IBM MobileFirst 底層的 SDK 有可能被更換掉,所以會出現上述訊息。故,我們將 version 1.1 換成 1.2。如此,在部署 wlapp 時,就不會有 FWLSE3210W 的警告訊息。

但是,上述問題解決後。客戶驗證 Direct Update 的系統發現還是只能更新一次。所以,問題實際上還沒有被解決。

後來,我們又發現 worklight.properties 檔案中,有一個參數是 wl.realm.expiration.directUpdate 的值預設為 3600 (一小時內,Direct Update 不會再生效)。在舊版的值似乎是 -1。但是,目前設定 -1  MobileFirst Server 又無法正常啟動。所以,只好設定個 60 秒 (反正改程式應該都會超過一分鐘吧!)。

修改後,重新更新 MobileFirst Server 的 Runtime WAR 檔之後,Direct Update 的更新頻率就換成1分鐘後可重覆更新的模式。

[參考資料]
1. The direct update doesn't work in MobileFirst Studio v7.1