router組件負責將http請求交給對應的Action處理(一個static/public的控制器方法)。
一個http請求在mvc框架裏被當作一個mvc事件看待。這個事件包含了兩個主要信息:
- 包含在查詢字符串裏的請求路徑 (比如/clients/1542, /photos/list)。
- HTTP方法(GET, POST, PUT, DELETE)
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
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 節,以瞭解控制器是如何工作的。