Servlet - 会话跟踪
Servlet
標(biāo)簽 : Java與Web
會(huì)話跟蹤
HTTP本身是“無狀態(tài)”協(xié)議,它不保存連接交互信息,一次響應(yīng)完成之后即連接斷開,下一次請(qǐng)求需要重新建立連接,服務(wù)器不記錄上次連接的內(nèi)容.因此如果判斷兩次連接是否是同一用戶, 就需要使用會(huì)話跟蹤技術(shù)來解決.常見的會(huì)話跟蹤技術(shù)有如下幾種:
- URL重寫: 在URL結(jié)尾附加會(huì)話ID標(biāo)識(shí),服務(wù)器通過會(huì)話ID識(shí)別不同用戶.
- 隱藏表單域: 將會(huì)話ID埋入HTML表單隱藏域提交到服務(wù)端(會(huì)話ID不在瀏覽器頁面顯示).
- Cookie: 第一次請(qǐng)求時(shí)服務(wù)器主動(dòng)發(fā)一小段信息給瀏覽器(即Cookie),下次請(qǐng)求時(shí)瀏覽器自動(dòng)附帶該段信息發(fā)送給服務(wù)器,服務(wù)器讀取Cookie識(shí)別用戶.
- Session: 服務(wù)器為每個(gè)用戶創(chuàng)建一個(gè)Session對(duì)象保存到內(nèi)存,并生成一個(gè)sessionID放入Cookie發(fā)送給瀏覽器,下次訪問時(shí)sessionID會(huì)隨Cookie傳回來,服務(wù)器再根據(jù)sessionID找到對(duì)應(yīng)Session對(duì)象(Java領(lǐng)域特有).
Session機(jī)制依賴于Cookie,如果Cookie被禁用Session也將失效.
Cookie
Cookie是識(shí)別當(dāng)前用戶,實(shí)現(xiàn)持久會(huì)話的最好方式.最初由網(wǎng)景公司開發(fā),但現(xiàn)在所有主流瀏覽器都支持.以至于HTTP協(xié)議為他定義了一些新的HTTP首部.
URL重寫與隱藏表單域兩種技術(shù)都有一定的局限,細(xì)節(jié)可參考博客四種會(huì)話跟蹤技術(shù)
- Cookie規(guī)范
- Cookie通過請(qǐng)求頭/響應(yīng)頭在服務(wù)器與客戶端之間傳輸, 大小限制為4KB;
- 一臺(tái)服務(wù)器在一個(gè)客戶端最多保存20個(gè)Cookie;
- 一個(gè)瀏覽器最多保存300個(gè)Cookie;
Cookie的key/value均不能保存中文,如果需要,可以在保存前對(duì)中文進(jìn)行編碼, 取出時(shí)再對(duì)其解碼.
Java-Cookie
在Java中使用Cookie, 必須熟悉javax.servlet.http.Cookie類, 以及HttpServletRequest/HttpServletResponse接口提供的幾個(gè)方法:
| Cookie(String name, String value) | Constructs a cookie with the specified name and value. |
| String getName() | Returns the name of the cookie. |
| String getValue() | Gets the current value of this Cookie. |
| void setValue(String newValue) | Assigns a new value to this Cookie. |
| void setMaxAge(int expiry) | Sets the maximum age in seconds for this Cookie. |
| int getMaxAge() | Gets the maximum age in seconds of this Cookie. |
| void setPath(String uri) | Specifies a path for the cookie to which the client should return the cookie. |
| void setDomain(String domain) | Specifies the domain within which this cookie should be presented. |
| Cookie[] getCookies() | Returns an array containing all of the Cookie objects the client sent with this request. |
| void addCookie(Cookie cookie) | Adds the specified cookie to the response. |
- 示例: 獲取上次訪問時(shí)間
從Request中獲取Cookie: last_access_time, 如果沒有則新建,否則顯示last_access_time內(nèi)容, 并更新為當(dāng)前系統(tǒng)時(shí)間, 最后放入Response:
有效期
Cookie的Max-Age決定了Cookie的有效期,單位為秒.Cookie類通過getMaxAge()與setMaxAge(int maxAge)方法來讀寫Max-Age屬性:
| 0 | Cookie立即作廢(如果原先瀏覽器已經(jīng)保存了該Cookie,那么可以通過設(shè)置Max-Age為0使其失效) |
| < 0 | 默認(rèn),表示只在瀏覽器內(nèi)存中存活,一旦瀏覽器關(guān)閉則Cookie銷毀 |
| > 0 | 將Cookie持久化到硬盤上,有效期由Max-Age決定 |
Set-Cookie: last_access_time="xxx"; Domain=.fq.com
該響應(yīng)首部就是在告訴瀏覽器將Cookie last_access_time="xxx"發(fā)送給域”.fq.com”中的所有站點(diǎn)(如www.fq.com, mail.fq.com).
Cookie類通過setDomain()方法設(shè)置域?qū)傩?
如果沒有指定域, 則Domain默認(rèn)為產(chǎn)生Set-Cookie響應(yīng)的服務(wù)器主機(jī)名.
路徑屬性
Cookie規(guī)范允許用戶將Cookie與部分Web站點(diǎn)關(guān)聯(lián)起來.該功能可通過向Set-Cookie響應(yīng)首部添加Path屬性來實(shí)現(xiàn):
Set-Cookie:last_access_time="Tue Apr 26 19:35:16 CST 2016"; Path=/servlet/這樣如果訪問http://www.example.com/hello_http_servlet.do就不會(huì)獲得last_access_time,但如果訪問http://www.example.com/servlet/index.html, 就會(huì)帶上這個(gè)Cookie.
Cookie類中通過setPath()方法設(shè)置路徑屬性.
如果沒有指定路徑, Path默認(rèn)為產(chǎn)生Set-Cookie響應(yīng)的URL的路徑.
Session
在所有的會(huì)話跟蹤技術(shù)中, Session是功能最強(qiáng)大,最多的. 每個(gè)用戶可以沒有或者有一個(gè)HttpSession對(duì)象, 并且只能訪問他自己的Session對(duì)象.
與URL重寫, 隱藏表單域和Cookie不同, Session是保存在服務(wù)器內(nèi)存中的數(shù)據(jù),在達(dá)到一定的閾值后, Servlet容器會(huì)將Session持久化到輔助存儲(chǔ)器中, 因此最好將使保存到Session內(nèi)的對(duì)象實(shí)現(xiàn)java.io.Serializable接口.
使用Session, 必須熟悉javax.servlet.http.HttpSession接口, 以及HttpServletRequest接口中提供的幾個(gè)方法:
| void setAttribute(String name, Object value) | Binds an object to this session, using the name specified. |
| Object getAttribute(String name) | Returns the object bound with the specified name in this session, or null if no object is bound under the name. |
| void invalidate() | Invalidates this session then unbinds any objects bound to it. |
| Enumeration<String> getAttributeNames() | Returns an Enumeration of String objects containing the names of all the objects bound to this session. |
| void removeAttribute(String name) | Removes the object bound with the specified name from this session. |
| String getId() | Returns a string containing the unique identifier assigned to this session. |
| boolean isNew() | Returns true if the client does not yet know about the session or if the client chooses not to join the session. |
| HttpSession getSession() | Returns the current session associated with this request, or if the request does not have a session, creates one. |
| HttpSession getSession(boolean create) | Returns the current HttpSession associated with this request or, if there is no current session and create is true, returns a new session. |
| String getRequestedSessionId() | Returns the session ID specified by the client. |
示例-購物車
- domain
- 商品列表頁面(/jsp/products.jsp)
- 商品詳情(/jsp/product_details.jsp)
- 加入購物車(AddCardServlet)
- 購物車(/jsp/shopping_card.jsp)
有效期
Session有一定的過期時(shí)間: 當(dāng)用戶長(zhǎng)時(shí)間不去訪問該Session,就會(huì)超時(shí)失效,雖然此時(shí)sessionID可能還在Cookie中, 只是服務(wù)器根據(jù)該sessionID已經(jīng)找不到Session對(duì)象了.
Session的超時(shí)時(shí)間可以在web.xml中配置, 單位為分鐘:
另外一種情況: 由于sessionID保存在Cookie中且Max-Age為-1,因此當(dāng)用戶重新打開瀏覽器時(shí)已經(jīng)沒有sessionID了, 此時(shí)服務(wù)器會(huì)再創(chuàng)建一個(gè)Session,此時(shí)新的會(huì)話又開始了.而原先的Session會(huì)因?yàn)槌瑫r(shí)時(shí)間到達(dá)而被銷毀.
字符編碼
字符編碼就是以二進(jìn)制的數(shù)字來對(duì)應(yīng)字符集的字符,常見字符編碼方式有:ISO-8859-1(不支持中文),GB2312,GBK,UTF-8等.在JavaWeb中, 經(jīng)常遇到的需要編碼/解碼的場(chǎng)景有響應(yīng)編碼/請(qǐng)求編碼/URL編碼:
響應(yīng)編碼
服務(wù)器發(fā)送數(shù)據(jù)給客戶端由Response對(duì)象完成,如果響應(yīng)數(shù)據(jù)是二進(jìn)制流,就無需考慮編碼問題.如果響應(yīng)數(shù)據(jù)為字符流,那么就一定要考慮編碼問題:
response.getWriter()默認(rèn)使用ISO-889-1發(fā)送數(shù)據(jù),而該字符集不支持中文,因此遇到中文就一定會(huì)亂碼.
在需要發(fā)送中文時(shí), 需要使用:
response.setCharacterEncoding("UTF-8"); // getWriter() ...設(shè)置編碼方式,由于在getWriter()輸出前已經(jīng)設(shè)置了UTF-8編碼,因此輸出字符均為UTF-8編碼,但我們并未告訴客戶端使用什么編碼來讀取響應(yīng)數(shù)據(jù),因此我們需要在響應(yīng)頭中設(shè)置編碼信息(使用Content-Type):
response.setContentType("text/html;charset=UTF-8"); // getWriter() ...注意: 這句代碼不只在響應(yīng)頭中添加了編碼信息,還相當(dāng)于調(diào)用了一次response.setCharacterEncoding("UTF-8");
請(qǐng)求編碼
1. 瀏覽器地址欄編碼
在瀏覽器地址欄書寫字符數(shù)據(jù),由瀏覽器編碼后發(fā)送給服務(wù)器,因此如果在地址欄輸入中文,則其編碼方式由瀏覽器決定:
| IE/FireFox | GB2312 |
| Chrome | UTF-8 |
2. 頁面請(qǐng)求
如果通過頁面的超鏈接/表單向服務(wù)器發(fā)送數(shù)據(jù),那么其編碼方式由當(dāng)前頁面的編碼方式確定:
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">3. GET
當(dāng)客戶端發(fā)送GET請(qǐng)求時(shí),無論客戶端發(fā)送的數(shù)據(jù)編碼方式為何,服務(wù)端均已ISO-8859-1解碼(Tomcat8.x之后改用UTF-8),這就需要我們?cè)趓equest.getParameter()獲取數(shù)據(jù)后再轉(zhuǎn)換成正確的編碼:
private Map<String, String> convertToParameterMap(HttpServletRequest request) throws UnsupportedEncodingException {Enumeration<String> names = request.getParameterNames();Map<String, String> parameters = new HashMap<String, String>();if (names != null) {while (names.hasMoreElements()) {String name = names.nextElement();String value = request.getParameter(name);parameters.put(name, new String(value.getBytes("ISO-8859-1"), "UTF-8"));}}return parameters; }4. POST
當(dāng)客戶端發(fā)送POST請(qǐng)求時(shí),服務(wù)端也是默認(rèn)使用IOS-8859-1解碼,但POST的數(shù)據(jù)是通過請(qǐng)求體傳送過來,因此POST請(qǐng)求可以通過request.setCharacterEncoding()來指定請(qǐng)求體編碼方式:
private Map<String, String> convertToParameterMap(HttpServletRequest request) throws IOException {Map<String, String> parameters = new HashMap<String, String>();if (request.getMethod().equals("POST")) {request.setCharacterEncoding("UTF-8");Enumeration<String> names = request.getParameterNames();while (names.hasMoreElements()) {String key = names.nextElement();parameters.put(key, request.getParameter(key));}} else {Enumeration<String> names = request.getParameterNames();while (names.hasMoreElements()) {String key = names.nextElement();String value = request.getParameter(key);parameters.put(key, new String(value.getBytes("ISO-8859-1"), "UTF-8"));}}return parameters; }URL編碼
網(wǎng)絡(luò)標(biāo)準(zhǔn)RFC 1738規(guī)定:
“…Only alphanumerics [0-9a-zA-Z], the special characters "$-_.+!*'()," [not including the quotes - ed], and reserved characters used for their reserved purposes may be used unencoded within a URL.”
“只有字母和數(shù)字[0-9a-zA-Z]、一些特殊符號(hào)"$-_.+!*'(),"[不包括雙引號(hào)]、以及某些保留字,才可以不經(jīng)過編碼直接用于URL。”
如果URL中有漢字,就必須編碼后使用, 而URL編碼過程其實(shí)很簡(jiǎn)單:
首先需要指定一種字符編碼,把字符串解碼后得到byte[],然后把小于0的字節(jié)+256,再將其轉(zhuǎn)換成16進(jìn)制,最后前面再添加一個(gè)%.
這個(gè)編碼過程在Java中已經(jīng)封裝成了現(xiàn)成的庫, 可直接使用:
| static String encode(String s, String enc) | Translates a string into application/x-www-form-urlencoded format using a specific encoding scheme. |
| static String decode(String s, String enc) | Decodes a application/x-www-form-urlencoded string using a specific encoding scheme. |
注: 在Web中Tomcat容器會(huì)自動(dòng)識(shí)別URL是否已經(jīng)編碼并自動(dòng)解碼.
參考
更多有關(guān)編碼知識(shí), 可以參考:
1. 阮一峰: 關(guān)于URL編碼
2. Web開發(fā)者應(yīng)知的URL編碼知識(shí)
3. 字符集和字符編碼(Charset & Encoding)
總結(jié)
以上是生活随笔為你收集整理的Servlet - 会话跟踪的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 在表空间有足够free space的情况
- 下一篇: 刘宇凡:自媒体不是自媒体 应是志媒体