02.HTTP路由

02.HTTP路由

router組件負責將http請求交給對應的Action處理(一個static/public的控制器方法)。

一個http請求在mvc框架裏被當作一個mvc事件看待。這個事件包含了兩個主要信息:

  •  包含在查詢字符串裏的請求路徑 (比如/clients/1542, /photos/list)。
  • HTTP方法(GET, POST, PUT, DELETE)

關於REST

Representational state transfer (表述性狀態轉移REST)是一種應用於分佈式超媒體系統(如www)的軟件架構設計風格(注意:不是標準)。

REST規定了幾個關鍵的設計原則:

  • 應用程序功能都被當作資源看待
  • 每個資源都有唯一的可尋址URI地址
  • 所有的資源共享同一個接口來傳送客戶端和資源間的狀態
  • 其表現形式爲每個超鏈接都不帶有參數

如果你正在使用http,那麼這些接口都是通過一系列可用的http方法進行定義。協議用於訪問屬於下列狀態的資源:

  • 客戶端服務器Client-server
  • 無狀態的Stateless
  • 可緩存的Cacheable
  • 可分層的Layered

如果某個應用程序遵循主要的REST設計原則,那麼這個應用程序就是RESTful的。play框架很容易創建RESTful風格的應用程序:

  • Play router把所有的URI和HTTP方法都進行解析,並把請求路由一一對應到相應的java調用。基於正則表達式的URL範示使這個操作過程更靈活。
  • 協議是stateless的,也就是說你不能在服務器上爲兩個連接的請求存儲任何狀態。
  • Play把http當作關鍵特性進行考慮,因此play框架提供了讓你完全訪問http信息的能力。

routes文件語法

Router使用conf/routes文件作爲配置文件。此文件列出了所有應用程序所需要的路由。每個路由都由一個HTTP方法+ URI 範示來表示一個Java調用。

示例:

GET   /clients/{id}            Clients.show          

每個route開始於一個http方法,接着是一個URL範示,最後一個元素是java調用定義。

你可以使用#爲路由添加一個註釋

# Display a client

GET   /clients/{id}            Clients.show          

HTTP方法

HTTP方法可以是以下幾個http協議支持的任何方法:

  • GET
  • POST
  • PUT
  • DELETE
  • HEAD

這些方法也支持WS(web service)作爲action方法來指明一個WebSocket 請求:

如果使用*作爲http方法,則route將匹配所有的http方法請求,如:

*   /clients/{id}             Clients.show          

路由將接受下面兩個請求:

GET /clients/1541

PUT /clients/1212

URI範示 Pattern

URI範示定義了路由的請求路徑。請求路徑的某些部分可以是動態的。任何動態的部分都必須使用大括號進行界定{…},如:

/clients/all

將精確匹配:

/clients/all

但…

/clients/{id}

可以匹配下面的請求:

/clients/12121

/clients/toto

一個URI範示可以有多個動態部分:

/clients/{id}/accounts/{accountId}

對於動態部分的默認匹配策略使用的是正則表達式 /[^/]+/,因此,我們可以爲動態部分定義自己的正則表達式。

下面的正則表達式只能匹配數字值作爲id:

/clients/{<[0-9]+>id}

下面的表達式只允許4至10個小寫字母作爲id:

/clients/{<[a-z]{4,10}>id}

在這裏,可以使用任何可用的正則表達式。

注意!

凡是被命名了的動態部分,控制器隨後可以從http參數map裏得到動態部分的值。

默認情況下,play會把URL後的反斜線/當作不同的值,比如:

GET    /clients         Clients.index

此路由只會匹配/clients URL路徑,不會匹配/clients/。如果你打算匹配這兩個URL路徑,那麼你需要在路徑後增加一個/?作爲結束標誌,比如:

GET    /clients/?       Clients.index

注意:

在這裏,除了反斜線外,URI範示不能含有任何可選部分。

Java調用定義

route定義的最後一部分就是java調用,通過使用action方法的完整名稱來進行定義。action方法必須是控制類的public static void方法,而且這個控制器類必須定義於controllers包裏,且是play.mvc.Controller的子類。

如果控制器類不在controllers包下,則需要在控制器類名稱前添加java包名稱。默認的controllers包可以直接使用,因此在此包下的控制器不需要單獨指定包名。

GET    /admin             admin.Dashboard.index          

把404當作action來用

你可以直接使用400作爲路由action,用於標記那些應用程序必須忽略的URL路徑,如忽略網站ico請求:

# Ignore favicon requests

GET    /favicon.ico            404

指派靜態參數

在某種情況下,你可能會重複使用一個存在的基於某些參數值的路由,如下:

public static void page(String id) {

    Page page =Page.findById(id);

    render(page);

}

相應的route:

GET   /pages/{id}       Application.page

現在,我打算爲一個id爲’hame’的頁面定義一個URL別名,我可以定義一個具有靜態參數的其他路由:

GET    /home              Application.page(id:'home')

GET   /pages/{id}       Application.page

當頁面的id值爲‘home’時,第一個路由與第二路由造價。但是,既然第一個具有更高優先級,那麼這個路由將用作Application.page方法處理id爲‘home’時的默認路由。

變量和腳本

在路由裏,也可使用${…} 語法來在路由中使用變量,使用%{…}語法來在路由中使用腳本,和你在模板中使用變量和腳本的情況是一樣的,比如:

%{context = play.configuration.getProperty('context', '')}%

 

# Home page

GET   ${context}         Secure.login

GET    ${context}/        Secure.login

另外一個例子就是在CRUD模塊裏的路由文件使用crud.types標籤來循環調用所有的model types,以爲每種類型生成控制器路由定義。

路由優先級

許多路由定義可能會匹配同一個請求,如果產生了衝突,那麼只採用第一個路由(遵照下面的聲明順序)。

比如:

GET   /clients/all       Clients.listAll

GET   /clients/{id}      Clients.show

照此定義,則下面的URL:

/clients/all

將調用第一個路由指定的Clients.listAll方法(那怕第二個路由也同樣匹配這個URL)。

服務器靜態資源

staticDir: mapping

使用特定的action staticDir,可以用來定位每個你打算當作靜態資源容器進行發佈的文件夾。

比如:

GET   /public/          staticDir:public

當提供的請求含有/public/* 路徑時,Play將把應用程序/public目錄下的文件發佈出去。

其優先權和標準的路由優先權相同。

staticFile: mapping

也可直接映射一個URL路徑到一個靜態文件。

# Serve index.html static file for home requests

GET     /home                  staticFile:/public/html/index.html

URL 編碼

由於不可能對URL解碼或重新編碼 (比如你並能確定URL裏的斜線是否就是真正的斜線或%2F), URL應該明確進行編碼。play默認使用UTF-8進行編碼,但你也可以使用ISO-8859-1, UTF-16BE,UTF-16LE, UTF-16當成默認的WebEncoding配置參數。詳見http://download.oracle.com/javase/1.4.2/docs/api/java/nio/charset/Charset.html

比如:

映射/stéphane時可以使用如下方法:

GET /st%C3%A9phane Application.stephane

反轉路由:用於生成某些URL

Router可以用於在一個java調用裏生成一個URL。因此,你就可以把所有的URI範示集中到一個配置文件裏,這樣,在重構應用程序時,你就更有把握了。

比如,下面的路由定義:

GET   /clients/{id}      Clients.show

在代碼裏,同樣可以生成URL,以便調用Clients.show:

map.put("id", 1541);

String url = Router.reverse("Clients.show",map).url;

//結果爲 GET/clients/1541

URL生成器已經被集成到許多框架的組件裏,你根本就不需要使用Router.reverse進行操作。

比如,如果你添加的參數並沒有包含到URI範示裏,那麼這些參數將加到查詢字符串裏:

map.put("id", 1541);

map.put("display", "full");

//結果爲:GET/clients/1541?display=full

String url = Router.reverse("Clients.show",map).url;

優先權順序再次用於查找最特別的路由,以生成URL。

設置內容風格(CSS)

Play選擇依照request.format的值來爲http response選擇合適的media type。 這個值通過文件擴展名確定哪個視圖模板將會被使用,同時設置response的Content-type的值爲Play的mime-types.properties文件映射格式確定的媒體類型。

play請求的默認格式爲html。因此,index()控制器方法的默認模板就是index.html文件。如果指定了一個不同的格式,就需要選擇對應格式的模板。

你可以在調用render方法前以編程的方式設定格式。比如,爲了使用媒體類型爲text/css的CSS,你可這樣處理:

request.format = "css"; 

然而,更清晰的方法就是在rourtes文件裏使用URL來指定格式。你可以通過爲控制器方法指定格式來特定的路由添加格式,比如:下面的路由將處理來自/index.xml的請求,Application.index()方法將設置格式爲xml,並使用index.xml模板進行渲染。

GET   /index.xml        Application.index(format:'xml') 

類似的還有:

GET   /stylesheets/dynamic_css  css.SiteCSS(format:'css')

在下面的路由裏,Play也可直接從URL裏提取格式:

GET   /index.{format}    Application.index

在這個路由裏,/index.xml請求將設置格式爲xml,同時使用XML模板進行渲染; /index.txt請求將使用原始的text模板進行渲染。

Play也可使用http內容協商自動設置格式。

HTTP 內容協商 negotiation

play和其他RESTful架構一樣,直接使用http提供的功能,而不是試着隱藏http或是在其之上放置一個抽象層。http的內容協商(Content negotiation)特性允許http服務器依照http客戶端請求的媒體類型爲同一個URL提供不同的媒體類型media types。客戶端特定的可接受的內容類型是通過Accept header確定的,比如需要一個xml response就定義爲:

Accept: application/xml

客戶端可以指定多個媒體類型,並且可以指定(*/*)來表示可接受任意媒體類型:

Accept: application/xml, image/png, */*

傳統的Web瀏覽器總是在其Accept header裏包含了通配符:這樣,他們就可以接收任意媒體類型,當然包括play提供的默認html類型。內容協商更多是用於定製的客戶端,比如一個要求返回JSON的Ajax請求,或一個e-book閱讀器需要的PDF或EPUB版本的文檔。

從http headers開始設置內容類型

如果Accept header包含了text/html或application/xhtml以及作爲*/*通配符的結果時, Play選擇其默認格式html。如果通配符值爲空時,默認格式將不被選擇。

Play對html, txt, json和 xml媒體格式提供了內建支持。比如,下面定義了一個用於渲染某些數據的控制器方法:

public static void index() {

   final Stringname = "Peter Hilton";

   final Stringorganisation = "Lunatech Research";

   final String url= "http://www.lunatech-research.com/";

   render(name,organisation, url);

}

在一個瀏覽器裏,如果請求的URL映射到這個方法時,那麼play將渲染index.html模板,因爲瀏覽器發送的Accept header裏包含了text/html值。

通過設置請求格式爲xml,Play響應的結果Acceptheader類型爲:text/xml,同時使用index.xml模塊進行渲染,比如:

<?xml version="1.0"?>

<contact>

<name>${name}</name>

<organisation>${organisation}</organisation>

<url>${url}</url>

</contact>

Accept header內建的格式對應的格式以及對應的模板文件見下表(以index()控制器方法爲例):

Accept header

Format

Template file name

Mapping

null

null

index.html

Default template extension for null format

image/png

null

index.html

媒體類型沒有映射到格式

*/*, image/png

html

index.html

默認媒體類型映射到html格式

text/html

html

index.html

Built-in format

application/xhtml

html

index.html

Built-in format

text/xml

xml

index.xml

Built-in format

application/xml

xml

index.xml

Built-in format

text/plain

txt

index.txt

Built-in format

text/javascript

json

index.json

Built-in format

application/json, */*

json

index.json

Built-in format, default media type ignored

定製格式

通過檢查請求header和設置相應用的格式,可以爲自己的自定義類型添加內容協商,因此只需在http請求選擇相應的媒體類型時設置這個自定義格式即可。比如,爲了在控制器裏實現帶有text/x-vcard的vCard功能,需要所有請求處理之前對定製格式進行檢查:

@Before

static void setFormat() {

        if(request.headers.get("accept").value().equals("text/x-vcard")){

               request.format= "vcf";

        }

}

現在,一個Accept: text/x-vcard header請求將會渲染index.vcf模板,比如:

BEGIN:VCARD

VERSION:3.0

N:${name}

FN:${name}

ORG:${organisation}

URL:${url}

END:VCARD 

繼續討論

當Router明確了哪個java調用將用於處理已經接收的http請求,play就會調用這個java調用。見Controllers 節,以瞭解控制器是如何工作的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章