你可以使用 HttpServletResponse
物件上的 setHeader()
、addHeader()
來設定回應標頭,setHeader()
設定標頭名稱與值,addHeader()
則可以在同一個標頭名稱上附加值。
setHeader()
、addHeader()
方法接受字串值,如果標頭的值是整數,則可以使用 setIntHeader()
、addIntHeader()
方法,如果標頭的值是個日期,則可以使用 setDateHeader()
、addDateHeader()
方法。
有些標頭必須搭配 HTTP 狀態碼(Status code),設定狀態碼的話,可以透過 HttpServletResponse
的 setStatus()
方法,例如,正常回應的 HTTP 狀態碼為 200 OK,可以透過 HttpServletResponse.SC_OK
來設定,如果想要重新導向頁面,必須傳送狀態碼 301 Moved Permanently、302 Found,前者可以透過 HttpServletResponse.SC_MOVED_PERMANENTLY
取得,後者可以透過 HttpServletResponse.SC_SC_FOUND
(建議)或 HttpServletResponse.SC_MOVED_TEMPORARILY
取得。
例如,若某個資源也許永久性地移動至另一個網址,當客戶端請求原有網址時,必須要求客戶端重新導向至新網址,並要求未來連結時也應使用新網址的話(像是告訴搜尋引擎網站搬家了,這有利於網址 SEO),可以如下撰寫程式:
response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
response.addHeader("Location", "new_url");
如果資源只是暫時性搬移,或者是將來可能改變,仍希望客戶端依舊使用現有位址來存取資源,不要快取資源之類的,可以使用暫時重定向:
response.setStatus(HttpServletResponse.SC_FOUND);
response.addHeader("Location", "temp_url");
所有的標頭設定,必須在回應確認之前(Commit),在回應確認之後設定的標頭,會被容器忽略。
除了 301、302 之外,HTTP 1.1 增加了 303 See Other 與 307 Temporary Redirect 狀態碼,如果不擔心古老只支援 HTTP 1.0 的客戶端問題,詳情可參考〈Status Code Definitions〉。
容器可以(但非必要)對回應進行緩衝,通常容器預設都會對回應進行緩衝,你可以操作 HttpServletResponse
以下有關緩衝的幾個方法:
getBufferSize()
setBufferSize()
isCommitted()
reset()
resetBuffer()
flushBuffer()
setBufferSize()
必須在呼叫 HttpServletResponse
的 getWriter()
或 getOutputStream()
方法之前呼叫,所取得的 Writer
或 ServletOutputStream
都會套用這個設定,在呼叫 HttpServletResponse
的 getWriter()
或 getOutputStream()
方法之後呼叫 setBufferSize()
,會丟出 IllegalStateException
。
在緩衝區未滿之前,所設定的回應相關內容都不會真正傳至客戶端,你可以使用 isCommitted()
看看是否回應已確認。如果想要重置所有的回應資訊,可以呼叫 reset()
方法,這會連同已設定的標頭一併清除,呼叫 resetBuffer()
會重置回應內容,但不會清除已設定的標頭內容。
flushBuffer()
會出清(flush)所有緩衝區中已設定的回應資訊至客戶端,reset()
、resetBuffer()
必須在回應未確認前呼叫,在回應已確認後呼叫 reset()
、resetBuffer()
會丟出 IllegalStateException
。
對於暫時重定向,除了自行透過 HTTP 狀態碼與 Location
標頭的設定之外,還可以使用 HttpServletResponse
的 sendRedirect()
要求瀏覽器重新請求另一個 URL,例如:
response.sendRedirect("https://openhome.cc");
這個方法會在回應中設定 HTTP 狀態碼 302 以及 Location
標頭,無論是自行控制狀態碼、標頭,或者是透過 sendRedirect()
方法重定向,客戶端會使用 GET
方法請求指定的 URL,因此在網址列上會發現 URL 的變更。由於是利用 HTTP 狀態碼與標頭資訊,要求瀏覽器重新導向網頁,因此這個方法必須在回應未確認輸出前執行,否則會發生 IllegalStateException
。
重新定向的使用時機之一,是在表單 POST 之後,為了避免使用者在 POST 表單之後,重新載入網頁造成重複發送 POST 內容,可以在 POST 之後要求重新導向,重定向的使用時機之二是使用者登入後自動導回先前閱讀之頁面,例如,若目前頁面為 xyz.html,設定一個鏈結為 login?url=xyz.html,在使用者登入成功之後取得 url
請求參數來進行重新導向。
如果重新導向的目的地,是根據使用者的指定,特別是允許使用者指定外部網址的開放式重新導向,請務必特別小心,以免成為被攻擊的弱點,如果非得開放式重新導向,務必檢查允許的對象網址,非開放式重新導向也得小心檢查,或者是對重新導向的目標予以編碼,使用編碼來替代任意的 URL 指定,再於應用程式中對應至真正的 URL。
如果在處理請求的過程中發現一些錯誤,而你想要傳送伺服器預設的狀態與錯誤訊息,則可以使用 sendError()
方法。例如,如果根據請求參數必須傳回的資源根本不存在,則可以如下送出錯誤訊息:
response.sendError(HttpServletResponse.SC_NOT_FOUND);
SC_NOT_FOUND
會令伺服器回應 404 狀態碼。如果想使用自訂的訊息來取代預設的訊息文字,則可以使用 sendError()
的另一個版本:
response.sendError(HttpServletResponse.SC_NOT_FOUND, "筆記文件");
以 HttpServlet
的以 doGet()
為例,其預設實作就使用了 sendError()
方法:
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String protocol = req.getProtocol();
String msg = Strings.getString("http.method_get_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
} else {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
}
}
sendError()
方法必須在回應未確認輸出前執行,否則會發生 IllegalStateException
。
回應物件若被容器關閉,則必須出清所有的回應內容,回應物件被關閉的時機點有:
- Servlet 的
service()
方法已結束 - 回應的內容長度超過
HttpServletResponse
的setContentLength()
設定的長度 - 呼叫了
sendRedirect()
方法 - 呼叫了
sendError()
方法 - 呼叫了
AsyncContext
的complete()
方法(之後還會介紹)