你應該瞭解的一些Tomcat基本概念(一)

Tomcat介紹

簡介

Tomcat是開源的 Java Web 應用服務器,實現了 Java EE(Java Platform Enterprise Edition)的部 分技術規範,好比 Java Servlet、Java Server Page、JSTL、Java WebSocket。Java EE 是 Sun 公 司爲企業級應用推出的標準平臺,定義了一系列用於企業級開發的技術規範,除了上述的以外,還有 EJB、Java Mail、JPA、JTA、JMS 等,而這些都依賴具體容器的實現。html

其餘的一些Web服務器

目錄結構

這些是一些關鍵的tomcat目錄:java

  • / bin-Startup, shutdown和其餘腳本。windows爲*.bat文件,linux爲 *.sh文件。
  • / conf-配置文件和相關的DTDs。這裏最重要的文件是server.xml。它是容器的主要配置文件。
  • / logs-日誌文件默認位於此處。
  • / webapps-這是您的webapp所在的位置。

工做流程

當客戶請求某個資源時,Servlet 容器使用 ServletRequest 對象把客戶的請求信息封裝起 來,而後調用 Java Servlet API 中定義的 Servlet 的一些生命週期方法,完成 Servlet 的執行, 接着把 Servlet 執行的要返回給客戶的結果封裝到 ServletResponse 對象中,最後 Servlet 容 器把客戶的請求發送給客戶,完成爲客戶的一次服務過程。linux

組織架構

Tomcat是一個基於組件的服務器,它的構成組件都是可配置的,其中最外層的是Catalina servlet容器,其餘組件按照必定的格式要求配置在這個頂層容器中。web

Tomcat的各類組件都是在Tomcat安裝目錄下的/conf/server.xml文件中配置的。apache

<Server>                                     //頂層類元素,能夠包括多個Service   
    <Service>                                //頂層類元素,可包含一個Engine,多個Connecter
        <Connector>                          //鏈接器類元素,表明通訊接口
                <Engine>                     //容器類元素,爲特定的Service組件處理客戶請求,要包含多個Host
                        <Host>               //容器類元素,爲特定的虛擬主機組件處理客戶請求,可包含多個Context
                                <Context>    //容器類元素,爲特定的Web應用處理全部的客戶請求
                                </Context>
                        </Host>
                </Engine>
        </Connector>
    </Service>
</Server>
複製代碼

因此,tomcat的體系結構以下:windows

由上圖可看出Tomca的心臟是兩個組件:Connecter和Container。一個Container能夠選擇多個Connecter,多個Connector和一個Container就造成了一個Service。Service能夠對外提供服務,而Server服務器控制整個Tomcat的生命週期。數組

容器

Servlet容器處理客戶端的請求並填充response對象。Servlet容器實現了Container接口。在Tomcat中有4種級別的容器:Engine,Host,Context和Wrapper。tomcat

Engine:表示整個Catalina Servlet引擎;安全

Host:包含一個或多個Context容器的虛擬主機;bash

Context:表示一個Web應用程序,能夠包含多個Wrapper;

Wrapper:表示一個獨立的Servlet;

4個層級接口的標準實現分別是:StandardEngine類,StandardHost類,StandardContext類和StandardWrapper類。它們在org.apache.catalina.core包下。

Engine

Engine

Engine是表示整個Catalina Servlet引擎的容器。它在如下類型的場景中頗有用:

1)但願使用攔截器來查看整個引擎的每一個處理請求。

2)但願使用獨立的http鏈接器運行catalina,但仍但願支持多個虛擬主機。

一般,在部署鏈接到web服務器(如apache)的catalina時,您不會使用引擎,由於鏈接器將利用web服務器的功能來肯定應該使用哪一個上下文(甚至多是哪一個Wrapper)來處理此請求。

附加到Engine的子容器一般是Host(表示虛擬Host)或上下文(表示單個servlet上下文)的實現,具體取決於Engine實現。

若是使用,Engine始終是catalina層次結構中的頂級容器。所以,實現的setParent()方法應該拋出IllegalArgumentException。

Host

Host是一個容器,表示Catalina Servlet引擎中的虛擬Host。它在如下類型的場景中頗有用:

1)但願使用Interceptors來查看此特定虛擬Host處理的每一個請求。

2)但願使用獨立的http鏈接器運行catalina,但仍但願支持多個虛擬主機。

一般,在部署鏈接到web服務器(如apache)的catalina時,您不會使用Host,由於鏈接器將利用web服務器的功能來肯定應該使用哪一個上下文(甚至多是哪一個Wrapper)來處理此請求。

Host的父容器一般是一個Engine,但多是其餘一些實現,或者在不須要時能夠省略。

主機的子容器一般是Context(表示單個servlet上下文)。

Context

Context是一個容器,表示catalina servlet引擎中的一個servlet上下文,所以是一個單獨的web應用程序。

所以,它在catalina的幾乎全部部署中都頗有用(即便鏈接到web服務器(如apache)的鏈接器使用web服務器的工具來標識處理此請求的適當Wrapper也是如此。

它還提供了一種使用攔截器的方便機制,攔截器能夠查看這個特定web應用程序處理的每一個請求。

上下文的父容器一般是Host,但多是其餘一些實現,或者在不須要時能夠省略。

上下文的子容器一般是Wrapper的實現(表示單個servlet定義)。

Wrapper

Wrapper是一個容器,它表示來自web應用程序的部署描述符的一個單獨的servlet定義。它提供了一種方便的機制來使用攔截器,攔截器能夠看到這個定義所表示的對servlet的每一個請求。

wrapper的實現負責管理其底層servlet類的servlet生命週期,包括在適當的時候調用init()和destroy(),以及考慮servlet類自己是否存在單線程模型聲明。

Wrapper的父容器一般是context的實現,表示這個servlet在其中執行的servlet上下文(所以是web應用程序)。

Wrapper實現上不容許使用子容器,所以addChild()方法應該拋出illegalargumentexception

Pipeline

每一個管道上面都有閥門,PipelineValve關係也是同樣的。Valve表明管道上的閥門,能夠控制管道的流向,固然每一個管道上能夠有多個閥門。若是把Pipeline比做公路的話,那麼Valve能夠理解爲公路上的收費站,車表明Pipeline中的內容,那麼每一個收費站都會對其中的內容作一些處理(收費,查證件等)。

Pipeline描述在調用invoke()方法時應按順序執行的閥集合的接口。要求管道中的某個閥門(一般是最後一個)必須處理請求並建立相應的響應,而不是試圖傳遞請求。

一般有一個單獨的管道實例與每一個容器相關聯。容器的正常請求處理功能一般封裝在容器特定的閥門中,該閥門應始終在管道的末端執行。爲了實現這一點,官方提供了setbasic()方法來設置老是最後執行的valve實例。在執行基礎閥以前,將按照添加順序執行其餘閥門。

基礎閥的做用是鏈接當前容器的下一個容器(一般是本身的自容器),能夠說基礎閥是兩個容器之間的橋樑。運行圖以下:

能夠看到在同一個 Pipeline上能夠有多個 Valve,每一個 Valve均可以作一些操做,不管是 Pipeline仍是 Valve操做的都是 RequestResponse。而在容器之間 PipelineValve則起到了橋樑的做用。

源碼剖析

1)Valve

package org.apache.catalina;

import java.io.IOException;

import javax.servlet.ServletException;

import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;

public interface Valve {

    public Valve getNext();
    
    public void setNext(Valve valve);

    public void backgroundProcess();

    public void invoke(Request request, Response response)
        throws IOException, ServletException;

    public boolean isAsyncSupported();
}
複製代碼

方法並非不少,首先,Pipeline上有許多valve,這些valve存放的方式更像是鏈表,當獲取到一個valve實例時,能夠經過getNext()獲取下一個。setNext則是設置當前valve的下一個valve實例。

2)Pipeline

package org.apache.catalina;

import java.util.Set;

public interface Pipeline extends Contained {

    public Valve getBasic();

    public void setBasic(Valve valve);

    public void addValve(Valve valve);

    public Valve[] getValves();

    public void removeValve(Valve valve);

    public Valve getFirst();

    public boolean isAsyncSupported();

    public void findNonAsyncValves(Set<String> result);
}
複製代碼

能夠看出Pipeline中不少的方法都是操做Valve的,包括獲取,設置,移除Valve,getFirst()返回的是Pipeline上的第一個Valve,而getBasic(),setBasic()則是獲取/設置基礎閥,咱們都知道在Pipeline中,每一個pipeline至少都有一個閥門,叫作基礎閥,而getBasic(),setBasic()則是操做基礎閥的。

接下來 ,咱們看實現類StandardPipeline的幾個重要方法。

1:startInternal
protected synchronized void startInternal() throws LifecycleException {

    // Start the Valves in our pipeline (including the basic), if any
    Valve current = first;
    if (current == null) {
        current = basic;
    }
    while (current != null) {
        if (current instanceof Lifecycle)
            ((Lifecycle) current).start();
        current = current.getNext();
    }
 
    setState(LifecycleState.STARTING);
}
複製代碼

組件的start()方法,將first(第一個閥門)賦值給current變量,若是current爲空,就將basic(也就是基礎閥)賦值給current,接下來遍歷單向鏈表,調用每一個對象的start()方法,最後將組件(pipeline)狀態設置爲STARTING(啓動中)。

2:setBasic
public void setBasic(Valve valve) {

    // 只有必要時纔會改變
    Valve oldBasic = this.basic;
    if (oldBasic == valve)
        return;

    // 條件符合的話,中止舊的基礎閥
    if (oldBasic != null) {
        if (getState().isAvailable() && (oldBasic instanceof Lifecycle)) {
            try {
                ((Lifecycle) oldBasic).stop();
            } catch (LifecycleException e) {
                log.error(sm.getString("standardPipeline.basic.stop"), e);
            }
        }
        if (oldBasic instanceof Contained) {
            try {
                ((Contained) oldBasic).setContainer(null);
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
            }
        }
    }

    // 條件符合的話,開啓valve
    if (valve == null)
        return;
    if (valve instanceof Contained) {
        ((Contained) valve).setContainer(this.container);
    }
    if (getState().isAvailable() && valve instanceof Lifecycle) {
        try {
            ((Lifecycle) valve).start();
        } catch (LifecycleException e) {
            log.error(sm.getString("standardPipeline.basic.start"), e);
            return;
        }
    }

    // 更新pipeline的閥
    Valve current = first;
    while (current != null) {
        if (current.getNext() == oldBasic) {
            current.setNext(valve);
            break;
        }
        current = current.getNext();
    }

    this.basic = valve;

}
複製代碼

這是用來設置基礎閥的方法,這個方法在每一個容器的構造函數中調用,代碼邏輯也比較簡單,稍微注意的地方就是閥門鏈表的遍歷。

3:addValve
public void addValve(Valve valve) {

    // 驗證Valve 關聯Container
    if (valve instanceof Contained)
            ((Contained) valve).setContainer(this.container);
            
    // 若是符合條件,就啓動valve
    if (getState().isAvailable()) {
        if (valve instanceof Lifecycle) {
            try {
                ((Lifecycle) valve).start();
            } catch (LifecycleException e) {
                log.error(sm.getString("standardPipeline.valve.start"), e);
            }
        }
  
    // 若是first爲空,就將valve賦值給first,並將下個valve設置爲基礎閥(由於爲空說明只有一個基礎閥)
    if (first == null) {
        first = valve;
        valve.setNext(basic);
    } else {
    	// 遍歷閥門鏈表,將valve設置在基礎閥以前
        Valve current = first;
        while (current != null) {
            if (current.getNext() == basic) {
                current.setNext(valve);
                valve.setNext(basic);
                break;
            }
            current = current.getNext();
        }
        
  	//觸發添加事件
    container.fireContainerEvent(Container.ADD_VALVE_EVENT, valve);
}
複製代碼

這方法是像容器中添加Valve,在server.xml解析的時候也會調用該方法。

4:getValves
public Valve[] getValves() {

    List<Valve> valveList = new ArrayList<>();
    Valve current = first;
    if (current == null) {
        current = basic;
    }
    while (current != null) {
        valveList.add(current);
        current = current.getNext();
    }

    return valveList.toArray(new Valve[0]);

}
複製代碼

獲取全部的閥門,其實就是將閥門鏈表添加到一個集合內,最後轉成數組返回。

5:removeValve
public void removeValve(Valve valve) {

    Valve current;
    
    // 若是first是要被刪除的結點,那麼將first指向下一位,current置空
    if(first == valve) {
        first = first.getNext();
        current = null;
    } else {
    	// 將current指向first
        current = first;
    }
    while (current != null) {
    	// 相似鏈表刪除,將要被刪除的valve前一位指向它的後一位(valve不會是first)
        if (current.getNext() == valve) {
            current.setNext(valve.getNext());
            break;
        }
        current = current.getNext();
    }

	// 若是first==basic,first置空。first嚴格定義是 除了基礎閥的第一個閥門。
    if (first == basic) first = null;

    if (valve instanceof Contained)
        ((Contained) valve).setContainer(null);

	// 停用並銷燬valve
    if (valve instanceof Lifecycle) {
        // Stop this valve if necessary
        if (getState().isAvailable()) {
            try {
                ((Lifecycle) valve).stop();
            } catch (LifecycleException e) {
                log.error(sm.getString("standardPipeline.valve.stop"), e);
            }
        }
        try {
            ((Lifecycle) valve).destroy();
        } catch (LifecycleException e) {
            log.error(sm.getString("standardPipeline.valve.destroy"), e);
        }
    }

	// 觸發container的移除valve事件
    container.fireContainerEvent(Container.REMOVE_VALVE_EVENT, valve);
}
複製代碼

它用來刪除指定的valve,在destroyInternal方法中被調用。

6:getFirst
public Valve getFirst() {
    if (first != null) {
        return first;
    }

    return basic;
}
複製代碼

在方法5中咱們也看到了,first指向的是容器第一個非基礎閥門的閥門,從方法6中也能夠看出來,first在只有一個基礎閥的時候並不會指向基礎閥,由於若是指向基礎閥的話就不須要判斷非空而後返回基礎閥了,這是個須要注意的點!

AccessLog

用於valve以指示valve提供訪問日誌記錄。tomcat內部使用它來標識記錄訪問請求的閥門,以便在處理鏈的早期被拒絕的請求仍然能夠添加到訪問日誌中。

此接口的實現應該是健壯的,以防提供的request和response對象爲空、具備空屬性或任何其餘「異常」,這些「異常」多是因爲試圖記錄一個幾乎確定被拒絕的請求,由於該請求的格式不正確。

其中,AccessLog的配置能夠在server.xml的Host中找到:

<Host name="localhost"  appBase="webapps"
      unpackWARs="true" autoDeploy="true">

  <!-- SingleSignOn valve, share authentication between web applications
       Documentation at: /docs/config/valve.html -->
  <!--
  <Valve className="org.apache.catalina.authenticator.SingleSignOn" />
  -->

  <!-- Access log processes all example.
       Documentation at: /docs/config/valve.html
       Note: The pattern used is equivalent to using pattern="common" -->
  <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
         prefix="localhost_access_log" suffix=".txt"
         pattern="%h %l %u %t &quot;%r&quot; %s %b" />

</Host>
複製代碼

Access Log Valve用來建立日誌文件,它能夠與任何Catalina容器關聯,記錄該容器處理的全部請求。輸出文件將放在由directory屬性指定的目錄中。文件名由配置的前綴、時間戳和後綴的串聯組成。文件名中時間戳的格式可使用filedateformat屬性設置。若是經過將rotatable設置爲false來關閉文件旋轉,則將省略此時間戳。pattern項的修改,能夠改變日誌輸出的內容。

參數/選項說明:

參數 含義
className 實現的java類名,必須設置成org.apache.catalina.valves.AccessLogValve
directory 存放日誌文件的目錄,若是指定了相對路徑,則會將其解釋爲相對於catalina_base。若是未指定目錄屬性,則默認值爲「logs」(相對於catalina_base)。
pattern 須要記錄的日誌信息的格式佈局,若是是」common」或者」combined」,說明是使用的標準記錄格式,也有自定義的格式,下面會詳細說明
prefix 日誌文件名的前綴,若是沒有指定,缺省值是」access_log.;(要注意後面有個小點)
resolveHosts 將遠端主機的IP經過DNS查詢轉換成主機名,設爲true。若是爲false,忽略DNS查詢,報告遠端主機的IP地址
sufix 日誌文件的後綴名。(sufix=」.log」);也須要注意有個小點
rotatable 缺省值爲true,決定日誌是否要翻轉,若是爲false則永不翻轉,而且忽略fileDateFormat,謹慎使用。
condition 打開條件日誌
fileDateFormat 容許在日誌文件名稱中使用定製的日期格式。日誌的格式也決定了日誌文件翻轉的頻率。

Pattern:

​ %a 遠端IP ​ %A 本地IP ​ %b 發送的字節數,不包含HTTP頭,若是爲0,使用」-」 ​ %B 發送的字節數,不包含HTTP頭 ​ %h 遠端主機名(若是resolveHosts=false),遠端的IP ​ %H 請求協議 ​ %l 從identd返回的遠端邏輯用戶名,老是返回’-’ ​ %m 請求的方法 ​ %p 收到請求的本地端口號 ​ %q 查詢字符串 ​ %r 請求的第一行 ​ %s  響應的狀態碼 ​ %S 用戶的sessionID ​ %t 日誌和時間,使用一般的log格式 ​ %u 認證之後的遠端用戶(若是存在的話,不然爲’-’) ​ %U 請求的URI路徑 ​ %v 本地服務器的名稱 ​ %D 處理請求的時間,以毫秒爲單位 ​ %T 處理請求的時間,以秒爲單位

Realm

首先說一下什麼是Realm,能夠把它理解成「域」,也能夠理解成「組」,由於它相似 類Unix系統 中組的概念。

Realm域提供了一種用戶密碼與web應用的映射關係。

由於tomcat中能夠同時部署多個應用,所以並非每一個管理者都有權限去訪問或者使用這些應用,所以出現了用戶的概念。可是想一想,若是每一個應用都去配置具備權限的用戶,那是一件很麻煩的事情,所以出現了role這樣一個概念。具備某一角色,就能夠訪問該角色對應的應用,從而達到一種域的效果。

每一個用戶咱們能夠設置不一樣的角色(在tomcat-users.xml中配置)。

每一個應用中會設定能夠訪問的角色(在web.xml中配置)。

當tomcat啓動後,就會經過Realm進行驗證(在server.xml中配置),經過驗證才能夠訪問該應用,從而達到角色安全管理的做用。

server.xml

<Realm className="org.apache.catalina.realm.LockOutRealm">
  <!-- This Realm uses the UserDatabase configured in the global JNDI
       resources under the key "UserDatabase".  Any edits
       that are performed against this UserDatabase are immediately
       available for use by the Realm.  -->
  <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
         resourceName="UserDatabase"/>
</Realm>
複製代碼

默認狀況下,Realm的配置位置是在Engine標籤內部,而且使用的是UserDatabase的方式。其餘的方式會在下面部分說明。

其中Realm的不一樣位置也會影響到它做用的範圍。

1 、在元素內部 —— Realm將會被全部的虛擬主機上的web應用共享,除非它被或者元素內部的Realm元素重寫。

2 、在元素內部 —— 這個Realm將會被本地的虛擬主機中的全部的web應用共享,除非被元素內部的Realm元素重寫。

3 、在元素內部 —— 這個Realm元素僅僅被該Context指定的應用使用。

配置

1)配置server.xml

img

上圖中的代碼配置了UserDatabase的目錄文件,爲conf/tomcat-users.xml。

2)在tomcat-users.xml中配置用戶密碼以及分配角色

3)在應用的web.xml中配置訪問角色以及安全限制的內容

<security-constraint>
    <web-resource-collection>
      <web-resource-name>HTML Manager interface (for humans)</web-resource-name>
      <url-pattern>/html/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
       <role-name>manager-gui</role-name>
    </auth-constraint>
  </security-constraint>
  <security-constraint>
    <web-resource-collection>
      <web-resource-name>Text Manager interface (for scripts)</web-resource-name>
      <url-pattern>/text/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
       <role-name>manager-script</role-name>
    </auth-constraint>
  </security-constraint>
  <security-constraint>
    <web-resource-collection>
      <web-resource-name>JMX Proxy interface</web-resource-name>
      <url-pattern>/jmxproxy/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
       <role-name>manager-jmx</role-name>
    </auth-constraint>
  </security-constraint>
  <security-constraint>
    <web-resource-collection>
      <web-resource-name>Status interface</web-resource-name>
      <url-pattern>/status/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
       <role-name>manager-gui</role-name>
       <role-name>manager-script</role-name>
       <role-name>manager-jmx</role-name>
       <role-name>manager-status</role-name>
    </auth-constraint>
  </security-constraint>

  <!-- Define the Login Configuration for this Application -->
  <login-config>
    <auth-method>BASIC</auth-method>
    <realm-name>Tomcat Manager Application</realm-name>
  </login-config>

  <!-- Security roles referenced by this web application -->
  <security-role>
    <description>
      The role that is required to access the HTML Manager pages
    </description>
    <role-name>manager-gui</role-name>
  </security-role>
  <security-role>
    <description>
      The role that is required to access the text Manager pages
    </description>
    <role-name>manager-script</role-name>
  </security-role>
  <security-role>
    <description>
      The role that is required to access the HTML JMX Proxy
    </description>
    <role-name>manager-jmx</role-name>
  </security-role>
  <security-role>
    <description>
      The role that is required to access to the Manager Status pages
    </description>
    <role-name>manager-status</role-name>
  </security-role>
複製代碼

這是manager項目中的web.xml中的內容,其中,role-name定義了能夠訪問的角色。其餘內容中上面定義了限制訪問的資源,下面的Login-config比較重要。

它定義了驗證的方式,BASIC就是基本的彈出對話框輸入用戶名密碼。仍是DIGEST方式,這種方式會對網絡中的傳輸信息進行加密,更安全。

相關文章
相關標籤/搜索