Web伺服器的工作原理

ImportNew - 進林發表於2015-02-13

Web伺服器工作原理概述

很多時候我們都想知道,web容器或web伺服器(比如Tomcat或者jboss)是怎樣工作的?它們是怎樣處理來自全世界的http請求的?它們在幕後做了什麼動作?Java Servlet API(例如ServletContext,ServletRequest,ServletResponse和Session這些類)在其中扮演了什麼角色?這些都是web應用開發者或者想成為web應用開發者的人必須要知道的重要問題或概念。在這篇文章裡,我將會盡量給出以上某些問題的答案。請集中精神!

文章章節:

  • 什麼是web伺服器、應用伺服器和web容器?
  • 什麼是Servlet?他們有什麼作用?
  • 什麼是ServletContext?它由誰建立?
  • ServletRequest和ServletResponse從哪裡進入生命週期?
  • 如何管理Session?知道cookie嗎?
  • 如何確保執行緒安全?

什麼是web伺服器,應用伺服器和web容器?

我先討論web伺服器和應用伺服器。讓我在用一句話大概講講:

“在過去它們是有區別的,但是這兩個不同的分類慢慢地合併了,而如今在大多在情況下和使用中可以把它們看成一個整體。”

Mosaic瀏覽器(通常被認為是第一個圖形化的web瀏覽器)和超連結內容的初期,演變出了“web伺服器”的新概念,它通過HTTP協議來提供靜態頁面內容和圖片服務。在那個時候,大多數內容都是靜態的,並且HTTP 1.0只是一種傳送檔案的方式。但在不久後web伺服器提供了CGI功能。這意味著我們可以為每個web請求啟動一個程式來產生動態內容。現在,HTTP協議已經很成熟了並且web伺服器變得更加複雜,擁有了像快取、安全和session管理這些附加功能。隨著技術的進一步成熟,我們從Kiva和NetDynamics學會了公司專屬的基於Java的伺服器端技術。這些技術最終全都融入到我們今天依然在大多數應用開發裡使用的JSP中。

以上是關於web伺服器的。現在我們來討論應用伺服器

在同一時期,應用伺服器已經存在並發展很長一段時間了。一些公司為Unix開發了Tuxedo(面向事務的中介軟體)、TopEndEncina等產品,這些產品都是從類似IMS和CICS的主機應用管理和監控環境衍生而來的。大部分的這些產品都指定了“封閉的”產品專用通訊協議來互連胖客戶機(“fat” client)和伺服器。在90年代,這些傳統的應用伺服器產品開始嵌入HTTP通訊功能,剛開始要利用閘道器來實現。不久後它們之間的界線開始變得模糊了。

同時,web伺服器越來越成熟,可以處理更高的負載、更多的併發和擁有更好的特性;應用伺服器開始新增越來越多的基於HTTP的通訊功能。所有的這些導致了web伺服器與應用伺服器的界線變得更窄了。

目前,“應用伺服器”和“web伺服器”之間的界線已經變得模糊不清了。但是人們還把這兩個術語區分開來,作為強調使用。

當有人說到“web伺服器”時,你通常要把它認為是以HTTP為核心、web UI為嚮導的應用。當有人說到“應用伺服器”時,你可能想到“高負載、企業級特性、事務和佇列、多通道通訊(HTTP和更多的協議)”。但現在提供這些需求的基本上都是同一個產品。

以上就是關於web伺服器和應用伺服器的全部內容。現在我們來看看第三個術語,即web容器。

在Java方面,web容器一般是指Servlet容器。Servlet容器是與Java Servlet互動的web容器的元件。web容器負責管理Servlet的生命週期、把URL對映到特定的Servlet、確保URL請求擁有正確的訪問許可權和更多類似的服務。綜合來看,Servlet容器就是用來執行你的Servlet和維護它的生命週期的執行環境。

什麼是Servlet?他們有什麼作用?

在Java裡,Servlet使你能夠編寫根據請求動態生成內容的服務端元件。事實上,Servlet是一個在javax.servlet包裡定義的介面。它為Servlet的生命週期宣告瞭三個基本方法——init()、service()和destroy()。每個Servlet都要實現這些方法(在SDK裡定義或者使用者定義)並在它們的生命週期的特定時間由伺服器來呼叫這些方法。

類載入器通過懶載入(lazy-loading)或者預載入(eager loading)自動地把Servlet類載入到容器裡。每個請求都擁有自己的執行緒,而一個Servlet物件可以同時為多個執行緒服務。當Servlet物件不再被使用時,它就會被JVM當做垃圾回收掉。

懶載入的Servlet

預載入的Servlet

什麼是ServletContext?它由誰建立?

當Servlet容器啟動時,它會部署並載入所有的web應用。當web應用被載入時,Servlet容器會一次性為每個應用建立Servlet上下文(ServletContext)並把它儲存在記憶體裡。Servlet容器會處理web應用的web.xml檔案,並且一次性建立在web.xml裡定義的Servlet、Filter和Listener,同樣也會把它們儲存在記憶體裡。當Servlet容器關閉時,它會解除安裝所有的web應用和ServletContext,所有的Servlet、Filter和Listner例項都會被銷燬。

從Java文件可知,ServletContext定義了一組方法,Servlet使用這些方法來與它的Servlet容器進行通訊。例如,用來獲取檔案的MIME型別、轉發請求或者編寫日誌檔案。在web應用的部署檔案(deployment descriptor)標明“分散式”的情況下,web應用的每一個虛擬機器都擁有一個上下文例項。在這種情況下,不能把Servlet上下文當做共享全域性資訊的變數(因為它的資訊已經不具有全域性性了)。可以使用外部資源來代替,比如資料庫。

ServletRequest和ServletResponse從哪裡進入生命週期?

Servlet容器包含在web伺服器中,web伺服器監聽來自特定埠的HTTP請求,這個埠通常是80。當客戶端(使用web瀏覽器的使用者)傳送一個HTTP請求時,Servlet容器會建立新的HttpServletRequest和HttpServletResponse物件,並且把它們傳遞給已經建立的Filter和URL模式與請求URL匹配的Servlet例項的方法,所有的這些都使用同一個執行緒。

request物件提供了獲取HTTP請求的所有資訊的入口,比如請求頭和請求實體。response物件提供了控制和傳送HTTP響應的便利方法,比如設定響應頭和響應實體(通常是JSP生成的HTML內容)。當HTTP響應被提交併結束後,request和response物件都會被銷燬。

如何管理Session?知道cookie嗎?

當客戶端第一次訪問web應用或者第一次使用request.getSession()獲取HttpSession時,Servlet容器會建立Session,生成一個long型別的唯一ID(你可以使用session.getId()獲取它)並把它儲存在伺服器的記憶體裡。Servlet容器同樣會在HTTP響應裡設定一個Cookie,cookie的名是JSESSIONID並且cookie的值是session的唯一ID。

根據HTTP cookie規範(正規的web瀏覽器和web伺服器必須遵守的約定),在cookie的有效期間,客戶端(web瀏覽器)之後的請求都要把這個cookie返回給伺服器。Servlet容器會利用帶有名為JSESSIONID的cookie檢測每一個到來的HTTP請求頭,並使用cookie的值從伺服器內容裡獲取相關的HttpSession。

HttpSession會一直存活著,除非超過一段時間沒使用。你可以在web.xml裡設定這個時間段,預設時間段是30分鐘。因此,如果客戶端已經超過30分鐘沒有訪問web應用的話,Servlet容器就會銷燬Session。之後的每一個請求,即使帶有特定的cookie,都再也不會訪問到同一個Session了。servletcontainer會建立一個新的Session。

現有的Session

新的Session

另外,在客戶端的session cookie擁有一個預設的存活時間,這個時間與瀏覽器的執行時間相同。因此,當使用者關閉瀏覽器後(所有的標籤或者視窗),客戶端的Session就會被銷燬。重新開啟瀏覽器後,與之前的Session關聯的cookie就再也不會被髮送出去了。再次使用request.getSession()會返回一個全新的HttpSession並且使用一個全新的session ID來設定cookie。

如何確保執行緒安全?

你現在應該已經知道所有的請求都在共享Servlet和Filter。這是Java的一個很棒的特性,它是多執行緒的並且不同的執行緒(即HTTP請求)可以使用同一個例項。否則,對每一個請求都重新建立一個實體會耗費很多的資源。

你同樣要知道,你不應該使用Servlet或者Filter的例項變數來存放任何的請求或者會話範圍內的資料。這些資料會被其他Session的所有請求共享。這是非執行緒安全的!下面的例子說明了這個問題:

public class MyServlet extends HttpServlet
{
    private Object thisIsNOTThreadSafe; //Don't to this

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
    {
        Object thisIsThreadSafe;

        thisIsNOTThreadSafe = request.getParameter("foo"); // BAD!! Shared among all requests!
        thisIsThreadSafe = request.getParameter("foo"); // OK, this is thread safe.
    }
}

不要這樣做,這會導致軟體出bug。

所有的話題已經講完了。敬請期待更多的文章。建議使用電子郵件訂閱來獲取文章更新的通知。

相關文章