在前面的文章裏,我們學些了部分Servlet的相關知識,相信Servelt已經成功的引起了你的注意(😝,猜測如此),今天我們來一起宏觀的瞭解下Servlet的運行過程,然後一起來看下HttpServletRequest中提供的獲取各種數據的方法。
1.Servlet的運行過程
對於用戶來講,在客戶端發起一次請求(比如說查詢某類商品),到頁面上顯示出結果(比如查詢到了n件商品,頁面上顯示了第一頁的內容),對於用戶來講就是一次點擊鼠標左鍵,但是在服務器,要做的可遠不止一次點擊這麼簡單,下面然我們來一起看下服務器都做了哪些操作。
首先,我們來看下面這張圖:
從圖中,我們可以看到,
- 客戶端的網絡請求首先會被Http服務器接收(也叫Web服務器、web容器,其需要提供web應用運行所需的環境,接收客戶端的Http請求);
- Web服務器根據請求的路徑將請求轉交給對應的Servlet容器(也稱Servlet引擎,爲Servlet的運行提供環境支持,可以理解爲tomcat或其他服務器);
- Servlet容器根據對應的虛擬路徑(@WebServlet中配置的)來加載Servlet,如果Serlvet沒有被實例化則創建該Servlet的一個實例(調用init方法);
- Servlet容器根據用戶的HTTP請求,創建一個ServletRequest對象(HTTP的請求信息被封裝在其中)和一個可以對HTTP請求進行響應的ServletResponse對象(類似於寄信,並在信中說明回信的地址),然後調用HttpServlet中重寫的service(ServletRequest req, ServletResponse res)方法,並在這個方法中,將ServletRequest、ServletResponse這兩個對象向下轉型,得到我們非常熟悉的HttpServletRequest和HttpServletResponse兩個對象,然後將客戶端的請求轉發到HttpServlet中protected修飾的service(HttpServletRequest req, HttpServletResponse resp)(此過程在Servlet入門中有講述過);
- service(HttpServletRequest req, HttpServletResponse resp)根據請求的method(get、post、put、delete、head、options、trace)來調用不同的方法,如doGet、doPost;
- 服務端處理完Http的請求後,根據HttpServletResponse對象將處理結果作爲Http響應返回給客戶端。
上面就是一個客戶端發起請求與接收響應之前服務器所執行的操作,對於一個Servlet來講,其執行過程就等同於它的生命週期:1.被Servlet容器加載------>2.接收servlet容器轉發的來自客戶端的Http請求------->3.處理完畢後,將處理結果返回至客戶端------>4.web服務終止時被銷燬。其中的2、3步驟,在web服務運行期間,可能會因爲客戶端的多次請求而執行多次,1、4步驟也有可能因爲服務的重啓或者主動銷燬而多次執行。
2.Http請求中包含的信息
HttpServletRequest中包含了客戶端HTTP請求的所有信息,其中主要爲三部分信息:請求行、請求頭、請求正文,這樣說大家可能會不太明白,下面我們通過幾張圖片來說明一下:
上圖還是我們的老朋友HelloServlet在Chrome中的運行頁面(前幾篇博客中有說明),我們打開可以通過右擊–>檢查(或F12)打開開發者工具主面板,點擊NetWork,點擊請求的連接(HttpServlet)可以查看客戶端向服務器的Http請求信息,紅框中的信息即爲Http請求中的部分信息爲總覽;
Http請求中還包含請求頭信息,其中包含許多的header;
如果Http請求中攜帶參數(url中攜帶的參數),可以在Query String Parameters中查看到(本次爲了顯示此部分內容,手動的在url中添加了"?username=liaizhu&password=123456");
如果Http請求方式爲post,裝在請求體中的數據可以在Form Data中看到,Query String Parameters和Form Data是可以共存的,即Http協議既允許我們通過url傳參,也可以通過請求體傳參,Get、Post方式更多的是對請求進行規範化,開發中還是儘量只使用一種方式傳參。
3.HttpServletRequest中獲取請求行信息的方法
Http的請求行中,會包含請求方法、請求資源名、請求路徑、Http版本等信息,我們可以在tomcat的安裝目錄–>logs—>localhost_access_log.xxx.txt中查看在某日期中(xxx爲日期,格式爲yyyy-MM-dd)請求本Tomcat的所有Http請求的請求頭信息,我們在上面請求HelloServlet的請求行信息如下:
其中"“括起來的爲請求行信息,GET表示請求方式,”/FirstProject/HelloServlet?username=liaizhu&password=123456"爲請求url,"HTTP/1.1"爲請求的協議及版本。請求行後跟的200爲Http響應的狀態碼(Status Code),293爲響應內容的長度(Content-Length)。
爲了獲取請求行中對應的信息,HttpServletRequest中實現了一攬子的方法,讓我們的操作變得更加簡單快捷。相關方法如下:
爲了更好的理解,我們通過一個例子來看下每個方法的獲取的返回值。我們創建一個RequestTestServlet,其doGet方法如下:
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 設置返回客戶端的contentType
// text/plain :純文本格式 設置爲text/html println的換行會失效,可以添加<br>換行標籤
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
// 獲取請求行的相關信息
out.println("HttpServletRequest對象獲取請求行信息方法示例:<br>");
out.println("getMethod : " + request.getMethod() + "<br>");
out.println("getRequestURI : " + request.getRequestURI() + "<br>");
out.println("getQueryString:" + request.getQueryString() + "<br>");
out.println("getProtocol : " + request.getProtocol() + "<br>");
out.println("getContextPath:" + request.getContextPath() + "<br>");
out.println("getServletPath : " + request.getServletPath() + "<br>");
out.println("getRemoteAddr : " + request.getRemoteAddr() + "<br>");
out.println("getRemoteHost : " + request.getRemoteHost() + "<br>");
out.println("getRemotePort : " + request.getRemotePort() + "<br>");
out.println("getLocalAddr : " + request.getLocalAddr() + "<br>");
out.println("getLocalName : " + request.getLocalName() + "<br>");
out.println("getLocalPort : " + request.getLocalPort() + "<br>");
out.println("getServerName : " + request.getServerName() + "<br>");
out.println("getServerPort : " + request.getServerPort() + "<br>");
out.println("getScheme : " + request.getScheme() + "<br>");
out.println("getRequestURL : " + request.getRequestURL() + "<br>");
}
瀏覽器上輸入對應的url運行結果如下圖所示:
我們可以看到,HttpServletRequest提供的方法幾乎可以獲取我們想要的任何請求頭中的信息,還可以獲得客戶端的的ip地址(客戶端出口的公網ip),在上例中,getRemoteAddr等四個方法獲取到的值全爲0:0:0:0:0:0:0:1,這是因爲客戶端和服務器端都在一個主機上,hosts文件解析地址的時候將localhost解析爲ipv6了,我們可以將localhost改爲127.0.0.1,即可獲得ipv4的地址。其中的getRemotePort獲取的是客戶端與服務器管建立的Tcp連接所使用的端口號。
4.HttpServletRequest中獲取請求頭的相關方法
當客戶端請求Servlet時,需要通過請求頭向服務器傳遞附加信息,例如客戶端可以接受的數據類型、請求源、消息正文的長度、是否保持TCP連接等,我們先來看下Http中常見的請求頭信息:
同樣的,爲了方便的獲取請求頭中對應的信息,HttpServletRequest也提供了一系列的方法,相關方法如下:
爲了測試上述方法,我們在RequestTestServlet中的doGet方法增加如下代碼:
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//獲取請求行信息
//...
out.println("<hr/>");
out.println("HttpServletRequest對象獲取請求頭信息方法示例:<br>");
out.println("getHeaderNames,all headers info as follows:" + "<br>");
// 獲取請求消息中所有頭字段
Enumeration headerNames = request.getHeaderNames();
// 使用循環遍歷所有請求頭,並通過getHeader()方法獲取一個指定名稱的頭字段
while (headerNames.hasMoreElements()) {
String headerName = (String) headerNames.nextElement();
out.print(headerName + " : " + request.getHeader(headerName) + "<br>");
}
out.println("getCookies,all cookies info as follows:" + "<br>");
Cookie []cookies = request.getCookies();
for(Cookie cookie: cookies) {
out.println(cookie.getName() + ":" + cookie.getValue() + "<br>");
}
}
並且爲了測試getCookies方法,我們在chrome中手動添加如下兩個cookie:
瀏覽器中輸入對應url的執行結果如下圖所示:
其中紅框部分爲通過getHeaderNames獲取請求頭name的Enumeration對象,並通過迭代的方式獲取了request上傳的所有的請求頭信息,可以看到,cookie數據也是通過請求頭來傳遞到服務器的。綠框部分爲getCookies方法獲取的Cookie對象的數組,並迭代輸出數組中的所有cookie的name和value。
5.HttpServletRequest中獲取請求參數
上面講了這麼多,其實都是HttpServletRequest提供的錦上添花的方法,HttpServletRequest最重要的,就是可以獲取用戶提交來的數據,比如表單數據或者一些查詢參數。因爲Servlet在MVC架構中是充當controller這個角色的,其負責響應用戶的請求,也就需要和用戶進行交互,負責獲取從前端(JSP,客戶端)獲取數據(用戶輸入、或者查詢條件等),並在處理結束後,給客戶端一個響應。如果無法獲取頁面數據,那麼後續的操作也就無從談起。
當然,爲了方便獲取頁面中的參數,HttpServletRequest也也也提供了一系列的方法,相關方法如下:
爲了展示上述方法的使用,我們創建一個PersonalMessage.html的頁面,頁面代碼如下:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<body>
<form action="RequestTestServlet" method="post">
姓名: <input type="text" name="name" style="width: 150px" />
<p />
年齡: <input type="text" name="age" style="width: 150px" />
<p />
愛好:
<input type="checkbox" name="hobby" value="sing">籃球
<input type="checkbox" name="hobby" value="dance">羽毛球
<input type="checkbox" name="hobby" value="football">足球
<p />
<input type="submit" value="提交" />
<p />
</form>
</body>
</html>
並且,爲了和前兩個例子區分開,我們form的提交方式設置爲post,RequestTestServlet中對應的doPost方法如下:
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 設置request對象的解碼方式
request.setCharacterEncoding("utf-8");
String name = request.getParameter("name");
String age = request.getParameter("age");
System.out.println("姓 名:" + name);
System.out.println("年 齡:" + age);
// 獲取參數名爲“hobby”的值
String[] hobbys = request.getParameterValues("hobby");
System.out.print("愛 好: ");
for (int i = 0; i < hobbys.length; i++) {
System.out.print(hobbys[i] + ",");
}
Map<String, String[]> paramMap = request.getParameterMap();
//...
}
其運行結果如下圖所示:
6.總結
本文主要講解了Http請求的三部分內容,分別爲請求行、請求頭和請求體,以及如何通過HttpServletRequest獲取對應的信息,通常來講Servlet獲取客戶端數據的數據是第一步,因此HttpServletRequest對象是非常重要的,對其中的方法做到熟練掌握也可讓我們在開發的過程中更加的得心應手。
又到了分隔線以下,本文到此就結束了,本文內容全部都是由博主自己進行整理並結合自身的理解進行總結,如果有什麼錯誤,還請批評指正。
Java web這一專欄會是一個系列博客,喜歡的話可以持續關注,如果本文對你有所幫助,還請還請點贊、評論加關注。
有任何疑問,可以評論區留言。