03_Request
The Request
Request物件封裝了來自客戶端請求的所有資訊。在HTTP協議中,這個資訊是在請求的HTTP頭和訊息體從客戶端傳輸給伺服器的。
1. HTTP協議引數
servlet的請求引數是作為請求的一部分從客戶機向servlet容器傳送的字串。當請求是一個HttpServletRequest物件時,在第24頁上“當引數可用時”的條件被滿足時,容器將從URI查詢字串和POST-ed資料中填充引數。
引數儲存為一組名稱-值對。對於任何給定的引數名,都可以存在多個引數值。ServletRequest介面的以下方法可以訪問引數:
- getParameter
- getParameterNames
- getParameterValues
- getParameterMap
getParameterValues方法返回包含與引數名相關聯的所有引數值的字串物件陣列。getParameter方法返回的值必須是getParameterValues返回的字串物件陣列中的第一個值。getParameterMap方法返回一個請求的引數的java.util.Map,其中包含名稱作為鍵和引數值作為對映值。
查詢字串和post主體的資料被聚合到請求引數集中,在post body資料之前顯示查詢字串資料。例如,如果一個請求是用一個a=hello的查詢字串和一個a=goodbye&a=world的post體做的,那麼結果的引數集將被排序為=(hello, goodbye, world).
GET請求(由HTTP 1.1定義)的一部分的路徑引數沒有被這些api公開。它們必須從getRequestURI方法或getPathInfo方法返回的字串值中解析。
1.1 當引數可用時
The following are the conditions that must be met before post form data will be populated to the parameter set:
下面是在將表單資料填充到引數集之前必須滿足的條件:
- The request is an HTTP or HTTPS request.
- The HTTP method is POST.
- The content type is application/x-www-form-urlencoded.
- The servlet has made an initial call of any of the getParameter family of methods on the request object.
如果條件沒有滿足,並且在引數集中不包含post表單資料,那麼post資料仍然必須通過請求物件的輸入流對servlet可用。如果條件滿足,則不再可以從請求物件的輸入流直接讀取post表單資料。
2.檔案上傳
Servlet容器允許在資料作為multipart/form-data傳送時上傳檔案。
如果滿足以下任一條件,servlet容器將提供multipart/form-data處理。
■servlet處理的請求註釋@MultipartConfig 8.1.5節的定義,“@MultipartConfig”8 - 74頁。
■部署描述符包含servlet multipart-config元素處理請求。
如何提供型別multipart/form-data的請求資料取決於servlet容器是否提供multipart/form-data處理:
■如果servlet容器提供了multipart/form-data處理,資料是通過以下在HttpServletRequest的方法可用的:
■ public Collection<Part> getParts()
■ public Part getPart(String name)
Each part provides access to the headers, content type related with it and the content via the Part.getInputStream method.
每個部分都提供了對header的訪問,內容型別與它相關,而且內容來自Part.getInputStream方法。
對於以表單資料作為內容配置的部分,但是如果沒有檔名,該部分的字串值也可以通過使用該部分名稱的HttpServletRequest的etParameter和getParameterValues方法獲得。
每個部分都提供了對header的訪問,內容型別與它相關,內容通過部分。getInputStream方法。
對於以表單資料作為內容配置但是沒有檔名的部分,則該部分的字串值也將通過HttpServletRequest的getParameter和getParameterValues方法獲得,並使用該部分的名稱。
■如果servlet容器不提供multi-part/form-data處理,可用的資料將通過HttpServletReuqest.getInputStream。
3.屬性
屬性是與請求相關聯的物件。屬性可以由容器設定,以表達通過API無法表達的資訊,也可以由servlet設定以與另一個servlet進行資訊交流(通過RequestDispatcher)。可使用ServletRequest介面的下列方法訪問屬性:
■ getAttribute
■ getAttributeNames
■ setAttribute
只有一個屬性值可能與屬性名相關聯。以java和javax的字首開始的屬性名稱是本規範的保留定義。類似地,屬性名稱以sun., com.sun., oracle and com.oracle的字首開始,是由甲骨文公司保留的。建議將屬性集中的所有屬性按照Java程式語言規範所建議的反向域名約定命名。
4.資訊頭
servlet可以通過HttpServletRequest介面的以下方法訪問HTTP請求的頭:
■ getHeader
■ getHeaders
■ getHeaderNames
getHeader方法返回給定標題名稱的標題。在HTTP請求中,可以有多個具有相同名稱的頭,例如快取控制頭。如果有多個具有相同名稱的頭,則getHeader方法將返回請求中的第一個頭。getheader方法允許訪問與特定頭名稱關聯的所有頭值,返回字串物件的列舉。
頭可能包含整數或日期資料的字串表示。HttpServletRequest介面的以下便利方法提供了訪問頭資料的一種格式:
■getIntHeader
■getDateHeader
如果getIntHeader方法無法將標題值轉換為int,則丟擲NumberFormatException。如果getDateHeader方法不能將訊息頭轉換為日期物件,則丟擲一個IllegalArgumentException。
5.請求路徑元素
導致servlet服務請求的請求路徑由許多重要的部分組成。以下元素從請求URI路徑獲得,並通過請求物件公開:
■Context Path:與 ServletContext 相關聯的路徑字首是這個 servlet 的一部分。如果這個上下文是基於 Web伺服器的 URL 名稱空間基礎上的“預設”上下文,那麼這個路徑將是一個空字串。否則,如果上下文不是
基於伺服器的根名稱空間,那麼這個路徑以/字元開始,但不以/字元結束。
■ Servlet Path:路徑部分直接對應於啟用此請求的對映。此路徑以“/”字元開頭,除非請求與“/*”或“”模式匹配,在這種情況下,它是一個空字串。
■ PathInfo: 請求路徑的一部分,不是上下文路徑或Servlet路徑的一部分。如果沒有額外的路徑,則為null,或者是一個帶引導' / '的字串。
使用 HttpServletRequest 介面中的下面方法來訪問這些資訊:
■ getContextPath
■ getServletPath
■ getPathInfo
重要的是要注意,除了請求 URI 和路徑部分的 URL 編碼差異外,下面的等式永遠為真:
requestURI = contextPath + servletPath + pathInfo
舉幾個例子來澄清上述各點,請考慮以下幾點:
表 3-1 上下文設定的例子
Context Path | /catalog |
---|---|
Servlet Mapping | Pattern: /lawn/* Servlet: LawnServlet |
Servlet Mapping | Pattern: /garden/* Servlet: GardenServlet |
Servlet Mapping | Pattern: *.jsp Servlet: JSPServlet |
遵守下列行為:
表 3-2 遵守路徑元素行為
請求路徑 | 路徑元素 |
---|---|
/catalog/lawn/index.html | ContextPath: /catalog ServletPath: /lawn PathInfo: /index.html |
/catalog/garden/implements/ | ContextPath: /catalog ServletPath: /garden PathInfo: /implements/ |
/catalog/help/feedback.jsp | ContextPath: /catalog ServletPath: /help/feedback.jsp PathInfo: null |
6. 路徑轉換方法
開發人員獲得與特定路徑等效的檔案系統路徑。這些方法有:
■ServletContext.getRealPath
■HttpServletRequest.getPathTranslated
getRealPath方法接受一個字串引數,並返回路徑對應的本地檔案系統上的檔案的字串表示。getpathtransform方法計算請求的pathInfo的實際路徑。
在servlet容器無法通過這些方法確定的檔案有效路徑的情況下,例如,當Web應用程式從存檔中執行時,在本地無法訪問的遠端檔案系統上,或者在資料庫中,這些方法必須返回null。當容器在呼叫getRealPath()時從包含的JAR檔案中解壓它們,必須考慮到JAR檔案的META-INF/ Resources目錄中的資源,並且在這種情況下必須返回未解壓的位置。
7.非阻塞IO
Web 容器中的非阻塞請求處理有助於提高對改善 Web 容器可擴充套件性不斷增加的需求,增加 Web 容器可同時處理請求的連線數量。servlet 容器的非阻塞 IO 允許開發人員在資料可用時讀取資料或在資料可寫時寫資料。
非阻塞 IO 僅對在 Servlet 和 Filter(2.3.3.3 節定義的,“非同步處理”)中的非同步請求處理和升級處理
(2.3.3.5 節定義的,“升級處理”)有效。否則,當呼叫 ServletInputStream.setReadListener 或ServletOutputStream.setWriteListener 方法時將丟擲 IllegalStateException。
ReadListener 為非阻塞 IO 提供了下面的回撥方法:
■ ReadListener
■ onDataAvailable().當可以從傳入的請求流中讀取資料時 ReadListener 的 onDataAvailable 方法被呼叫。當資料可讀時容器初次呼叫該方法。當且僅當下面描述的 ServletInputStream 的 isReady 方法返回 false,容器隨後將呼叫 onDataAvailable 方法。
■ onAllDataRead().當讀取完註冊了此監聽器的 ServletRequest 的所有資料時呼叫 onAllDataRead 方法。
■ onError(Throwable t). 處理請求時如果有任何錯誤或異常發生時呼叫 onError 方法。
容器必須執行緒安全的訪問 ReadListener 中的方法。
除了上述 ReadListener 定義的方法外,下列方法已被新增到 ServletInputStream 類中:
■ ServletInputStream
■ boolean isFinished(). 與 ServletReader/ServletInputStream 相關的請求的所有資料已經讀取完時 isFinished方法返回 true。否則返回 false。
■ boolean isReady().如果可以無阻塞地讀取資料 isReady 方法返回 true。如果沒有資料可以無阻塞地讀取該方法返回 false。 如果 isReady 方法返回 false,呼叫 read 方法是非法的,且必須丟擲 IllegalStateException。
■ void setReadListener(ReadListener listener). 設定上述定義的 ReadListener,呼叫它以非阻塞的方式讀取資料。一旦把監聽器與給定的 ServletInputStream 關聯起來,當資料可以讀取,所有的資料都讀取完或如果處理請求時發生錯誤,容器呼叫 ReadListener 的方法。註冊一個 ReadListener 將啟動非阻塞 IO。 在那時切換到傳統的阻塞 IO 是非法的,且必須丟擲 IllegalStateException。在當前請求範圍內,隨後呼叫 setReadListener是非法的且必須丟擲 IllegalStateException。
8.HTTP/2伺服器推送
伺服器推送是HTTP/2中出現在servlet API中的最明顯的改進。HTTP/2中的所有新特性,包括伺服器推送,都是為了提高web瀏覽體驗的效能。伺服器推送的貢獻在於改進了感知瀏覽器的效能,從簡單的事實來看,在瞭解額外的資源(例如影象、樣式表和指令碼)與初始請求的關係上伺服器的處境要比客戶機好得多。例如,伺服器可能知道,無論何時瀏覽器請求index.html,它不久將請求header.gif,footer.gif和style.css。因為伺服器知道這一點,所以他們可以預先伴隨傳送index.html的位元組開始傳送這些資源的位元組。
要使用伺服器推送,可以從HttpServletRequest中獲取一個PushBuilder的引用,根據需要將構建器進行mutate,然後呼叫push()。請參閱方法的javadoc javax.servlet.http.HttpServletRequest.newPushBuilder javax.servlet.http()和類。規範規範的PushBuilder。本節的其餘部分將在第6頁“其他重要參考”中引用的HTTP/2規範版本中的“伺服器推送”一節中對實現要求進行呼叫。
除非明確排除,Servlet 4.0容器必須支援HTTP/2規範部分“伺服器推送”中指定的伺服器推送。如果客戶端能夠使用HTTP/2,那麼容器必須啟用伺服器推送,除非客戶端通過傳送SETTINGS_ENABLE_PUSH設定值(0)來實現當前連線,從而禁用了伺服器推送。在這種情況下,僅針對該連線,不能啟用伺服器推送。
除了允許客戶端使用SETTINGS_ENABLE_PUSH設定禁用伺服器推送之外,servlet容器還必須遵守客戶機的請求,以避免在更細粒度的基礎上接收被推的響應,因為它會注意到引用被推流的流識別符號的CANCEL或拒絕流程式碼。這種互動的一個常見用法是,當瀏覽器已經擁有快取中的資源時。
9. Cookies
HttpServletRequest介面提供了getCookies方法,以獲得在請求中出現的一組cookie。這些cookie是客戶端向伺服器傳送的資料。通常,作為cookie的一部分,客戶端返回的唯一資訊是cookie的名稱和cookie值。當cookie被髮送到瀏覽器時,可以設定其他cookie屬性,比如註釋,通常不會返回。該規範還允許cookie僅為HttpOnly cookie。HttpOnly cookie向客戶機表明它們不應該暴露在客戶端指令碼程式碼中(除非客戶機知道要查詢這個屬性,否則它不會被過濾掉)。使用HttpOnly cookie有助於減輕某些型別的跨站點指令碼攻擊。
10. SSL 屬性
如果在安全協議(如HTTPS)上傳輸了請求,則必須通過ServletRequest介面的isSecure方法公開該資訊。Web容器必須向servlet程式設計師公開以下屬性:
TABLE 3-3 Protocol Attributes
Attribute | Attribute Name | Java Type |
---|---|---|
cipher suite | javax.servlet.request.cipher_suite | String |
bit size of the algorithm | javax.servlet.request.key_size | Integer |
SSL session id | javax.servlet.request.ssl_session_id | String |
如果有與請求相關聯的SSL證照,那麼它必須由servlet容器公開給servlet程式設計師作為java.security.cert型別的物件陣列。X509Certificate javax.servlet.request.X509Certificate通過ServletRequest屬性。
這個陣列的順序被定義為以信任的升序排列。鏈中的第一個證照是由客戶端設定的,第二個證照是用來對第一個證照進行身份驗證的,以此類推。
11.國際化
客戶端可以選擇向Web伺服器表明他們希望響應的語言是什麼。可以使用Accept-Language頭和HTTP/1.1規範中描述的其他機制從客戶端傳遞這些資訊。在ServletRequest介面中提供了以下方法來確定傳送者的首選語言環境:
- getLocale
- getLocales
getLocale方法將返回客戶機想要接受內容的首選語言環境。請參閱RFC 7231 (HTTP/1.1)的第14.4節,瞭解關於如何解釋Accept-Language頭的更多資訊,以確定客戶機的首選語言。
getLocales方法將返回一個Locale物件的列舉,以減少訂單開始時的首選語言環境,客戶可以接受的區域。
如果客戶機沒有指定首選語言環境,則getLocale方法返回的語言環境必須是servlet容器的預設語言環境,而getLocales方法必須包含預設語言環境的單個語言環境元素的列舉。
12. 請求資料編碼
目前,許多瀏覽器都不傳送帶有ContentType頭的字元編碼限定符,將字元編碼的確定留給讀取HTTP請求。在沒有字元編碼限定符的情況下,如果內容型別是application/x-www-form-urlencode,則容器用於建立請求讀取器和解析POST資料的預設編碼必須是US-ASCII。任何%nn編碼的值必須被解碼為ISO-8859-1。對於任何其他內容型別,如果客戶端請求、web應用程式或容器供應商的特定配置(對於容器中的所有web應用程式)都沒有指定,則容器用於建立請求讀取器和解析POST資料的預設編碼必須是ISO-8859-1。但是,為了向開發人員表明沒有字元編碼限定符,容器必須從getCharacterEncoding()方法返回null。
如果客戶端沒有設定字元編碼,並且請求資料用與上面描述的預設編碼不同的編碼進行編碼,那麼就會發生破壞。來補救這種情況,ServletContext上可使用setRequestCharacterEncoding(String enc),web.xml使用的< request-character-encoding >元素以及在ServletRequest介面上可以使用setCharacterEncoding(String enc)。開發人員可以通過呼叫這個方法重寫容器提供的字元編碼。必須在解析任何post資料或從請求中讀取任何輸入之前呼叫它。一旦資料被讀取,呼叫此方法將不會影響編碼。
13.請求物件的生存期
除非為元件啟用了非同步處理,並且在請求物件上呼叫startAsync方法,否則每個請求物件僅在servlet的service方法的範圍內,或者在過濾器的doFilter方法的範圍內有效。在非同步處理髮生的情況下,請求物件仍然有效,直到在AsyncContext上呼叫complete為止。容器通常回收請求物件,以避免請求物件建立的效能開銷。開發人員必須意識到,不建議使用startAsync在上面描述的範圍之外的請求物件的引用,因為它可能有不確定的結果。
在升級的情況下,以上的說法依舊正確。