Ch5 : Play Request & Response
[Request & Response 321]
上一個章節我們介紹了Play程式應該怎樣寫網站服務。基本上常見網站用途,像有取得固定頁面資訊,或者需要填寫表單資訊後,成功導到成功頁面,失敗導到失敗頁面或者在原畫面顯示錯誤資訊。這些都會經過Play接收到使用者的Resquest經過Play處理後,會Response出一個結果,以下就簡單介紹常見的Request & Response來練習Play網站吧!!
專案參考 : https://github.com/loveu8/playMyBtaisMariaDB/tree/Ch5
Keeping trial and error to learn everything. You can do more than you think.
[Request & Response]
常見的HTTP Method中,我挑選最使用的GET與POST來說明Play應該怎樣寫網站服務,範例也都很簡單,在這之前首先我們在app/controller
新增Application.java,來寫後續相關的服務。也請記得要把我們第三章建立的專案給啟動起來activator run
。
note:一般來說可以把Play專案run起來,如果發現有重新編輯程式的話,可以呼叫目前在routes寫好的url服務即可,Play發現到程式有變動時,會自己重新編譯程式。若是不喜歡頻繁的編譯,也可以選擇寫完後,在運行compile指令後,確認沒問題,再運行run指令來啟動Play。
package controllers;
import play.mvc.Controller;
import play.mvc.Result;
public class Application extends Controller{
}
Play with HTTP GET
1.用GET取得網址後,顯示網頁文字。
app/controller/Application.java
public Result index() {
// 這邊單純回傳字串到網頁上
return ok("Hi");
}
conf/routes
# 輸入: http://127.0.0.1/home 取得Application.index回傳結果
GET /home controllers.Application.index
Request
GET : http://127.0.0.1/home
Response
Hi
2.如果需要用HTTP GET傳遞參數時,可以使用query string方式傳遞參數,query string大多時候都是用類似於key-value的方式來傳遞參數,或者有時候只有value。
app/controller/Application.java
public Result index() {
// 這邊接收來至於前端傳來的姓名後,回傳歡迎資訊到頁面上
public Result name(String name) {
return ok("Hi! " + name + " , welcome!!");
}
}
conf/routes
# query string 的 key 要注意要跟後面controller的變數名稱相同,才能讓routes接收到正確的參數
# ex url : http://127.0.0.1:9000/user?name=Mary
GET /user controllers.Application.name(name : String)
# 這種方式比較適合,query string只有一個的時候,:name,後面就是要指定的參數
# ex url : http://127.0.0.1:9000/user/Tom
GET /user/:name controllers.Application.name(name : String)
Request
-以下是常見兩種不同的HTTP GET 取得 query string寫法,最後都可以達到一樣的效果。
GET : http://127.0.0.1/user/Tom
-
GET : http://127.0.0.1/user?name=Mary
Response
Hi! Tom , welcome!!
-
Hi! Mary , welcome!!
3.如果要使用HTTP GET時,有時候需要比較多的query string,一樣可以仿照上面的範例下去寫。
note :如果是用key-value方式下去寫,每個parameter之間要用&符號接下一個parameter。
ex url : http://127.0.0.1:9000/celebrate?name=Tom&birthday=20160101
app/controller/Application.java
public Result userBirthday(String name ,int birthday ){
return ok(name + "'s birthday is " + birthday);
}
conf/routes
GET /celebrate controllers.Application.userBirthday(name : String , birthday : Int )
Request
GET : http://127.0.0.1:9000/celebrate?name=Tom&birthday=20160101
Response
Tom's birthday is 20160101
4.如果你想要吐出Json格式的資料,可以利用Play本身內建的方法,達到以下效果。
note:Play內建的play.libs.Json物件,可以幫助你把各種型態的物件,轉換成Json格式輸出,是很方便的工具。
ex url : http://127.0.0.1:9000/userJson/Tom
app/controller/Application.java
public Result nameJson(String name) {
Map <String , String> user = new HashMap<String , String>();
user.put("name", name);
return ok(Json.toJson(user));
}
conf/routes
GET /userJson/:name controllers.Application.nameJson(name : String)
Request
GET : http://127.0.0.1:9000/userJson/Tom
Response
{"name":"Tom"}
Play with HTTP POST
1.我們會寫出三個方法在conf/routes裡
a.GET取得帳號登入頁面
b.表單送出後POST檢查帳號登入是否正確
c.防呆的GET方法。
接下來我們看Play下HTTP POST是怎樣運作的。
我們帳號登入的流程是,先取得登入頁面,輸入帳號與密碼後,檢查登入資訊是否正確,正確時導到登入成功頁面,失敗導入失敗頁面。我們要先在app/views/
在package新增auth資料夾,把登入相關頁面放到這個目錄下。
ex url : http://127.0.0.1:9000/login
app/views/auth/login.scala.html(在app/views/auth目錄下新增login.scala.html)
@** 這是play在scala.html的註解 **@
@**
* 我們特別注意到action的網址,其實是play roues 與 contoller 搭配寫出的服務,缺一都不可,這樣
* 可以藉由play幫我們找出要執行的方法
* 注意到html上input欄位的name,需要跟controller要取出表單的參數(key)同名,才能正確取得資料
**@
<form action="@controllers.routes.Application.auth.url" method="post">
帳號 <input type="text" autocomplete="off" name="account" ></input><br>
密碼 <input type="password" autocomplete="off" name="password"></input><br>
<input type="submit" value="登入">
</form>
app/views/auth/ok.scala.html(在app/views/auth目錄下新增ok.scala.html)
登入成功
app/views/auth/fail.scala.html(在app/views/auth目錄下新增fail.scala.html)
登入失敗
app/controller/Application.java
// 請注意controller前面需要import views.html.auth.*下的html檔案
import views.html.auth.*;
...
// 登入頁面
public Result login( ){
return ok(login.render());
}
// 導頁到登入頁面
public Result changeToLogin( ){
return redirect(controllers.routes.Application.login().url());
}
@Inject
// 相依性注入Play的FormFactory,可參考reference介紹Injection
FormFactory formFactory;
// 開始檢查登入資訊
public Result auth(){
DynamicForm requestData = formFactory.form().bindFromRequest(); // 動態取得頁面上的表單
Map <String , String> formData = requestData.get().getData(); // 把form轉換成Map物件
String account = formData.get("account"); // 根據表單欄位的name名稱,取出表單裡面的值
String password = formData.get("password");
play.Logger.info("account = " + account + " , password = " + password); // 使用內建play logger 印出表單資料
// 預設帳號密碼是tom , 密碼123
if("tom".equals(account) && "123".equals(password)){ // 測試帳號密碼是否正確
// 成功導到成功頁面
return ok(ok.render());
} else {
// 失敗導到失敗頁面
return ok(fail.render());
}
}
conf/routes
#取得登入頁面,預設不給字串
GET /advLogin controllers.Application.advLogin(errorMessage : String ?="")
#帳號登入防呆
GET /advAuth controllers.Application.advChangeToLogin()
#帳號登入防呆,以免有人用GET方式呼叫advAuth檢查
POST /advAuth controllers.Application.advAuth()
Request
GET : http://127.0.0.1:9000/login <---取得登入頁面
POST : http://127.0.0.1:9000/auth <---輸入完帳號密碼後,POST到這個網址的服務,檢查是否正確
GET : http://127.0.0.1:9000/auth <---防呆設計,會導頁到原本的登入頁
Response
成功 : 登入成功
失敗 : 登入失敗
以上範例示範了使用DynamicForm與FormFactory協助我們取得頁面上的表單資料。而實際帳號登入,需要跟資料庫查詢確認後才算完成,這邊用簡單用字串比對方式,達到我們要練習的效果。
2.接下來,我們要改善Play取得表單資訊的方法,主要是Play支援把網頁表單資料與物件一對一的欄位對映,就不需要使用Map取得key的方式,取出表單value(欄位值),可以直接使用物件對應出的欄位。這邊也會示範帳號輸入錯誤時,怎樣回傳錯誤訊息提示使用者。
在app\pojo\login
,按造目錄階層,依序新增pojo與login的package來擺放我們得物件User
,裡面
有兩個屬性是account與password,這邊為了方便介紹,我把帳號驗證的檢查也放在User
裡,建議之後可以獨立成一個Class專門做檢查帳號登入動作即可。
package pojo.login;
public class User {
private String account;
private String password;
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
// 簡單驗證帳號密碼
public String validate(String dbAccount , String dbPassword) {
if (nullOrEmptyCheck(account, password)) {
return "請輸入帳號與密碼";
}
if(!accountCheck(dbAccount ,dbPassword , account , password)){
return "請確認帳號與密碼是否輸入正確";
}
// 通過檢查,回傳pass
return "pass";
}
// 撿查使用者,有沒有故意不輸入
private boolean nullOrEmptyCheck(String account2, String password2) {
return account2 == null || password2 == null || "".equals(account2) || "".equals(password2);
}
// 資料庫查尋到的帳號密碼與使用者輸入比對
private boolean accountCheck(String dbAccount , String dbPassword , String userAccount , String userPassword){
return dbAccount != null && dbPassword != null && "".equals(dbAccount) && "".equals(dbPassword)
&& dbAccount.equals(userAccount) && dbPassword.equals(userPassword);
}
}
ex url : http://127.0.0.1:9000/advLogin
app/views/auth/advLogin.scala.html(在app/views/auth目錄下新增advLogin.scala.html)
@** errorMessage 用來提示帳號登入錯誤資訊,若是沒有錯誤資訊不會顯示出來 **@
@(errorMessage : String)
<form action='@controllers.routes.Application.advAuth.url' method="post">
帳號 <input type="text" autocomplete="off" name="account" ></input><br>
密碼 <input type="password" autocomplete="off" name="password"></input><br>
@** Play可以在xxx.scala.html,寫scala語言程式,前面需要多增加"@"符號才可以使用**@
@if(!"".equals(errorMessage)){
<span style="color:red;">@errorMessage</span><br>
}
<input type="submit" value="登入">
</form>
app/views/auth/advOk.scala.html(在app/views/auth目錄下新增advOk.scala.html)
登入成功
app/controller/Application.java
// 請注意controller前面需要import views.html.auth.*下的html檔案
import views.html.auth.*;
...
// 登入頁面
public Result advLogin(String errorMessage){
return ok(advLogin.render(""));
}
// 導頁到登入頁面
public Result advChangeToLogin(){
return redirect(controllers.routes.Application.advLogin("").url());
}
// 開始檢查登入資訊
public Result auth(){
User user = formFactory.form(User.class).bindFromRequest().get(); // 使用Play內建formFactory來對應到User物件
String account = user.getAccount(); // 找出對應物件後,就可以取得表單所儲存的值
String password = user.getPassword();
play.Logger.info("account = " + account + " , password = " + password); // 使用內建play logger 印出表單資料
// 預設帳號密碼是tom , 密碼123 , 測試帳號密碼是否正確
if("pass".equals(user.validate("tom","123"))){
return ok(advOk.render());
} else {
// 導回到登入頁面
String errorMessage = user.validate("tom","123");
play.Logger.info("login errorMessage = " + errorMessage); // log 印出 錯誤訊息
return ok(advLogin.render(errorMessage)); // 回傳檢查後錯誤的資訊
}
}
conf/routes
#取得登入頁面
GET /advLogin controllers.Application.advLogin()
#帳號登入防呆,以免有人用GET方式呼叫advAuth檢查
GET /advAuth controllers.Application.advChangeToLogin()
#帳號登入撿查
POST /advAuth controllers.Application.advAuth()
Request
GET : http://127.0.0.1:9000/advLogin <---取得登入頁面
POST : http://127.0.0.1:9000/advAuth <---輸入完帳號密碼後,POST到這個網址的服務,檢查是否正確
GET : http://127.0.0.1:9000/advAuth <---防呆設計,會導頁到原本的登入頁
Response
成功 : 登入成功
失敗 - 沒有輸入帳號與密碼
失敗 - 錯誤的帳號與密碼
[Final]
到這邊Play的Request & Response都練習過一次了,對於Play寫網站服務會有基本概念。下面會再說明一次這次練習使用到Play幾個關鍵技術或內建方法,建議多多實作與練習,會更熟悉Play的開發方式。
1.play.libs.Json協助我們把物件轉換Json格式,也可以幫助我們parse進來的Json Request。Play使用的Json核心是jackson,可以參閱官方Play API會有更多實用的介紹。
2.HTTP POST Form的action使用到Play reverse-routing的技術,它可以協助我們去觸發我們寫好的Play服務,我們可以透過這個方式取得Play實際的路徑,好處是不用再自己手動寫URL,減少不必要的錯誤。
ex : @controllers.routes.Application.advAuth.url
檢視原始碼後會發現是對應出來的抽象路徑
<form action='/advAuth' method="post"> <----被轉換過的網址
帳號 <input type="text" autocomplete="off" name="account" ></input><br>
密碼 <input type="password" autocomplete="off" name="password"></input><br>
<input type="submit" value="登入">
</form>
3.DynamicForm與FormFactory可以幫助我們做表單資料的轉換。
如果是動態表單的取得資料時要特別注意,用Map可能會出現取不到資料的情況,建議這部分要檢查資料是否正確。
DynamicForm requestData = formFactory.form().bindFromRequest();
Map <String , String> formData = requestData.get().getData();
String account = formData.get("account"); <---可能錯誤,建議額外寫驗證資料是否存在。
若是使用FormFactory對應Class的方式取得表單資料,也要特別注意,表單的欄位資訊,要跟Class符合,以免發生錯誤。
User user = formFactory.form(User.class).bindFromRequest().get();
String account = user.getAccount(); <---若是表單沒有這欄位,會出錯。
以上就是特別要去注意的地方。下個章節,會介紹Play在[name].scala.html頁面上的操作,本身Play支援scala語言本身也是相依於JVM,寫起來感覺很像Java,它可以幫助我們在頁面上做更好的資料畫面呈現。
[Reference]
1.JavaForms
https://www.playframework.com/documentation/2.5.x/JavaForms
2.Dependency Injection 筆記 (1)
http://huan-lin.blogspot.com/2011/10/dependency-injection-1.html
3.play-2-reverse-routing-get-route-from-controller-method
http://stackoverflow.com/questions/24405637/play-2-reverse-routing-get-route-from-controller-method
4.java學習筆記之Spring依賴注入和控制反轉
https://read01.com/7O63GN.html
5.Play API
https://www.playframework.com/documentation/2.5.x/api/java/index.html