Play Framework介紹:HTTP路由
HTTP路由(譯者注:Play的路徑映射機(jī)制)組件負(fù)責(zé)將HTTP請(qǐng)求交給對(duì)應(yīng)的action(一個(gè)控制器Controller的公共靜態(tài)方法)處理。
對(duì)于MVC框架來說,一個(gè)HTTP請(qǐng)求可以看成一個(gè)事件。這個(gè)事件包含2方面的信息:
- 請(qǐng)求的路徑(例如 /clients/1542, /photos/list),包括查詢字符串(Query String)。
- HTTP的請(qǐng)求方法 (GET, POST, PUT, DELETE)。
關(guān)于REST
Representational state transfer(簡稱REST)是一種分布式超媒體軟件架構(gòu)風(fēng)格,類似互聯(lián)網(wǎng)。
REST規(guī)定了一些關(guān)鍵的設(shè)計(jì)原則:
- 應(yīng)用的功能分散在各種資源中
- 每個(gè)資源對(duì)應(yīng)一個(gè)唯一的可訪問的資源標(biāo)識(shí)符(URI)
- 所有資源在客戶端和資源之間使用一個(gè)統(tǒng)一的接口來轉(zhuǎn)移狀態(tài)。
如果你使用過HTTP協(xié)議,HTTP協(xié)議的方法(譯者注:GET、POST、PUT和DELETE等)定義了這些接口。訪問資源狀態(tài)使用的協(xié)議有:
- 客戶-服務(wù)器
- 無狀態(tài)性
- 緩存
- 分層
如果應(yīng)用遵循了上述的REST設(shè)計(jì)原則,那么我們稱該應(yīng)用是RESTful的。 使用Play框架很容易構(gòu)建RESTful的應(yīng)用:
- Play路由通過解析URI和HTTP methods,將request請(qǐng)求映射到對(duì)Java方法的調(diào)用?;谡齽t表達(dá)式的URI模式讓你處理起來更加靈活。
- 協(xié)議是無狀態(tài)的,這意味著你不能在服務(wù)端保存2次成功請(qǐng)求之間的任何狀態(tài)。
- Play認(rèn)為HTTP是關(guān)鍵的特性,因此Play讓你可以毫無保留地訪問HTTP的所有信息。
路由文件的語法
conf/routes 文件用于配置路由規(guī)則。這個(gè)文件包含了應(yīng)用的所有路徑映射。每一個(gè)路由配置項(xiàng)由HTTP方法,URI模式和對(duì)應(yīng)的Java調(diào)用組成。
我們看看,一個(gè)路由配置項(xiàng)是這樣子的:
- GET /clients/{id} Clients.show
每一個(gè)路由配置項(xiàng)以一個(gè)HTTP方法開始,后面跟著URI模式,最后是Java調(diào)用的聲明。
你可以給路由文件增加注釋,以 # 開頭。
- # Display a client
- GET /clients/{id} Clients.show
HTTP方法
HTTP方法可以是任何HTTP所支持的有效的方法:
- GET
- POST
- PUT
- DELETE
- HEAD
此外它支持使用 WS 作為action方法,表示一個(gè) WebSocket 請(qǐng)求。
如果使用 * 作為方法,則這個(gè)路由項(xiàng)將和任何HTTP請(qǐng)求方法相匹配。
- * /clients/{id} Clients.show
這個(gè)路由項(xiàng)將匹配以下兩者(譯者注:當(dāng)然也匹配所有其他的HTTP方法):
- GET /clients/1541
- PUT /clients/1212
URI模式
URI模式定義了請(qǐng)求的路徑。請(qǐng)求的路徑可以定義動(dòng)態(tài)URI,動(dòng)態(tài)URI的部分必須包含在 {…} 中。
- /clients/all
將完全匹配:
- /clients/all
但是…
- /clients/{id}
將同時(shí)匹配以下兩者:
- /clients/12121
- /clients/toto
一個(gè)URI模式可以包含一個(gè)以上的動(dòng)態(tài)部分:
- /clients/{id}/accounts/{accountId}
動(dòng)態(tài)部分的默認(rèn)匹配策略是由正則表達(dá)式 /[^/]+/ 來定義的,你也可以為動(dòng)態(tài)部分定義你自己的匹配正則表達(dá)式。
下面這個(gè)正則表達(dá)式只能接受id為數(shù)字的URI請(qǐng)求:
- /clients/{<[0-9]+>id}
下面這個(gè)則只接受id是一個(gè)包含4位到10位小寫字母的URI請(qǐng)求:
- /clients/{<[a-z]{4,10}>id}
總之任何合法的正則表達(dá)式都可以在這里使用。
注意 動(dòng)態(tài)部分在此處是有命名的(譯者注:如上述例子中動(dòng)態(tài)部分命名為id)。稍候Controller控制器可以通過HTTP參數(shù)對(duì)象(Map)獲取此處的動(dòng)態(tài)部分的值(譯者注:即獲取id的實(shí)際值)。 |
Play認(rèn)為斜杠 / 是很重要的,不可忽略。例如,看看下面這個(gè)路由項(xiàng):
- GET /clients Clients.index
它將會(huì)匹配 /clients 但是不會(huì)匹配 /clients/ 。你可以通過在斜杠 / 后加上一個(gè)問號(hào) ? ,讓這個(gè)路由項(xiàng)匹配到URI尾部含有斜杠 / 或者沒有斜杠 / 的兩種情況,例如:
- GET /clients/? Clients.index
上述URI模式的尾部斜杠后面加上一個(gè)問號(hào)表示尾部斜杠是 可選的,除此以外,URI模式 不能 有任何其他的可選部分。 |
聲明Java調(diào)用
路由配置項(xiàng)的最后一部分是Java調(diào)用的聲明,這部分是由一個(gè)action方法的全稱來定義的,并且這個(gè)action方法必須是一個(gè)控制器Controller類中的 public static void 的方法,Controller類必須定義在 controllers 包下,而且必須作為 play.mvc.Controller 的子類。
你可以在 controllers 包下增加自定義的包名,那樣的話你在此處的聲明就需要在Controller類名前加上自定義的包名。 controllers 包本身是固定的,所以在路由項(xiàng)的action聲明中不需要寫出來。
- GET /admin admin.Dashboard.index
指派靜態(tài)的參數(shù)
在某些情況下,你想重用一個(gè)已聲明的action,但是想定義一個(gè)特殊的路由項(xiàng),這個(gè)路由項(xiàng)具有一些特殊的參數(shù)值。
讓我們?cè)谙旅胬又锌匆幌略趺醋觯?/p>
- public static void page(String id) {
- Page page = Page.findById(id);
- render(page);
- }
對(duì)應(yīng)的路由項(xiàng)是:
- GET /pages/{id} Application.page
現(xiàn)在,我想為id為‘home’的頁面(即/pages/home)指定一個(gè)URL別名,我可以使用靜態(tài)參數(shù)定義另外一個(gè)路由項(xiàng):
- GET /home Application.page(id:'home')
- GET /pages/{id} Application.page
當(dāng)page id為‘home’時(shí),上面的兩個(gè)路由項(xiàng)是等價(jià)的。但是,由于第一個(gè)路由項(xiàng)的優(yōu)先級(jí)比第二個(gè)路由項(xiàng)高,所以當(dāng)page ID為‘home’時(shí),請(qǐng)求將匹配到第一個(gè)路由項(xiàng)。
變量和腳本
你可以在 routes 文件中用 ${ … } 的語法來使用變量,也可以用 %{ … } 的語法來使用腳本,就像在模板templates文件里使用一樣。例如:
- %{ context = play.configuration.getProperty('context', '') }%
- # Home page
- GET ${context} Secure.login
- GET ${context}/ Secure.login
另一個(gè)例子可以看CRUD模塊的 routes 文件,它使用 crud.types 標(biāo)簽循環(huán)遍歷所有model類型,為每一個(gè)model類型生成一個(gè)controller的路由項(xiàng)。
路由的優(yōu)先級(jí)
很多路由項(xiàng)可以匹配相同的URL請(qǐng)求,如果有沖突的話,將按照在route文件中聲明的順序,匹配最前面的路由項(xiàng)。
例如:
- GET /clients/all Clients.listAll
- GET /clients/{id} Clients.show
對(duì)于這樣的定義,下面的URI請(qǐng)求:
- /clients/all
將被第一個(gè)路由項(xiàng)攔截,并調(diào)用 Clients.listAll(盡管第二個(gè)路由項(xiàng)也匹配該請(qǐng)求)。
處理靜態(tài)資源
使用 staticDir 作為特殊的action方法,可以將指定的文件目錄公開為靜態(tài)資源文件的容器。
例如:
- GET /public/ staticDir:public
當(dāng)請(qǐng)求路徑與 /public/* 匹配時(shí),Play會(huì)從 /pubic 文件夾目錄中取得靜態(tài)資源文件。
路由優(yōu)先權(quán)也適用于這種靜態(tài)資源的路由項(xiàng)。
反向路由:生成URL
在Java代碼中可以使用Router生成URL,所以你可以將所有URI匹配模式集中地配置在唯一的一個(gè)配置文件中,然后充滿信心地重構(gòu)你的應(yīng)用。
例如,下面的這個(gè)路由配置:
- GET /clients/{id} Clients.show
在你的代碼中,可以根據(jù)Clients.show生成對(duì)應(yīng)的URL:
- map.put("id", 1541);
- String url = Router.reverse("Clients.show", map).url; // GET /clients/1541
由action方法反向生成URL的這個(gè)功能被集成在Play框架的很多組件中,你永遠(yuǎn)不需要直接調(diào)用 Router.reverse 這個(gè)方法。 |
如果增加的參數(shù)不包含在URI匹配模式中,這些參數(shù)會(huì)被附加到請(qǐng)求參數(shù)(Query String)的后面。
- map.put("id", 1541);
- map.put("display", "full");
- String url = Router.reverse("Clients.show", map).url; // GET /clients/1541?display=full
路由對(duì)象Router會(huì)按照優(yōu)先級(jí)的順序找到最符合條件的路由匹配規(guī)則去生成URL。
設(shè)置 Content Types
Play根據(jù) request.format 的值來決定HTTP響應(yīng)的媒體類型 media type 。通過匹配文件后綴名, request.format 的值可以決定要使用的模板文件 template。Play 還會(huì)從 mime-types.properties 文件的映射關(guān)系中,根據(jù)媒體類型 media type 對(duì)應(yīng)的 Content-type ,來決定HTTP響應(yīng)的內(nèi)容類型。
Play默認(rèn)的響應(yīng)格式是 html ,因此控制器方法 index() 默認(rèn)渲染的模板文件將是 index.html 文件。通過各種方式,你可以指定一個(gè)不同的格式,這樣就可以自定義替代的模板。
在調(diào)用 render 方法之前,你可以使用編程的方式設(shè)置響應(yīng)的格式。例如,為了提供一個(gè)媒體類型 media type 為 text/css 的層疊樣式表,你可以這樣做:
- request.format = "css";
但是,更清晰的方式是在 routes 文件中使用URL來指定格式。你可以在路由項(xiàng)中,給控制器的方法添加格式的聲明,以此來設(shè)置響應(yīng)類型。例如,下面的路由項(xiàng)將處理 /index.xml 的請(qǐng)求,設(shè)置 xml 的響應(yīng)格式并且渲染 index.xml 模板文件。
- GET /index.xml Application.index(format:'xml')
類似地:
- GET /stylesheets/dynamic_css css.SiteCSS(format:'css')
對(duì)于像下面這樣的路由項(xiàng),Play也可以直接從請(qǐng)求URL中解析出格式。
- GET /index.{format} Application.index
對(duì)于這個(gè)路由項(xiàng), /index.xml 的請(qǐng)求將使用 xml 的格式并且渲染 XML 的模板文件, /index.txt 的請(qǐng)求將使用 txt 的格式并且渲染簡單文本(plain text)的模板文件。
Play也可以根據(jù)HTTP內(nèi)容協(xié)商(content negotiation)自動(dòng)選擇響應(yīng)的格式。
HTTP內(nèi)容協(xié)商
Play 與其他 RESTful 架構(gòu)的一個(gè)共同點(diǎn)是,直接使用 HTTP 的功能,而不是嘗試隱藏 HTTP 或者在 HTTP 之上添加抽象層。內(nèi)容協(xié)商(Content negotiation )是這樣的一個(gè) HTTP 特性,它允許 HTTP 服務(wù)器根據(jù)不同的 HTTP 客戶端請(qǐng)求的媒體類型(media types),對(duì)同一個(gè) URL ,響應(yīng)不同的媒體類型( media types )。HTTP 客戶端在 Accept 請(qǐng)求頭部中設(shè)置媒體類型(media types),來指定接受的內(nèi)容類型(content types)。例如這樣的請(qǐng)求表示希望得到一個(gè) XML 的響應(yīng):
- Accept: application/xml
客戶端可以指定一個(gè)以上的媒體類型(media type),也可以使用星號(hào)通配符( */* )表示可以接受任何的媒體類型(media type):
- Accept: application/xml, image/png, */*
傳統(tǒng)的 web 瀏覽器大多數(shù)在 Accept 頭部中包含通配符 */* :它們將接受任何的媒體類型(media type),而 Play 會(huì)響應(yīng)默認(rèn)的 HTML 內(nèi)容類型。內(nèi)容協(xié)商(content negotiation)更常用于自定義的客戶端,例如一個(gè)期望得到 JSON 響應(yīng)的 Ajax 請(qǐng)求,或者一個(gè)期望得到 PDF 或 EPUB 格式的文件的電子閱讀器。
在HTTP頭部中設(shè)置Content Type
如果請(qǐng)求的 Accept 頭部中含有 text/html, application/xhtml 或 通配符 */* ,Play 將使用默認(rèn)的格式 html 。但如果沒有通配符 */* ,Play 將不會(huì)使用默認(rèn)的格式。
Play內(nèi)置了幾種支持的格式: html, txt, json and xml。例如,定義一個(gè)控制器方法,渲染一些數(shù)據(jù):
- public static void index() {
- final String name = "Peter Hilton";
- final String organisation = "Lunatech Research";
- final String url = "http://www.lunatech-research.com/";
- render(name, organisation, url);
- }
如果瀏覽器發(fā)送的一個(gè)請(qǐng)求 URL 匹配了這個(gè)方法(例如使用 http://localhost:9000/訪問新創(chuàng)建的Play應(yīng)用),那么 Play 將渲染 index.html 模板文件,因?yàn)闉g覽器請(qǐng)求的 Accept 頭部值含有 text/html。
對(duì)于含有 Accept: text/xml 頭部的請(qǐng)求,Play會(huì)把響應(yīng)格式設(shè)置為 xml ,且渲染 index.xml 模板文件,例如:
- <?xml version="1.0"?>
- <contact>
- <name>${name}</name>
- <organisation>${organisation}</organisation>
- <url>${url}</url>
- </contact>
以控制器的 index() 方法為例,Play 內(nèi)置的 Accept 頭部類型映射的工作原理如下:Play將 accept 請(qǐng)求頭部包含的媒體類型(media type)映射到一種格式(format),從而決定渲染的模板文件。
Accept 頭部 | 格式(Format) | 模板文件名 | 映射關(guān)系 |
---|---|---|---|
null | null | index.html | 格式為null映射到默認(rèn)的模板文件 |
image/png | null | index.html | 媒體類型的文件不通過格式來映射(譯者注:通過靜態(tài)資源目錄來訪問) |
*/*, image/png | html | index.html | 格式為html時(shí)映射到默認(rèn)的媒體類型 |
text/html | html | index.html | 內(nèi)置的格式 |
application/xhtml | html | index.html | 內(nèi)置的格式 |
text/xml | xml | index.xml | 內(nèi)置的格式 |
application/xml | xml | index.xml | 內(nèi)置的格式 |
text/plain | txt | index.txt | 內(nèi)置的格式 |
text/javascript | json | index.json | 內(nèi)置的格式 |
application/json, */* | json | index.json | 內(nèi)置的格式, 忽略默認(rèn)的媒體類型 |
自定義格式
通過檢查請(qǐng)求的頭部,并且相應(yīng)地設(shè)置格式( format ),你可以自定義格式的類型。你只能設(shè)置與 HTTP 請(qǐng)求接受的媒體類型一致的格式。例如,在控制器中所有的請(qǐng)求處理之前,你可以設(shè)置自定義的格式,然后響應(yīng)一個(gè) text/x-vcard 媒體類型的 vCard :
- @Before
- static void setFormat() {
- if (request.headers.get("accept").value().equals("text/x-vcard")) {
- request.format = "vcf";
- }
- }
現(xiàn)在,對(duì)于一個(gè)帶有 Accept: text/x-vcard 頭部的請(qǐng)求,Play 將渲染一個(gè) index.vcf 模板文件,例如:
- BEGIN:VCARD
- VERSION:3.0
- N:${name}
- FN:${name}
- ORG:${organisation}
- URL:${url}
- END:VCARD
原文鏈接:http://play-framework.herokuapp.com/zh/routes
【編輯推薦】