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中,我挑選最使用的GETPOST來說明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方法。
接下來我們看PlayHTTP 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

成功 : 登入成功
失敗 : 登入失敗

以上範例示範了使用DynamicFormFormFactory協助我們取得頁面上的表單資料。而實際帳號登入,需要跟資料庫查詢確認後才算完成,這邊用簡單用字串比對方式,達到我們要練習的效果。

2.接下來,我們要改善Play取得表單資訊的方法,主要是Play支援把網頁表單資料與物件一對一的欄位對映,就不需要使用Map取得key的方式,取出表單value(欄位值),可以直接使用物件對應出的欄位。這邊也會示範帳號輸入錯誤時,怎樣回傳錯誤訊息提示使用者。

app\pojo\login,按造目錄階層,依序新增pojologinpackage來擺放我們得物件User,裡面 有兩個屬性是accountpassword,這邊為了方便介紹,我把帳號驗證的檢查也放在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]

到這邊PlayRequest & Response都練習過一次了,對於Play寫網站服務會有基本概念。下面會再說明一次這次練習使用到Play幾個關鍵技術或內建方法,建議多多實作與練習,會更熟悉Play的開發方式。

1.play.libs.Json協助我們把物件轉換Json格式,也可以幫助我們parse進來的Json RequestPlay使用的Json核心是jackson,可以參閱官方Play API會有更多實用的介紹。

2.HTTP POST Formaction使用到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.DynamicFormFormFactory可以幫助我們做表單資料的轉換。
如果是動態表單的取得資料時要特別注意,用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

results matching ""

    No results matching ""