2014年8月27日 星期三

Spring 教學(2) - 關於@RequestMapping

上一篇文章介紹了如何使用Spring Boot建立一個簡單的Spring MVC專案,這次針對@RequestMapping 做進一步的說明。如前篇文章所述,@RequestMapping 簡化了傳統J2EE應用程式要在 web.xml 設定檔中,明確的定義每一個 Servlet 所對應的 URL 為何,當專案愈來愈大時,設定檔將變得肥大且不容易維護。
而在 Spring MVC 應用程式,我們可以直接在 @RequestMapping 設定過濾 HTTP Requests 的條件,讓符合條件的 HTTP Requests 送給對應的 Controller/Method 進行處理。而且 @RequestMapping 除了前篇範例可以針對 URL 進行對應之外,尚能針對 HTTP 的各種屬性進行過濾,以下說明 @RequestMapping 的幾種使用方式。

1. 以URL作為對應條件

//指定從/hello會被對應到此hello()方法
@RequestMapping("/hello")
public @ResponseBody String hello(){
    return "Hello World!";
}

2. 加入 HTTP RequestMethod 過濾

//當HTTP Request Method 為 POST 時,由此方法處理
//適合用來開發 RESTful 應用
@RequestMapping(value="/hello", method = RequestMethod.POST)
public @ResponseBody String helloByPOST(){
    return "Hello By POST!";
}

3. 加入 HTTP Header 過濾

//當 HTTP Request 具備此 header 時,由此方法處理
@RequestMapping(value="/hello", headers = {"apiVersion=2.0"})
public @ResponseBody String helloWithHeader(){
    return "Hello With API Version 2";
}

4. 加入 consumes 過濾條件

//當 HTTP Request 的 Content-Type Header 為 application/json 時,由此方法處理
//此為 3. headers 的一種特例
@RequestMapping(value="/hello", consumes = "application/json" )
public @ResponseBody String helloWithConsumes(){
    return "Hello With Consumes";
}

5. 加入 produces 過濾

//當 HTTP Request 的 Accept Header 包含 application/json 時,由此方法處理
//此為 headers 的一種特例
@RequestMapping(value="/hello", produces = "application/json")
public @ResponseBody String helloWithProduces(){
    return "Hello With Produces";
}

6. 加入 HTTP Request 參數過濾

//當 HTTP Request 帶有 name 參數時,由此方法處理
@RequestMapping(value="/hello", params = "name")
public @ResponseBody String helloWithName(
                    @RequestParam("name") String name){
    return "Hello With Name: " + name;
} 

7. 多重過濾條件

//上述幾個參數皆能一次指定多個,例如:
//當 method 為 POST 或 PUT
//且 Content-Type 為 text/plain 或 application/xml
//由此方法處理
@RequestMapping(value="/hello",
        method = {RequestMethod.POST, RequestMethod.PUT},
        consumes = {"text/plain", "application/xml"})
public @ResponseBody String helloWithMultiple(){
    return "Hello By Produces";
}

8. 加入URL參數過濾

//同時指定多個 @PathVariable
@RequestMapping("/hello/{name1}/{name2}")
public @ResponseBody String helloWithTwoPeople(
                @PathVariable("name1") String name1, 
                @PathVariable("name2") String name2){
    return "Hello " + name1 + ", " + name2;
}

9. 以 regular expression 過濾 URL 參數

//當網址符合此格式,且電話號碼符合(0900-000-000)時,以此方法處理
@RequestMapping(
    "/hello/{name}/phone/{phone:[\\d]{4}-[\\d]{3}-[\\d]{3}}")
public @ResponseBody String helloWithPhone(
                @PathVariable("name") String name,
                @PathVariable("phone") String phone){
    return "Hello " + name + ", your phone number is " + phone;
}
請自行撰寫程式試用以了解 Spring MVC 的強大,並可以測試看看當同時滿足兩個方法的條件,或是有衝突時,Spring MVC 會如何處理。另外,在這邊推薦可以安裝 Chrome 瀏覽器的套件 POSTMAN 來輔助測試,可自行指定 HTTP Request Method, Headers, Body 等,相當方便我們進行測試。
POSTMAN

2014年8月16日 星期六

Spring 教學(1) - 從 Spring Boot 開始

這個系列的文章會陸續介紹 Spring Framework 的一些使用方式,並分享平常使用 Spring Framework 的一些經驗。

關於 Spring Framework

先簡單地從Spring Framework的起源開始說起。Spring Framework 是一個應用程式開發框架, 由Rod Johnson於2002年建立,起初建立的目的是要降低企業級應用程式開發的複雜度,作為替代EJB開發框架的另一種選擇。

發展到現在,Spring Framework已經成為一個相當龐大,提供一系列的模組及套件,來協助開發企業級應用。其中,又以依賴注入(DI, Depecdency Injection) 以及 AOP (Aspect Oriented Programming) 可以說是其核心的模組。

想當初還在就讀二技的時候,約莫是2007年左右,尚未真正地開發過任何的企業應用。因緣際會地到一家軟體公司擔任實習工程師,這才接觸到一個大型專案的開發方式。而在實習的期間,第一個要跨過去的龐大門檻就是所謂的SSH框架(Spring Framework、Struts、Hibernate),當初是花了好大一番功夫,才勉強了解 Spring Framework 皮毛,一直到最近幾年在工作上使用,才算真正地清楚 Spring Framework 背後的運作方式。

也因為對初學者而言,Spring Framework不易了解,因此有了 Spring Boot 專案的誕生。Spring Boot 提供了一系列的 starter 專案,讓使用者可以無腦地很簡單地直接套用,不需自己一步步尋找所有需要用到的套件,或建構複雜的POM (Project Object Model)文件,就能建立具備標準企業級應用能力的Java專案。

在此先以 Spring Boot 建立一個Web專案作為範例,本部落格範例專案皆會以 Gradle 作為建構的工具,若較不清楚Gradle的朋友,可以改以Maven作為建構工具,或可以參考Gradle的教學,來了解Gradle的使用方式。

建立Spring Boot Web專案

欲建立 Spring Boot Web 專案,只需要於 build.gradle 中加入一項 dependency (spring-boot-starter-web)。

dependencies {
    compile("org.springframework.boot:spring-boot-starter-web:1.1.5.RELEASE")
}

當建置專案時,gradle即會將所有spring-boot-starter-web相依的套件庫抓下來,讓我們從eclipse上來檢視有多少套件被參考:

spring-boot-starter-web dependencies

您沒看錯!有這麼多的套件被參考,除了Spring Framework的核心套件外,尚包括了jackson、logging(以slf4j作為façade)、embeded Tomcat等套件。

接下來只需撰寫簡單的主程式:

//這邊使用 Java Class 作為設定,而非XML
@Configuration
//啟用 Spring Boot 自動配置,將自動判斷專案使用到的套件,建立相關的設定。
@EnableAutoConfiguration
//自動掃描 Spring Bean 元件
@ComponentScan( basePackages = {"peace.application", "peace.controller"} )
public class WebApplication {
    public static void main(String args[]){
        //執行SpringApplication
        SpringApplication.run(WebApplication.class, args);
    }
}

以及一支 HelloController:

package peace.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

//此類別為Spring Controller元件
@Controller
public class HelloController {

    //透過 @RequestMapping 指定從/hello會被對應到此hello()方法
    @RequestMapping("/hello")
    //透過 @ResponseBody 告知Spring,此函數的回傳值是HTTP Response的本文
    public @ResponseBody String hello(){
        return "Hello World!";
    }
}

撰寫完上述兩支類別後,我們可以直接執行Java Application,執行後可以從console看到印出的訊息,包括Spring Boot的版本以及embeded Tomcat啟動的位置(localhot:8080)。
Spring Boot Console

打開瀏覽器,輸入http://localhost:8080/hello 可以看到「Hello World!」,就完成了最基本的Hello World範例。

Hello World

讓我們略作修正,於HelloController增加一個方法,讓使用者可以指定其想要Hello的對象。

//加入{}對URL進行參數化
@RequestMapping("/hello/{name}")
//加入 @PathVariable 讓Spring自動將對應的URL參數轉換為此方法的參數
public @ResponseBody String hello(@PathVariable("name") String name){
    return "Hello " + name;
}

這時候,我們的程式對使用者提供了兩個介面,包括不指定對象的/hello,會印出Hello World!,以及可以指定對象的/hello/{name},例如於瀏覽器輸入http://localhost:8080/hello/Peace 的結果:
Hello Peace

Spring Framework 做了些什麼事?

  1. 我們透過 @RequestMapping簡化傳統 J2EE 程式要在 web.xml 設定 URL pattern 對應到 Servlet。之所以能夠做到,是因為 Spring Boot 自動註冊了一支DispatcherServlet,來攔截所有的 HTTP Request,並根據開發者在 @RequestMapping 的設定,將使用者的 HTTP Request 轉給對應的方法進行處理,而當使用者要求的URL無法被對應到正確的controller時,則會被轉到/error,顯示錯誤訊息。
  2. 我們透過了 @ResponseBody 告訴 Spring Framework「此方法的回傳值就是網頁的回傳值」。我們簡化了直接使用 HttpServletRespnse 物件來輸出HTML,Spring Framework 直接幫我們將回傳值序列化為 HTTP Response Body。
  3. 我們在 @RequestMapping 加入{}將網址格式化為參數,當Spring Framework偵測到/hello/xxx,而非/hello時,會呼叫第二個方法。而透過 @PathVariable 的搭配,讓Spring Framework 自動地將URL上的參數反序列化為Java物件。
  4. 透過上述兩個簡單的範例,我們可以發現,Spring Framework**簡化**了相當多的開發細節,開發者不需要處理HTTP的序列化/反序列化,讓開發者可以更專心地開發該方法本身的內涵。

以上範例程式碼可至這裏下載

2014年8月1日 星期五

[工作筆記] Tomcat 出現 Weak Cipher 弱點

先前 Tomcat Server 被公司的滲透測試主機掃描到 Weak Cipher 的弱點,
稍微 Google 搜尋一下,就可以查到設定的方式,就可以將不安全的 SSL加密演算法停用。

<connector port="443" maxhttpheadersize="8192" address="127.0.0.1" enablelookups="false" disableuploadtimeout="true" acceptCount="100" scheme="https" secure="true" clientAuth="false" sslProtocol="SSL" ciphers="SSL_RSA_WITH_RC4_128_MD5, SSL_RSA_WITH_RC4_128_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, SSL_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA" keystoreFile="mydomain.key" keystorePass="password" truststoreFile="mytruststore.truststore" truststorePass="password"/>

信心滿滿地設定完成後卻發現沒有效果,忽然想到先前已將Tomcat Connector調整為使用 native connector,
設定方式與一般的connector不同,官方文件寫的又不夠清楚(可能是我太笨了),花了一些時間測試才完成設定,撰寫這份筆記避免以後有類似的情況發生。

<Connector  protocol="HTTP/1.1" port="443"
            scheme="https" secure="true" SSLEnabled="true"
            SSLHonorCipherOrder="true"
            SSLCipherSuite="EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS"
            SSLCertificateChainFile="CertificateChanFilePath"
            SSLCertificateFile="CertificateFilePath"
            SSLCertificateKeyFile="PrivateKeyFilePath"
            SSLPassword="PrivateKeyPassword"/>

關於這些 native connector 的 SSL設定的詳細說明可參考此文件

備註:

  1. 上述 weak cipher 的設定中,使用 native connector 與一般設定的描述方式不同,若使用 native connector,則描述方式可參照 OpenSSL 官方的描述方式。
  2. 有哪些 Weak Cipher 可參考 OWASP 文件的定義,我是直接參考 Qualys 上的設定範例進行設定。
  3. 設定完成後,可透過 Qualys SSL Test 網站來做全面性的檢查,包括最近的 heartbleed 以及 CVE-2014-0224 弱點

掃描結果:
SSL Test Result

2014年7月23日 星期三

領域驅動設計簡介(1) - 貧血的領域模型

領域驅動設計簡介(1) - 貧血的領域模型

目前執行的專案使用 Spring MVC 開發,設計分層式的架構,主要將系統架構分為四個層次:
貧血的系統架構
  1. User Interface Layer (使用者介面層):以HTML/JavaScript,搭配Spring MVC、JSON與後端溝通。
  2. Web Layer (控制器層):以Spring MVC Controller實現MVC架構。基本上Web Layer負責兩件事情,一為與前端互動,提供JSON API等,並將使用者導向目前對應的頁面,二則作為前端HTTP與後端Service溝通的橋樑。
  3. Service Layer (服務層):服務層包裝所有商業邏輯(business logic),將控制器層需要執行的任何「動作」包裝成一個個的「服務」。Web Layer呼叫時不會接觸到任何的商業邏輯,由Service Layer來控制所有商業邏輯,並於需要的時機傳遞至下方的 Persistence Layer。
  4. Persistence Layer (持久層):持久層負責處理領域物件的儲存(Persistence),提供DAO讓服務層呼叫,將物件儲存在實體儲存設備上(如資料庫、檔案)。
  5. Domain Model (領域模型):經過物件導向分析設計後的領域模型,作為各層次間溝通的主要統一結構。基本上這邊的領域模型只會包含屬性以及對應的getter/setter,不會有任何的商業邏輯。而在需要報表查詢等特殊的情況下,會另外建立查詢用的模型,類似Martin Fowler的CQRS模型
上述這個架構的好處是,各領域間的職責分離的相當清楚,以共通的Domain Model貫穿整體架構,各架構之間有一定的模型作為溝通的橋樑。所有的商業邏輯皆被包含在Service Layer之中,任何一層皆可以輕易的做抽換。
然而在開發的過程中,發現所有的商業邏輯皆被集包含在 Service Layer,所有的 Domain Model 被設計的再漂亮,卻因為不包含任何商業邏輯,只是單純的作為資料傳遞用,英雄無用武之地。平時所學的那些物件導向設計原則、設計模式,也沒有過多可以派得上用場的地方。
因此,跑去找了一些相關的,發現Martin Fowler也曾經提出過一個模式 Anemic Domain Model,中文可翻為貧血的領域模型。在這篇文章中,Martin Fowler 淺顯直白的說出這樣的一個模式與物件導向是相馳的,而且因為Domain Model與底層資料庫進行O/R Mapping,但卻又沒有善用它的好處,反而導致開發的成本提高。
今天先提到這邊,下一次將會開始介紹「領域驅動設計(Domain-Driven Design)」。