Java Servlet 實戰入門教程-04-servlet request 請求詳解
請求
請求物件封裝了客戶端請求的所有資訊。
在 HTTP 協議中,這些資訊是從客戶端傳送到伺服器請求的 HTTP 頭部和訊息體。
介面
全部介面參見 介面 UML
HttpServletRequest 在 ServletRequest 的基礎之上添加了 HTTP 的相關方法。
- 這個介面的實現是 Servlet-API 嗎?
不是。是容器實現的。
我們在使用的時候不用關心具體的實現,只需要知道這些方法即可。
本文閱讀順序
你可以簡單理解下面的屬性方法,然後直接去看 實戰例子
常見方法
HTTP 協議引數
servlet 的請求引數以字串的形式作為請求的一部分從客戶端傳送到 servlet 容器。
GET
這些 API 不會暴露 GET 請求(HTTP 1.1所定義的)的路徑引數。他們必須從 getRequestURI 方法或 getPathInfo 方法返回的字串值中解析。
POST
當請求是一個 HttpServletRequest 物件,且符合“引數可用時”描述的條件時,容器從 URI 查詢字串和 POST 資料中填充引數。
引數以一系列的名-值對(name-value)的形式儲存。任何給定的引數的名稱可存在多個引數值。
- 方法列表
ServletRequest 介面的下列方法可訪問這些引數:
序號 | 方法 | 說明 |
---|---|---|
1 | getParameter | 以字串形式返回請求引數的值,或者如果引數不存在則返回 null。 |
2 | getParameterNames | 返回一個 String 物件的列舉,包含在該請求中包含的引數的名稱。 |
3 | getParameterValues | 返回一個字串物件的陣列,包含所有給定的請求引數的值,如果引數不存在則返回 null。 |
4 | getParameterMap | 方法返回請求引數的一個 java.util.Map 物件,其中以引數名稱作為 map 鍵,引數值作為 map 值 |
- 查詢字串
查詢字串和 POST 請求的資料被彙總到請求引數集合中。查詢字串資料放在 POST 資料之前。
例如,如果請求由查詢字串 a=hello 和 POST 資料 a=goodbye&a=world 組成,得到的引數集合順序將是 a=(hello, goodbye, world)。
- 當引數可用時
Post 表單資料能填充到引數集(Paramter Set)前必須滿足的條件:
-
該請求是一個 HTTP 或 HTTPS 請求。
-
HTTP 方法是 POST。
-
內容型別是
application/x-www-form-urlencoded
。 -
該 servlet 已經對請求物件的任意 getParameter 方法進行了初始呼叫。
如果不滿足這些條件,而且引數集中不包括 post 表單資料,那麼 servlet 必須可以通過請求物件的輸入流得到 post 資料。
如果滿足這些條件,那麼從請求物件的輸入流中直接讀取 post 資料將不再有效。
屬性
屬性是與請求相關聯的物件。屬性可以由容器設定來表達資訊,否則無法通過 API 表示,或者由 servlet 設定將資訊傳達給另一個 servlet(通過 RequestDispatcher)。
- 方法
屬性通過 ServletRequest 介面中下面的方法來訪問:
序號 | 方法 | 說明 |
---|---|---|
1 | getAttribute | 以物件形式返回已命名屬性的值,如果沒有給定名稱的屬性存在,則返回 null。 |
2 | getAttributeNames | 返回一個列舉,包含提供給該請求可用的屬性名稱。 |
3 | setAttribute | 設定屬性 |
一個屬性名稱只能關聯一個屬性值。
- 保留定義
字首 java.
和 javax.
開頭的屬性名稱是本規範的保留定義。
同樣地,字首 sun.
和 com.sun.
,oracle
和 com.oracle
開頭的屬性名是Oracle Corporation 的保留定義。
建議屬性集中所有屬性的命名與Java程式語言的規範為包命名建議的反向域名約定一致。
請求頭
通過下面的 HttpServletRequest 介面方法,servlet 可以訪問 HTTP 請求的頭資訊:
序號 | 方法 | 說明 |
---|---|---|
1 | getHeader | 以字串形式返回指定的請求頭的值。多個頭可以具有相同的名稱,例如HTTP 請求中的 Cache-Control 頭。如果多個頭的名稱相同,getHeader方法返回請求中的第一個頭。 |
2 | getHeaders | getHeaders 方法允許訪問所有與特定頭名稱相關的頭值,返回一個 String 物件的 Enumeration(列舉)。 |
3 | getHeaderNames | 返回一個列舉,包含在該請求中包含的所有的頭名。 |
4 | getIntHeader | 返回指定的請求頭的值為一個 int 值。 |
5 | getDateHeader | 返回指定的請求頭的值為一個 Date 值。 |
如果 getIntHeader 方法不能轉換為 int 的頭值,則丟擲 NumberFormatException 異常。
如果 getDateHeader 方法不能把頭轉換成一個 Date 物件,則丟擲 IllegalArgumentException 異常。
SSL 屬性
如果請求已經是通過一個安全協議傳送,如 HTTPS,必須通過ServletRequest 介面的 isSecure()
方法公開該資訊。
Web 容器必須公開下列屬性給 servlet 程式設計師:
屬性 | 屬性名稱 | Java型別 |
---|---|---|
密碼套件 | javax.servlet.request.cipher_suite | String |
演算法的位大小 | javax.servlet.request.key_size | Integer |
SSL 會話 id | javax.servlet.request.ssl_session_id | String |
如果有一個與請求相關的 SSL 證書,它必須由 servlet 容器以 java.security.cert.X509Certificate
型別的物件陣列暴露給 servlet 程式設計師並可通過一個javax.servlet.request.X509Certificate 型別的 ServletRequest屬性訪問。
這個陣列的順序是按照信任的升序順序。
證書鏈中的第一個證書是由客戶端設定的,第二個是用來驗證第一個的,等等。
- 拓展閱讀
請求路徑元素
引導 servlet 服務請求的請求路徑由許多重要部分組成。
以下元素從請求URI路徑得到,並通過請求物件公開:
- Context Path
與 ServletContext 相關聯的路徑字首是這個servlet 的一部分。如果這個上下文是基於 Web 伺服器的 URL 名稱空間基礎上的“預設”上下文,那麼這個路徑將是一個空字串。
否則,如果上下文不是基於伺服器的名稱空間,那麼這個路徑以 /
字元開始,但不以 /
字元結束。
- Servlet Path
路徑部分直接與啟用請求的對映對應。這個路徑以 /
字元開頭,如果請求與 /*
或 模式匹配,在這種情況下,它是一個空字串。
- PathInfo
請求路徑的一部分,不屬於 Context Path 或 Servlet Path。
如果沒有額外的路徑,它要麼是null,要麼是以 /
開頭的字串。
重要的是要注意,除了請求 URI 和路徑部分的 URL 編碼差異外,下面的等式永遠為真:
requestURI = contextPath + servletPath + pathInfo
- 方法
使用 HttpServletRequest 介面中的下面方法來訪問這些資訊:
序號 | 方法 | 說明 |
---|---|---|
1 | getContextPath | 返回指示請求上下文的請求 URI 部分 |
2 | getServletPath | 返回呼叫 JSP 的請求的 URL 的一部分 |
3 | getPathInfo | 當請求發出時,返回與客戶端傳送的 URL 相關的任何額外的路徑資訊 |
路徑轉換方法
在 API 中有兩個方便的方法,允許開發者獲得與某個特定的路徑等價的檔案系統路徑。
這些方法是:
序號 | 方法 | 說明 |
---|---|---|
1 | ServletContext.getRealPath | getRealPath 方法需要一個 String 引數,並返回一個 String 形式的路徑,這個路徑對應一個在本地檔案系統上的檔案 |
2 | HttpServletRequest.getPathTranslated | getPathTranslated方法推斷出請求的 pathInfo 的實際路徑 |
這些方法在 servlet 容器無法確定一個有效的檔案路徑 的情況下,如 Web應用程式從歸檔中,在不能訪問本地的遠端檔案系統上,或在一個數據庫中執行時,這些方法必須返回 null。
JAR 檔案中 META-INF/resources
目錄下的資源,只有當呼叫 getRealPath() 方法時才認為容器已經從包含它的 JAR 檔案中解壓,在這種情況下,必須返回解壓縮後位置。
編碼
目前,許多瀏覽器不隨著 Content-Type 頭一起傳送字元編碼限定符,而是根據讀取 HTTP 請求確定字元編碼。
如果客戶端請求沒有指定請求預設的字元編碼,容器用來建立請求讀取器和解析 POST 資料的編碼必須是“ISO-8859-1”。
然而,為了向開發人員說明客戶端沒有指定請求預設的字元編碼,在這種情況下,客戶端傳送字元編碼失敗,容器從 getCharacterEncoding 方法返回 null。
如果客戶端沒有設定字元編碼,並使用不同的編碼來編碼請求資料,而不是使用上面描述的預設的字元編碼,那麼可能會發生問題。
為了彌補這種情況,ServletRequest 介面添加了一個新的方法 setCharacterEncoding(String enc)
。
開發人員可以通過呼叫此方法來覆蓋由容器提供的字元編碼。
必須在解析任何 post 資料或從請求讀取任何輸入之前呼叫此方法。
此方法一旦呼叫,將不會影響已經讀取的資料的編碼。
埠資訊
- 方法
序號 | 方法 | 說明 |
---|---|---|
1 | getServerPort | 返回接收到這個請求的埠號 |
2 | getLocalPort | 返回呼叫 JSP 的請求的 URL 的一部分 |
3 | getRemotePort | 當請求發出時,返回與客戶端傳送的 URL 相關的任何額外的路徑資訊 |
getServerPort() 含義應該很清楚,不過,這有可能與 getLocalPort() 混淆。
所以我們先來解釋相對容易的方法: getRemotePort()。
首先,你可能會問“對誰而言是遠端的?“
在這種情況下,由於是伺服器在問,所以客戶是遠端的。既然客戶對伺服器是遠端的,所以getRemotePort()是指“ 得到客戶的埠”。 也就是要得到發出請求的客戶的埠號。
要記住: 對於一個 servlet ,遠端就意味著客戶。
getLocalPort() 和 getServerPort() 的差別很微妙
getServerPort() 說, “請求原來發送到哪個埠?”。
getLocalPort() 則是說“請求最後傳送到哪個端?”
不錯,二.者確實有區別,因為儘管請求要傳送到一個埠( 伺服器所監聽的埠),但是伺服器也就會為每個執行緒找一個不同的本地埠。
這樣一來,一個應用就能同時處理多個客戶了。
生命週期
每個請求物件只在一個 servlet 的 service 方法的作用域內,或過濾器的 doFilter 方法的作用域內有效,除非該元件啟用了非同步處理並且呼叫了請求物件的 startAsync 方法。
在發生非同步處理的情況下,請求物件一直有效,直到呼叫 AsyncContext 的 complete 方法。
容器通常會重複利用請求物件,以避免建立請求物件而產生的效能開銷。
開發人員必須注意的是,不建議在上述範圍之外保持 startAsync 方法還沒有被呼叫的請求物件的引用,因為這樣可能產生不確定的結果。
實戰例子
程式碼
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/request/header")
public class RequestHeaderServlet extends HttpServlet {
private static final long serialVersionUID = 29535446166538705L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 設定響應內容型別為 HTML
resp.setContentType("text/html;charset=UTF-8");
PrintWriter out = resp.getWriter();
String docType =
"<!DOCTYPE html> \n";
out.println(docType +
"<html>\n" +
"<head><meta charset=\"utf-8\"></head>\n" +
"<body>\n" +
"| Header 名稱 | Header 值 |<br>" +
"|:---|:---|<br>");
Enumeration headerNames = req.getHeaderNames();
while (headerNames.hasMoreElements()) {
String paramName = (String) headerNames.nextElement();
out.print("| " + paramName);
String paramValue = req.getHeader(paramName);
out.println(" | " + paramValue + " | <br>");
}
out.println("</body></html>");
}
}
頁面顯示
Header 名稱 | Header 值 |
---|---|
host | localhost:8081 |
connection | keep-alive |
cache-control | max-age=0 |
upgrade-insecure-requests | 1 |
user-agent | Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36 |
accept | text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8 |
accept-encoding | gzip, deflate, br |
accept-language | zh-CN,zh;q=0.9,en;q=0.8 |
cookie | Idea-4401a678=46e214a2-e649-459c-94d2-61f9b18edb64; sidebar_collapsed=false; Idea-4401adf8=ed913581-acee-42f2-b17a-323a575a3d66; _ga=GA1.1.1104403428.1526306331 |
原始碼地址
介面 UML
參考資料
《Head First Servlets and Jsp》