溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何深入Tomcat源碼分析Session

發(fā)布時間:2021-12-08 18:23:26 來源:億速云 閱讀:130 作者:柒染 欄目:大數(shù)據(jù)

如何深入Tomcat源碼分析Session,針對這個問題,這篇文章詳細(xì)介紹了相對應(yīng)的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。

Session到底是個啥?

我們都知道,HTTP協(xié)議本身是無狀態(tài)的(Stateless),這對于一些簡單的頁面展示來說,功能足夠,不受影響。而對于日漸復(fù)雜的動態(tài)頁面、應(yīng)用,各種需要登錄認(rèn)證等場景,就力不從心了。試想對于一個已經(jīng)登錄的用戶仍然一次次的提示登錄,會是什么感受呢?

因此,多數(shù)需要保持狀態(tài)的應(yīng)用,就要保證客戶端和服務(wù)端交互狀態(tài)的一致。對于瀏覽器發(fā)起的多次請求,僅基于HTTP協(xié)議,是無法識別出是否為同一用戶請求的。而為了保持客戶端和服務(wù)端交互狀態(tài),可以采取一系列的策略,例如:

  • Cookie

  • 隱藏form 表單域

  • Session

  • URL

  • SSL

下面將通過深入Tomcat源碼,分析這一最常用的Servlet容器內(nèi)部是如何使用Session來保持客戶端與服務(wù)器狀態(tài)一致的。

做為一名Web應(yīng)用開發(fā)者,我們一定都聽說過,甚至了解過Session這個詞,以及其背后代表的一些概念。

Session,中文稱之為會話,用于兩個設(shè)備之間交互時狀態(tài)的保持。因此,會話中至少要有一方需要保存會話的狀態(tài)。

在Servlet規(guī)范中,session對象由HttpSession這一接口來表示,接口描述簡明的概括了其主要作用

Provides a way to identify a user across more than one page request or

visit to a Web site and to store information about that user.


The servlet container uses this interface to create a session between an HTTP

client and an HTTP server. The session persists for a specified time period,

across more than one connection or page request from the user. A session

usually corresponds to one user.

而Tomcat內(nèi)部則是通過StandardSession實現(xiàn)了HttpSession這個接口,內(nèi)部統(tǒng)一使用StandardSession來處理。

在初次請求應(yīng)用時,如果需要用到Session,則會創(chuàng)建之。一般的Servlet中并不會直接使用。而如果是請求JSP文件,由于JSP默認(rèn)的隱式對象中是包含

session的,其在生成Servlet文件時,內(nèi)部相當(dāng)于包含了

HttpServletRequest.getSession(true)

因此,請求時會直接創(chuàng)建session對象。

創(chuàng)建session的過程,大致是下面的樣子:

protected Session doGetSession(boolean create) {

// There cannot be a session if no context has been assigned yet

Context context = getContext();

if (context == null) {

return (null);

}

// Return the current session if it exists and is valid

if ((session != null) && !session.isValid()) {

session = null;

}

if (session != null) {

return (session);

}


// Return the requested session if it exists and is valid

Manager manager = context.getManager();

if (manager == null) {

return (null); // Sessions are not supported

}

if (requestedSessionId != null) {

try {

session = manager.findSession(requestedSessionId);

} catch (IOException e) {

session = null;

}

if ((session != null) && !session.isValid()) {

session = null;

}

if (session != null) {

session.access();

return (session);

}

}


// Create a new session if requested and the response is not committed

if (!create) {

return (null);

}

if (response != null

&& context.getServletContext()

.getEffectiveSessionTrackingModes()

.contains(SessionTrackingMode.COOKIE)

&& response.getResponse().isCommitted()) {

throw new IllegalStateException(

sm.getString("coyoteRequest.sessionCreateCommitted"));

}


// Attempt to reuse session id if one was submitted in a cookie

// Do not reuse the session id if it is from a URL, to prevent possible

// phishing attacks

// Use the SSL session ID if one is present.

if (("/".equals(context.getSessionCookiePath())

&& isRequestedSessionIdFromCookie()) || requestedSessionSSL ) {

session = manager.createSession(getRequestedSessionId());

} else {

session = manager.createSession(null);

}


// Creating a new session cookie based on that session

if (session != null

&& context.getServletContext()

.getEffectiveSessionTrackingModes()

.contains(SessionTrackingMode.COOKIE)) {

Cookie cookie =

ApplicationSessionCookieConfig.createSessionCookie(

context, session.getIdInternal(), isSecure());

response.addSessionCookieInternal(cookie);

}

if (session == null) {

return null;

}

session.access();

return session;

}

整體流程基本是先判斷是否已經(jīng)存在session,如果沒有則創(chuàng)建。如果有,則直接使用。

此時,需要注意兩個問題:

  1. 初次請求時,session如何生成并傳遞給客戶端的

  2. 后續(xù)的其它請求中,如果將客戶端的請求與服務(wù)端已有的session建立關(guān)聯(lián)的

上面代碼中,判斷session不存在并創(chuàng)建的過程,是直接調(diào)用createSession這個方法,并會根據(jù)sessionId是否為空,來確定是完全新創(chuàng)建session,還是恢復(fù)已有session。

public Session createSession(String sessionId) {

if ((maxActiveSessions >= 0) &&

(getActiveSessions() >= maxActiveSessions)) {

rejectedSessions++;

throw new TooManyActiveSessionsException(

sm.getString("managerBase.createSession.ise"),

maxActiveSessions); //注意這里有個策略

}

// Recycle or create a Session instance

Session session = createEmptySession();

// Initialize the properties of the new session and return it

session.setNew(true);

session.setValid(true);

session.setCreationTime(System.currentTimeMillis());

session.setMaxInactiveInterval(this.maxInactiveInterval);

String id = sessionId;

if (id == null) {

id = generateSessionId();

}

session.setId(id);

sessionCounter++;

SessionTiming timing = new SessionTiming(session.getCreationTime(), 0);

synchronized (sessionCreationTiming) {

sessionCreationTiming.add(timing);

sessionCreationTiming.poll();

}

return (session);

}


這里有個Session超時時間,即最大空閑時間


注意此處maxInactiveInterval的值,即為我們默認(rèn)的web.xml中提供的session超時時間(后臺回復(fù)關(guān)鍵字004,了解更多),為30分鐘。

在創(chuàng)建完Session之后,Tomcat通過在響應(yīng)頭中設(shè)置Set-Cookie這個MimeHeader來返回給客戶端session數(shù)據(jù)。返回的數(shù)據(jù)是這樣的:

JSESSIONID=CC4D83F3A61823AA8F980C89890A19D7; Path=/manager/; HttpOnly

設(shè)置Header的過程如下:

public void addSessionCookieInternal(final Cookie cookie) {

if (isCommitted()) { //此處判斷,如果response已經(jīng)提交,則不能再設(shè)置

return;

}

String name = cookie.getName();

final String headername = "Set-Cookie";

final String startsWith = name + "=";

String header = generateCookieString(cookie); //此處根據(jù)具體cookie的內(nèi)容生成對應(yīng)的串,內(nèi)部會判斷cookie的版本,過期時間等


if (!set) {

addHeader(headername, header);

} }


我們看到,初次請求時,響應(yīng)頭中包含了高亮的數(shù)據(jù)。


那再次請求呢,我們看到這次響應(yīng)頭中沒有sessionId的數(shù)據(jù),而是轉(zhuǎn)移到請求頭中,并且是以Cookie的形式提供:


此時,傳到服務(wù)端,服務(wù)端解析Cookie對應(yīng)的JSESSIOONID,并提取對應(yīng)的sessionId值,與服務(wù)端對應(yīng)的session數(shù)據(jù)做關(guān)聯(lián)。

我們看代碼中的實現(xiàn)

再次請求時,從Request中獲取SessionCookie的地方在這里:

CoyoteAdapter.postParseRequset()

其內(nèi)部調(diào)用 parseSessionCookiesId(request), 解析請求頭中的cookie數(shù)據(jù)。

public void parseCookieHeader(MimeHeaders headers, ServerCookies serverCookies) {

// process each "cookie" header

int pos = headers.findHeader("Cookie", 0);

}

}

此處需要注意SessionCookie的名稱是允許配置的,因此這一名稱不一定一直都是JSESSIONID。

在解析Cookie獲取SessionId之后,我們拿到的僅僅是一個字符串,還不能馬上和Session關(guān)聯(lián)起來,此時request會將此值賦值給其內(nèi)部的一個名為

requestSessionId的屬性。

當(dāng)后面再次請求session時,就和我們最上面代碼看到的一樣,會有一個findSession的過程,

到此,我們基本了解了客戶端瀏覽器和服務(wù)端Tomcat之間,如果保持交互狀態(tài)的一致中的一種實現(xiàn)方式,即SessionCookie。

而本質(zhì)上,這一過程就是傳統(tǒng)上Socket交互的一個過程,我們完全可以自已寫一段代碼模擬返回響應(yīng)的數(shù)據(jù),只是需要注意響應(yīng)頭數(shù)據(jù)在HTTP規(guī)范中有特定的格式要求,如下,即數(shù)據(jù)之間要以CRLF分隔

總結(jié)下,客戶端初次請求服務(wù)端會創(chuàng)建Session,此時通過在響應(yīng)頭中設(shè)置Set-Cookie將sessionId傳遞給客戶端。后續(xù)客戶端的請求會在請求頭中設(shè)置Cookie項帶上sessionId,從而保證客戶端與服務(wù)端交互狀態(tài)的一致。

關(guān)于如何深入Tomcat源碼分析Session問題的解答就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注億速云行業(yè)資訊頻道了解更多相關(guān)知識。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI