1. 程式人生 > >深入理解Tomcat系列之七 詳解URL請求

深入理解Tomcat系列之七 詳解URL請求

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow

也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!

                       

前言

這裡分析一個實際的請求是如何在Tomcat中被處理的,以及最後是怎麼樣找到要處理的Servlet的?當我們在瀏覽器中輸入http://hostname:port/contextPath/servletPath

,前面的hostname與port用於建立tcp連線,由於Http也是基於Tcp協議的,所以這裡涉及TCP連線的三次握手。後面的contextPath與servletPath則是與伺服器進行請求的資訊,contextPath指明瞭與伺服器中哪個Context容器進行互動,伺服器會根據這個URL與對應的Context容器建立連線,那麼這個過程是如何實現的呢?

在Tomcat7(本文也是基於Tomcat7)中主要通過一個對映來完成的,這個對映的工作交給org.apache.tomcat.util.http.mapper.Mapper類來完成的,這個類儲存了Container容器所有子容器的資訊,在請求從Connector交給Container容器之前,Mapper會根據hostname和port將host容器與context容器設定到Request的mappingData屬性中,這樣在Connector的請求進入Container容器之前就知道了交給哪個容器了。
這段程式碼如下:

程式碼清單5-4:

// Virtual host mapping        if (mappingData.host == null) {            Host[] hosts = this.hosts;            int pos = findIgnoreCase(hosts, host);            if
((pos != -1) && (host.equalsIgnoreCase(hosts[pos].name))) {                mappingData.host = hosts[pos].object;                contexts = hosts[pos].contextList.contexts;                nesting = hosts[pos].contextList.nesting;            } else {                if (defaultHostName == null) {                    return;                }                pos = find(hosts, defaultHostName);                if ((pos != -1) && (defaultHostName.equals(hosts[pos].name))) {                    mappingData.host = hosts[pos].object;                    contexts = hosts[pos].contextList.contexts;                    nesting = hosts[pos].contextList.nesting;                } else {                    return;                }            }        }        // Context mapping        if (mappingData.context == null) {            int pos = find(contexts, uri);            if (pos == -1) {                return;            }            int lastSlash = -1;            int uriEnd = uri.getEnd();            int length = -1;            boolean found = false;            while (pos >= 0) {                if (uri.startsWith(contexts[pos].name)) {                    length = contexts[pos].name.length();                    if (uri.getLength() == length) {                        found = true;                        break;                    } else if (uri.startsWithIgnoreCase("/", length)) {                        found = true;                        break;                    }                }                if (lastSlash == -1) {                    lastSlash = nthSlash(uri, nesting + 1);                } else {                    lastSlash = lastSlash(uri);                }                uri.setEnd(lastSlash);                pos = find(contexts, uri);            }            uri.setEnd(uriEnd);            if (!found) {                if (contexts[0].name.equals("")) {                    context = contexts[0];                }            } else {                context = contexts[pos];            }            if (context != null) {                mappingData.contextPath.setString(context.name);            }        }        if (context != null) {            ContextVersion[] contextVersions = context.versions;            int versionCount = contextVersions.length;            if (versionCount > 1) {                Object[] contextObjects = new Object[contextVersions.length];                for (int i = 0; i < contextObjects.length; i++) {                    contextObjects[i] = contextVersions[i].object;                }                mappingData.contexts = contextObjects;            }            if (version == null) {                // Return the latest version                contextVersion = contextVersions[versionCount - 1];            } else {                int pos = find(contextVersions, version);                if (pos < 0 || !contextVersions[pos].name.equals(version)) {                    // Return the latest version                    contextVersion = contextVersions[versionCount - 1];                } else {                    contextVersion = contextVersions[pos];                }            }            mappingData.context = contextVersion.object;            mappingData.contextSlashCount = contextVersion.slashCount;        }        // Wrapper mapping        if ((contextVersion != null) && (mappingData.wrapper == null)) {            internalMapWrapper(contextVersion, uri, mappingData);        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92

下面啟動伺服器,在瀏覽器中輸入http://localhost:8080/examples/jsp/jsp2/el/composite.jsp,斷點除錯可以mappingData.host屬性為localhost,mappingData.contextPath.setString(context.name)中context.name為examples,mappingData.wrapperPath為/jsp/jsp2/el/composite.jsp,這驗證了mappingData屬性的有效性,那麼mappingData屬性是如何設定到Request物件的屬性中的呢?

通過org.apache.catalina.connector.Request的原始碼可以知道,其是通過setContextPath方法與setHost方法設定進去的,其原始碼如下:

程式碼清單5-5:

    public void setHost(Host host) {        mappingData.host = host;    }    public void setContextPath(String path) {        if (path == null) {            mappingData.contextPath.setString("");        } else {            mappingData.contextPath.setString(path);        }    }
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

由於請求是從Connector傳過來的,而CoyoteAdapter是Connector中處理請求的最後一個類,那麼設定這兩個屬性的程式碼肯定在CoyoteAdapter類中,果不其然:

程式碼清單5-6:

 // This will map the the latest version by default    connector.getMapper().map(serverName, decodedURI, version,                              request.getMappingData());    request.setContext((Context) request.getMappingData().context);    request.setWrapper((Wrapper) request.getMappingData().wrapper);    //Mapper的map方法    public void map(MessageBytes host, MessageBytes uri, String version,                    MappingData mappingData)        throws Exception {        if (host.isNull()) {            host.getCharChunk().append(defaultHostName);        }        host.toChars();        uri.toChars();        internalMap(host.getCharChunk(), uri.getCharChunk(), version,                mappingData);    }
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

intenalMap方法執行就是程式碼清單5-4的內容,這樣就把從Connector傳入請求,並設定Request物件的mappingData屬性的整個流程就打通了。還有一個疑問是為什麼Mapper類中可以擁有Container所有子容器的資訊呢?答案需要回到Tomcat啟動過程圖的第21步的startIntenal方法了:

程式碼清單5-7:

    public void startInternal() throws LifecycleException {        setState(LifecycleState.STARTING);        // Find any components that have already been initialized since the        // MBean listener won't be notified as those components will have        // already registered their MBeans        findDefaultHost();        Engine engine = (Engine) connector.getService().getContainer();        addListeners(engine);        Container[] conHosts = engine.findChildren();        for (Container conHost : conHosts) {            Host host = (Host) conHost;            if (!LifecycleState.NEW.equals(host.getState())) {                // Registering the host will register the context and wrappers                registerHost(host);            }        }    }
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

這段程式碼就是將MapperListener作為一個監聽者加到整個Container容器的每一個子容器中,這樣任何一個子容器發生變化,MapperListener都將被通知,響應的mappingData屬性也會改變。最後可以總結訪問請求地址為http://localhost:8080/examples/composite.jsp的處理過程:

  1. 在埠8080啟動Server,並通知Service完成啟動,Service通知Connector完成初始化和啟動的過程
  2. Connector首先收到這個請求,會呼叫ProtocolHandler完成http協議的解析,然後交給SocketProcessor處理,解析請求頭,再交給CoyoteAdapter解析請求行和請求體,並把解析資訊封裝到Request和Response物件中
  3. 把請求(此時應該是Request物件,這裡的Request物件已經封裝了Http請求的資訊)交給Container容器
  4. Container容器交給其子容器——Engine容器,並等待Engine容器的處理結果
  5. Engine容器匹配其所有的虛擬主機,這裡匹配到Host
  6. 請求被移交給hostname為localhost的Host容器,host匹配其所有子容器Context,這裡找到contextPath為/examples的Context容器。如果匹配不到就把該請求交給路徑名為”“的Context去處理
  7. 請求再次被移交給Context容器,Context繼續匹配其子容器Wrapper,由Wrapper容器載入composite.jsp對應的servlet,這裡編譯的servlet是basic_002dcomparisons_jsp.class檔案
  8. Context容器根據字尾匹配原則*.jsp找到composite.jsp編譯的java類的class檔案
  9. Connector構建一個org.apache.catalina.connector.Request以及org.apache.catalina.connector.Response物件,使用反射呼叫Servelt的service方法
  10. Context容器把封裝了響應訊息的Response物件返回給Host容器
  11. Host容器把Response返回給Engine容器
  12. Engine容器返回給Connector
  13. Connetor容器把Response返回給瀏覽器
  14. 瀏覽器解析Response報文
  15. 顯示資源內容

根據前面的內容,其中的對映關係是由MapperListener類完成的。

           

給我老師的人工智慧教程打call!http://blog.csdn.net/jiangjunshow

這裡寫圖片描述