我們平常編寫好的HttpServlet類后,就可以處理請求了,但是服務器在接收到請求信息以后是如何將這些請求傳遞到我們編寫的Servlet類中的???這個疑問在我心中的已經很久了,現在要來解決它。
我們之前的一篇文章Tomcat中容器是什么以及容器與容器之間的數量關系。簡單介紹Tomcat服務器的容器,以及容器與容器之間的關系。現在來講一下在服務器端口接收到請求后,這些請求是如何在這些容器之間傳遞的。
一、先建立一個簡單的動態WEB工程,然后寫一個HttpServlet的實現類。代碼如下
@WebServlet("/TestServlet") public class TestServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("這個doGet方法"); request.getRequestDispatcher("/test.jsp").forward(request, response); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } }
編寫一個訪問頁面,index.jsp.
<body> <h3>主頁</h3> <a href="/Test2/TestServlet" >Test</a> </body>
一個響應頁面。
<body> <h3>成功響應</h3> </body>
二、請求的需要經過哪些地方。
請求信息會經過以下的接口(java類)及其子類來處理,就相當於要經過許多模塊來處理。
三、具體步驟
如上圖,請求在服務器的處理經過:
①、AbstractEndpoint類及其子類來處理。
AbstractEndpoint這個抽象類中有一個抽象內部類Acceptor,這個Acceptor的實現類是AbstractEndpoint的三個子類的內部類Acceptor來實現的。
我們的請求就是被這Acceptor監聽到並且接收的。這個類其實是一個線程類,因為AbstractEndpoint.Acceptor實現了Runnable接口。
@Override
public void run() {
int errorDelay = 0; // running表示endpoint的狀態,在run()方法中這個while循環,會一直循環接收數據,直到endpoint 不處於運行狀態。 while (running) { // 當endpoint暫停,但還是運行狀態就讓線程睡眠。 while (paused && running) { state = AcceptorState.PAUSED; try { Thread.sleep(50); } catch (InterruptedException e) { // Ignore } } //endpoint不處於運行狀態,就停止接收數據。 if (!running) { break; } state = AcceptorState.RUNNING;
//下面就是的接收數據的過程。 try { //if we have reached max connections, wait countUpOrAwaitConnection(); long socket = 0; try { // 從server sock 接收傳進來的連接。 socket = Socket.accept(serverSock); if (log.isDebugEnabled()) { long sa = Address.get(Socket.APR_REMOTE, socket); Sockaddr addr = Address.getInfo(sa); log.debug(sm.getString("endpoint.apr.remoteport", Long.valueOf(socket), Long.valueOf(addr.port))); } } catch (Exception e) { //省略了部分處理異常的代碼。// The processor will recycle itself when it finishes }
state = AcceptorState.ENDED; }
以上就是AprEndpoint接收請求的過程。就是用一個接收器接收請求,過程中會使用套接字。但是好像並不是有的請求都會用這個Acceptor來接收。
當接收請求完畢,經過一系列的處理后就會由AprEndpoint的內部類SocketProcessor來將請求傳給ProtocolHandler來處理。這個SocketProcessor也是一個線程類。
它有一行代碼將套接字傳給了第二步來處理。
代碼中的handler就是AbstractProtocol中的內部類AbstractConnectionHandler的實例,這樣,套接字就被傳到第②步了。
②、在AbstractConnectionHandler接收到第一步傳來的套接字以后,對套接字進行處理,下面是它進行處理的代碼。
@SuppressWarnings("deprecation") // Old HTTP upgrade method has been deprecated public SocketState process(SocketWrapper<S> wrapper, SocketStatus status) { //省略部分代碼
//這是第三步中Processor接口的實現類。connections是一個Map對象,套接字為鍵,Processor接口實現類實例為值。 Processor<S> processor = connections.get(socket); if (status == SocketStatus.DISCONNECT && processor == null) { // Nothing to do. Endpoint requested a close and there is no // longer a processor associated with this socket. return SocketState.CLOSED; } wrapper.setAsync(false); ContainerThreadMarker.markAsContainerThread(); try { if (processor == null) { processor = recycledProcessors.poll(); } if (processor == null) { processor = createProcessor(); } initSsl(wrapper, processor); SocketState state = SocketState.CLOSED; do { if (status == SocketStatus.DISCONNECT && !processor.isComet()) { // Do nothing here, just wait for it to get recycled // Don't do this for Comet we need to generate an end // event (see BZ 54022) } else if (processor.isAsync() || state == SocketState.ASYNC_END) { state = processor.asyncDispatch(status); if (state == SocketState.OPEN) { // release() won't get called so in case this request // takes a long time to process, remove the socket from // the waiting requests now else the async timeout will // fire getProtocol().endpoint.removeWaitingRequest(wrapper); // There may be pipe-lined data to read. If the data // isn't processed now, execution will exit this // loop and call release() which will recycle the // processor (and input buffer) deleting any // pipe-lined data. To avoid this, process it now.
//wapper就是套接字包裝類的對象,這里還是理解為套接字,套接字在這里傳給了第③步的Processor接口的實例。 state = processor.process(wrapper); } } else if (processor.isComet()) { state = processor.event(status); } else if (processor.getUpgradeInbound() != null) { state = processor.upgradeDispatch(); } else if (processor.isUpgrade()) { state = processor.upgradeDispatch(status); } else {
//注釋同上, state = processor.process(wrapper); } if (state != SocketState.CLOSED && processor.isAsync()) { state = processor.asyncPostProcess(); //省略掉部分代碼 } catch(java.net.SocketException e) { //將部分捕獲異常的代碼省略掉
// Make sure socket/processor is removed from the list of current // connections connections.remove(socket); // Don't try to add upgrade processors back into the pool if (!(processor instanceof org.apache.coyote.http11.upgrade.UpgradeProcessor) && !processor.isUpgrade()) { release(wrapper, processor, true, false); } return SocketState.CLOSED; }
③、第二步完成后,就會交給Processor<S>接口的實現類來處理。在AbstractHttp11Processor中的process(...)方法來處理.部分代碼如下:
在這里將會創建請求和響應,但不是我們熟悉的HttpServletRequest或HttpServletResponse類型或其子類型。而是
@Override public SocketState process(SocketWrapper<S> socketWrapper) throws IOException { //省略部分代碼 // Process the request in the adapter if (!getErrorState().isError()) { try { rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
//請求和響應在這個類中被創建並將套接字中的信息寫入的請求中。在這里,請求將會被傳遞到第④步。
//這個adapter就是CoyoteAdapter的一個實例
adapter.service(request, response); // Handle when the response was committed before a serious // error occurred. Throwing a ServletException should both // set the status to 500 and set the errorException. // If we fail here, then the response is likely already // committed, so we can't try and set headers. if(keepAlive && !getErrorState().isError() && ( response.getErrorException() != null || (!isAsync() && statusDropsConnection(response.getStatus())))) { setErrorState(ErrorState.CLOSE_CLEAN, null); } setCometTimeouts(socketWrapper); } catch (InterruptedIOException e) { //省略部分代碼 } }
④、第三步完成之后交給CoyoteAdapter來處理,CoyotoAdapter是將請求傳入Server容器的切入點。
我一直不明白coyote是什么意思,有道上說是一種產於北美大草原的小狼。。。感覺不太能理解。所以我把Adapter接口的類注釋貼上來,只有一句簡單的話,如下:
Adapter. This represents the entry point in a coyote-based servlet container.
CoyoteAdapter中有一個service()方法。這個方法持有一個Connector的引用。這個Connector又持有一個Service容器的引用,而Service容器有持有一個Container(Container的實現類有StandardEngine、StandardHost等等)的引用。所以CoyoteAdapter就可以根據這些引用將請求傳遞到Server容器中了。如下圖中的代碼:
還是將CoyoteAdapter的service(...)方法的代碼帖出來一下吧。
/** * Service method. */ @Override public void service(org.apache.coyote.Request req, org.apache.coyote.Response res) throws Exception { //下面這兩行代碼就是將請求處理后轉化為HttpServletRequest的 Request request = (Request) req.getNote(ADAPTER_NOTES); Response response = (Response) res.getNote(ADAPTER_NOTES); //如果請求為null就讓Connector來創建。 if (request == null) { // Create objects request = connector.createRequest(); request.setCoyoteRequest(req); response = connector.createResponse(); response.setCoyoteResponse(res); //省略部分代碼 } if (connector.getXpoweredBy()) { response.addHeader("X-Powered-By", POWERED_BY); } boolean comet = false; boolean async = false; boolean postParseSuccess = false; try { // Parse and set Catalina and configuration specific // request parameters req.getRequestProcessor().setWorkerThreadName(Thread.currentThread().getName()); postParseSuccess = postParseRequest(req, request, res, response); if (postParseSuccess) { //check valves if we support async request.setAsyncSupported(connector.getService().getContainer().getPipeline().isAsyncSupported()); // Calling the container 在這里將請求傳到Server容器中。 connector.getService().getContainer().getPipeline().getFirst().invoke(request, response); //省略部分代碼 } } /**
⑤、如果上面的請求傳遞到的Container是StandaradEngine,那么就會Engine就會調用它持有的StandardPipeline對象來處理請求。StandardPipeline就相當於一條管道,這條管道中的有許多閥門,這些閥門會對請求進行處理,並且控制它下一步往哪里傳遞。StandardEngine的管道使用的閥門是StandardEngineValve。
⑥、和StandardEngine一樣,StandardHost、StandardContext、StandardWrapper這幾個容器都擁有自己的一條管道StandardPipeline來處理的請求。但是需要注意的是他們使用的閥門是不一樣的。StandardHost則會使用StandardHostValve,其他的同理。。。。。。。
⑦、當最后一個StandardWrapperVale處理完請求后,此時這個請求會如何傳遞呢??此時請求已經到達了最底層的容器了。StandardWrapper就是最底層的容器,它不允許再有子容器。其實每一個StandardWrapper代表一個Servlet,因為每一個StandardWrapper都會持有一個Servlet實例的引用。閥門會將請求交給誰???看一張圖:
這是debug的截圖,右上圖可知,當最后一個StandardWrapperValve處理完請求以后,把請求交給Filter來處理,此時請求進入過濾器鏈條中,也就是我們熟悉 filter chain。
⑧、當過濾器處理完之后當然是將請求傳遞給我們編寫的HttpServlet來處理了。
以上就是請求如何,從在服務器的監聽端口一步步經過處理和傳遞到我們自己編寫的Servlet的過程。當然過程中有什么錯誤的地方還望各位大神指出。
本文為GooPolaris原創,轉載須附上原文鏈接:https://i.cnblogs.com/EditPosts.aspx?postid=8115784