隱藏欄位


在 HTTP 協定中,伺服器是個健忘的傢伙,對每次的請求都一視同仁,根據請求中的資訊來執行程式並回應,每個請求對伺服器來說都是新的訪客請求。

如果你正在製作一個網路問卷,由於問卷內容很長,因此必須分作幾個頁面,上一頁面作答完後,必須請求伺服器顯示下一個頁面。但是在 HTTP 協定中,伺服器 並不會記得上一次請求的狀態,那上一頁的問卷結果要如何保留(其實伺服器根本不會記得這次請求是之前的瀏覽器發送過來的)?

既然伺服器不會記得兩次請求間的關係,那就由瀏覽器在每次請求時「主動告知」伺服器多次請求間必要的資訊,伺服器只要單純地處理請求中的相關訊息即可。

隱藏欄位(Hidden field)就是主動告知伺服器多次請求間必要資訊的方式之一。以問卷作答的範例來說,上一頁的問卷答案,可以用隱藏欄位的方式放在下一頁的表單中,如此發送下一頁表單時,就可以一併發送這些隱藏欄位,每一頁的問卷答案就可以保留下來。

那麼上一次的結果如何成為下一頁的隱藏欄位呢?在客戶端可以直接在瀏覽器上使用JavaScript製作這個功能,或是將上一頁的結果發送至伺服器,由伺服器將上一頁結果以隱藏欄位的方式再回應給瀏覽器。

隱藏欄位

以下這個範例是個簡單的示範,程式會有兩頁問卷,第一頁的結果會在第二頁成為隱藏欄位,當第二頁發送後,可以看到兩頁問卷的所有答案。

package cc.openhome;

import java.io.*;
import javax.servlet.*;
import javax.servlet.annotation.*;
import javax.servlet.http.*;

@WebServlet("/questionnaire")
public class Questionnaire extends HttpServlet {
    protected void processRequest(HttpServletRequest request, HttpServletResponse response) 
                      throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8");

        PrintWriter out = response.getWriter();
        out.println("<!DOCTYPE html>");
        out.println("<html>");
        out.println("<head>");
        out.println("<meta charset='UTF-8'>");
        out.println("</head>");
        out.println("<body>");

        String page = request.getParameter("page");
        out.println("<form action='questionnaire' method='post'>");

        if("page1".equals(page)) {          // 第一頁問卷
            out.println("問題一:<input type='text' name='p1q1'><br>");
            out.println("問題二:<input type='text' name='p1q2'><br>");
            out.println("<input type='submit' name='page' value='page2'>");
        }
        else if("page2".equals(page)) {    // 第二頁問卷
            String p1q1 = request.getParameter("p1q1");
            String p1q2 = request.getParameter("p1q2");
            out.println("問題三:<input type='text' name='p2q1'><br>");
            out.printf("<input type='hidden' name='p1q1' value='%s'>%n", p1q1);
            out.printf("<input type='hidden' name='p1q2' value='%s'>%n", p1q2);
            out.println("<input type='submit' name='page' value='finish'>");
        }
        else if("finish".equals(page)) {    // 最後答案收集
            out.println(request.getParameter("p1q1") + "<br>");
            out.println(request.getParameter("p1q2") + "<br>");
            out.println(request.getParameter("p2q1") + "<br>");
        }
        out.println("</form>");
        out.println("</body>");
        out.println("</html>");
    } 

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
        processRequest(request, response);
    } 

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
        processRequest(request, response);
    }
}

由於程式只使用一個 Servlet,所以利用一個 page 請求參數來區別該顯示第幾頁問卷。page 請求參數的值為 "page1" 時,顯示第一頁問卷題目;為 "page2" 時,顯示第二頁問卷題目,並將前一頁的答案以隱藏欄位的方式回應給瀏覽器,以便下一次可以再發送給伺服器;page 請求參數的值為 "finish" 時,應用程式將顯示問卷的所有答案。

<!DOCTYPE html>
<html>
<head>
<meta charset='UTF-8'>
</head>
<body>
<form action='questionnaire' method='post'>
問題三:<input type='text' name='p2q1'><br>
<input type='hidden' name='p1q1' value='測試一'>
<input type='hidden' name='p1q2' value='測試二'>
<input type='submit' name='page' value='finish'>
</form>
</body>
</html>

使用隱藏欄位的方式,顯然地在關掉網頁後,就會遺失先前請求的資訊,所以僅適合用於一些簡單的狀態管理,像是線上問卷。由於在檢視網頁原始碼時,就可以看到隱藏欄位的值,因此這個方法不適合用於隱密性需求高的資料,把信用卡資料或密碼之類的放到隱藏欄位更是不可行的作法。

隱藏欄位不是 Servlet/JSP 實際管理會話時的機制,在這邊實作隱藏欄位,只是為了說明,由瀏覽器主動告知必要的資訊,為實作 Web 應用程式會話管理的基本原理。