在 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 應用程式會話管理的基本原理。