Tomcat請(qǐng)求處理分析(一) - 從套接字到容器組件

在分析了Tomcat的啟動(dòng)過程和各個(gè)組件后俩块,本文開始分析Tomcat是如何處理請(qǐng)求的间影。讓我們回到Tomcat啟動(dòng)分析(六)文章的末尾沟突,在AbstractEndPoint類的processSocket方法中篮幢,工作線程池執(zhí)行的任務(wù)是一個(gè)SocketProcessorBase慷嗜,本文從SocketProcessorBase開始分析颂斜。

SocketProcessorBase類

SocketProcessorBase是一個(gè)實(shí)現(xiàn)了Runnable接口的抽象類夫壁,run方法調(diào)用了doRun抽象方法:

public abstract class SocketProcessorBase<S> implements Runnable {
    protected SocketWrapperBase<S> socketWrapper;
    protected SocketEvent event;

    public SocketProcessorBase(SocketWrapperBase<S> socketWrapper, SocketEvent event) {
        reset(socketWrapper, event);
    }

    public void reset(SocketWrapperBase<S> socketWrapper, SocketEvent event) {
        Objects.requireNonNull(event);
        this.socketWrapper = socketWrapper;
        this.event = event;
    }

    @Override
    public final void run() {
        synchronized (socketWrapper) {
            // It is possible that processing may be triggered for read and
            // write at the same time. The sync above makes sure that processing
            // does not occur in parallel. The test below ensures that if the
            // first event to be processed results in the socket being closed,
            // the subsequent events are not processed.
            if (socketWrapper.isClosed()) {
                return;
            }
            doRun();
        }
    }

    protected abstract void doRun();
}

NioEndPoint的createSocketProcessor函數(shù)返回的SocketProcessor是NioEndPoint類的內(nèi)部類,繼承了SocketProcessorBase抽象類,實(shí)現(xiàn)了doRun方法。該類的注釋說明SocketProcessor與Worker的作用等價(jià)汗贫。

/**
 * This class is the equivalent of the Worker, but will simply use in an
 * external Executor thread pool.
 */
protected class SocketProcessor extends SocketProcessorBase<NioChannel> {

    public SocketProcessor(SocketWrapperBase<NioChannel> socketWrapper, SocketEvent event) {
        super(socketWrapper, event);
    }

    @Override
    protected void doRun() {
        NioChannel socket = socketWrapper.getSocket();
        SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());
        try {
            int handshake = -1;
            try {
                if (key != null) {
                    if (socket.isHandshakeComplete()) {
                        // No TLS handshaking required. Let the handler
                        // process this socket / event combination.
                        handshake = 0;
                    } else if (event == SocketEvent.STOP || event == SocketEvent.DISCONNECT ||
                            event == SocketEvent.ERROR) {
                        // Unable to complete the TLS handshake. Treat it as
                        // if the handshake failed.
                        handshake = -1;
                    } else {
                        handshake = socket.handshake(key.isReadable(), key.isWritable());
                        // The handshake process reads/writes from/to the
                        // socket. status may therefore be OPEN_WRITE once
                        // the handshake completes. However, the handshake
                        // happens when the socket is opened so the status
                        // must always be OPEN_READ after it completes. It
                        // is OK to always set this as it is only used if
                        // the handshake completes.
                        event = SocketEvent.OPEN_READ;
                    }
                }
            } catch (IOException x) {
                handshake = -1;
                if (log.isDebugEnabled()) log.debug("Error during SSL handshake",x);
            } catch (CancelledKeyException ckx) {
                handshake = -1;
            }
            if (handshake == 0) {
                SocketState state = SocketState.OPEN;
                // Process the request from this socket
                if (event == null) {
                    state = getHandler().process(socketWrapper, SocketEvent.OPEN_READ);
                } else {
                    state = getHandler().process(socketWrapper, event);
                }
                if (state == SocketState.CLOSED) {
                    close(socket, key);
                }
            } else if (handshake == -1 ) {
                close(socket, key);
            } else if (handshake == SelectionKey.OP_READ){
                socketWrapper.registerReadInterest();
            } else if (handshake == SelectionKey.OP_WRITE){
                socketWrapper.registerWriteInterest();
            }
        } catch (CancelledKeyException cx) {
            socket.getPoller().cancelledKey(key);
        } catch (VirtualMachineError vme) {
            ExceptionUtils.handleThrowable(vme);
        } catch (Throwable t) {
            log.error("", t);
            socket.getPoller().cancelledKey(key);
        } finally {
            socketWrapper = null;
            event = null;
            //return to cache
            if (running && !paused) {
                processorCache.push(this);
            }
        }
    }
}
  • handshake相關(guān)的變量與函數(shù)都與SSL的握手有關(guān),暫時(shí)忽略邑茄,NioChannel的handshake函數(shù)直接返回了0;
  • 真正的處理過程在getHandler().process這處調(diào)用俊啼。

getHandler函數(shù)定義在AbstractEndPoint類中肺缕,相關(guān)代碼如下:

/**
 * Handling of accepted sockets.
 */
private Handler<S> handler = null;
public void setHandler(Handler<S> handler ) { this.handler = handler; }
public Handler<S> getHandler() { return handler; }

Handler是何時(shí)被賦值的呢?答案在Http11NioProtocol對(duì)象被創(chuàng)建的過程中授帕,調(diào)用父類構(gòu)造函數(shù)時(shí)會(huì)為端點(diǎn)的Handler賦值:

public AbstractHttp11Protocol(AbstractEndpoint<S> endpoint) {
    super(endpoint);
    setConnectionTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
    ConnectionHandler<S> cHandler = new ConnectionHandler<>(this);
    setHandler(cHandler);
    getEndpoint().setHandler(cHandler);
}

ConnectionHandler類

ConnectionHandler是AbstractProtocol類的靜態(tài)內(nèi)部類同木,上文調(diào)用了該類的process函數(shù),由于代碼較多此處不再展示豪墅。由于能力有限泉手,本文只分析最常見情景對(duì)應(yīng)的代碼:

  • processor = getProtocol().createProcessor();
    createProcessor是定義在AbstractProtocol中的抽象方法,AbstractHttp11Protocol實(shí)現(xiàn)了它:
    @Override
    protected Processor createProcessor() {
        Http11Processor processor = new Http11Processor(getMaxHttpHeaderSize(),
                getAllowHostHeaderMismatch(), getRejectIllegalHeaderName(), getEndpoint(),
                getMaxTrailerSize(), allowedTrailerHeaders, getMaxExtensionSize(),
                getMaxSwallowSize(), httpUpgradeProtocols, getSendReasonPhrase(),
                relaxedPathChars, relaxedQueryChars);
        processor.setAdapter(getAdapter());
        processor.setMaxKeepAliveRequests(getMaxKeepAliveRequests());
        processor.setConnectionUploadTimeout(getConnectionUploadTimeout());
        processor.setDisableUploadTimeout(getDisableUploadTimeout());
        processor.setCompressionMinSize(getCompressionMinSize());
        processor.setCompression(getCompression());
        processor.setNoCompressionUserAgents(getNoCompressionUserAgents());
        processor.setCompressibleMimeTypes(getCompressibleMimeTypes());
        processor.setRestrictedUserAgents(getRestrictedUserAgents());
        processor.setMaxSavePostSize(getMaxSavePostSize());
        processor.setServer(getServer());
        processor.setServerRemoveAppProvidedValues(getServerRemoveAppProvidedValues());
        return processor;
    }
    
  • state = processor.process(wrapper, status);這一行委托給Processor的process函數(shù)繼續(xù)處理請(qǐng)求偶器。

Processor接口

Processor接口用來處理協(xié)議的請(qǐng)求斩萌,類層次結(jié)構(gòu)如下圖所示。


Processor類層次結(jié)構(gòu).png

Http11Processor類的process函數(shù)定義在其父類AbstractProcessorLight中屏轰,代碼如下颊郎,它會(huì)接著調(diào)用service抽象方法處理請(qǐng)求。

@Override
public SocketState process(SocketWrapperBase<?> socketWrapper, SocketEvent status)
        throws IOException {
    SocketState state = SocketState.CLOSED;
    Iterator<DispatchType> dispatches = null;
    do {
        if (dispatches != null) {
            DispatchType nextDispatch = dispatches.next();
            state = dispatch(nextDispatch.getSocketStatus());
        } else if (status == SocketEvent.DISCONNECT) {
            // Do nothing here, just wait for it to get recycled
        } else if (isAsync() || isUpgrade() || state == SocketState.ASYNC_END) {
            state = dispatch(status);
            if (state == SocketState.OPEN) {
                // 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.
                state = service(socketWrapper);
            }
        } else if (status == SocketEvent.OPEN_WRITE) {
            // Extra write event likely after async, ignore
            state = SocketState.LONG;
        } else if (status == SocketEvent.OPEN_READ){
            state = service(socketWrapper);
        } else {
            // Default to closing the socket if the SocketEvent passed in
            // is not consistent with the current state of the Processor
            state = SocketState.CLOSED;
        }
        // 省略一些代碼
    } while (state == SocketState.ASYNC_END ||
            dispatches != null && state != SocketState.CLOSED);
    return state;
}

Http11Processor類實(shí)現(xiàn)了自己的service方法霎苗,由于代碼較多此處不再展示姆吭,重要的處理流程是getAdapter().service(request, response);這一行。
Http11Processor的adapter是在對(duì)象生成后set上去的唁盏,其參數(shù)是調(diào)用AbstractProtocol的getAdapter方法得到的内狸,而AbstractProtocol的adapter是Connector初始化的時(shí)候被賦值的检眯,代碼如下:

@Override
protected void initInternal() throws LifecycleException {
    super.initInternal();
    // Initialize adapter
    adapter = new CoyoteAdapter(this);
    protocolHandler.setAdapter(adapter);
    // 省略一些代碼
}

CoyoteAdapter類

給ProtocolHandler設(shè)置的Adapter是CoyoteAdapter類型,其service方法代碼如下:

@Override
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)
        throws Exception {
    Request request = (Request) req.getNote(ADAPTER_NOTES);
    Response response = (Response) res.getNote(ADAPTER_NOTES);
    if (request == null) {
        // Create objects
        request = connector.createRequest();
        request.setCoyoteRequest(req);
        response = connector.createResponse();
        response.setCoyoteResponse(res);

        // Link objects
        request.setResponse(response);
        response.setRequest(request);

        // Set as notes
        req.setNote(ADAPTER_NOTES, request);
        res.setNote(ADAPTER_NOTES, response);

        // Set query string encoding
        req.getParameters().setQueryStringCharset(connector.getURICharset());
    }

    if (connector.getXpoweredBy()) {
        response.addHeader("X-Powered-By", POWERED_BY);
    }

    boolean async = false;
    boolean postParseSuccess = false;

    req.getRequestProcessor().setWorkerThreadName(THREAD_NAME.get());

    try {
        // Parse and set Catalina and configuration specific
        // request parameters
        postParseSuccess = postParseRequest(req, request, res, response);
        if (postParseSuccess) {
            //check valves if we support async
            request.setAsyncSupported(
                    connector.getService().getContainer().getPipeline().isAsyncSupported());
            // Calling the container
            connector.getService().getContainer().getPipeline().getFirst().invoke(
                    request, response);
        }
        if (request.isAsync()) {
            async = true;
            ReadListener readListener = req.getReadListener();
            if (readListener != null && request.isFinished()) {
                // Possible the all data may have been read during service()
                // method so this needs to be checked here
                ClassLoader oldCL = null;
                try {
                    oldCL = request.getContext().bind(false, null);
                    if (req.sendAllDataReadEvent()) {
                        req.getReadListener().onAllDataRead();
                    }
                } finally {
                    request.getContext().unbind(false, oldCL);
                }
            }

            Throwable throwable =
                    (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);

            // If an async request was started, is not going to end once
            // this container thread finishes and an error occurred, trigger
            // the async error process
            if (!request.isAsyncCompleting() && throwable != null) {
                request.getAsyncContextInternal().setErrorState(throwable, true);
            }
        } else {
            request.finishRequest();
            response.finishResponse();
        }

    } catch (IOException e) {
        // Ignore
    } finally {
        // 省略一些代碼
    }
}

請(qǐng)求預(yù)處理

postParseRequest方法對(duì)請(qǐng)求做預(yù)處理昆淡,如對(duì)路徑去除分號(hào)表示的路徑參數(shù)锰瘸、進(jìn)行URI解碼、規(guī)格化(點(diǎn)號(hào)和兩點(diǎn)號(hào))

protected boolean postParseRequest(org.apache.coyote.Request req, Request request,
        org.apache.coyote.Response res, Response response) throws IOException, ServletException {
    // 省略部分代碼
    MessageBytes decodedURI = req.decodedURI();

    if (undecodedURI.getType() == MessageBytes.T_BYTES) {
        // Copy the raw URI to the decodedURI
        decodedURI.duplicate(undecodedURI);

        // Parse the path parameters. This will:
        //   - strip out the path parameters
        //   - convert the decodedURI to bytes
        parsePathParameters(req, request);

        // URI decoding
        // %xx decoding of the URL
        try {
            req.getURLDecoder().convert(decodedURI, false);
        } catch (IOException ioe) {
            res.setStatus(400);
            res.setMessage("Invalid URI: " + ioe.getMessage());
            connector.getService().getContainer().logAccess(
                    request, response, 0, true);
            return false;
        }
        // Normalization
        if (!normalize(req.decodedURI())) {
            res.setStatus(400);
            res.setMessage("Invalid URI");
            connector.getService().getContainer().logAccess(
                    request, response, 0, true);
            return false;
        }
        // Character decoding
        convertURI(decodedURI, request);
        // Check that the URI is still normalized
        if (!checkNormalize(req.decodedURI())) {
            res.setStatus(400);
            res.setMessage("Invalid URI character encoding");
            connector.getService().getContainer().logAccess(
                    request, response, 0, true);
            return false;
        }
    } else {
        /* The URI is chars or String, and has been sent using an in-memory
            * protocol handler. The following assumptions are made:
            * - req.requestURI() has been set to the 'original' non-decoded,
            *   non-normalized URI
            * - req.decodedURI() has been set to the decoded, normalized form
            *   of req.requestURI()
            */
        decodedURI.toChars();
        // Remove all path parameters; any needed path parameter should be set
        // using the request object rather than passing it in the URL
        CharChunk uriCC = decodedURI.getCharChunk();
        int semicolon = uriCC.indexOf(';');
        if (semicolon > 0) {
            decodedURI.setChars
                (uriCC.getBuffer(), uriCC.getStart(), semicolon);
        }
    }

    // Request mapping.
    MessageBytes serverName;
    if (connector.getUseIPVHosts()) {
        serverName = req.localName();
        if (serverName.isNull()) {
            // well, they did ask for it
            res.action(ActionCode.REQ_LOCAL_NAME_ATTRIBUTE, null);
        }
    } else {
        serverName = req.serverName();
    }

    // Version for the second mapping loop and
    // Context that we expect to get for that version
    String version = null;
    Context versionContext = null;
    boolean mapRequired = true;

    while (mapRequired) {
        // This will map the the latest version by default
        connector.getService().getMapper().map(serverName, decodedURI,
                version, request.getMappingData());
        // 省略部分代碼
    }
    // 省略部分代碼
}

以MessageBytes的類型是T_BYTES為例:

  • parsePathParameters方法去除URI中分號(hào)表示的路徑參數(shù)昂灵;
  • req.getURLDecoder()得到一個(gè)UDecoder實(shí)例避凝,它的convert方法對(duì)URI解碼,這里的解碼只是移除百分號(hào)眨补,計(jì)算百分號(hào)后兩位的十六進(jìn)制數(shù)字值以替代原來的三位百分號(hào)編碼管削;
  • normalize方法規(guī)格化URI,解釋路徑中的“.”和“..”撑螺;
  • convertURI方法利用Connector的uriEncoding屬性將URI的字節(jié)轉(zhuǎn)換為字符表示含思;
  • 注意connector.getService().getMapper().map(serverName, decodedURI, version, request.getMappingData()) 這行,之前Service啟動(dòng)時(shí)MapperListener注冊(cè)了該Service內(nèi)的各Host和Context实蓬。根據(jù)URI選擇Context時(shí)茸俭,Mapper的map方法采用的是convertURI方法解碼后的URI與每個(gè)Context的路徑去比較吊履,可參考Mapper類Context配置文檔安皱。

容器處理

如果請(qǐng)求可以被傳給容器的Pipeline即當(dāng)postParseRequest方法返回true時(shí),則由容器繼續(xù)處理艇炎,在service方法中有connector.getService().getContainer().getPipeline().getFirst().invoke(request, response)這一行:

  • Connector調(diào)用getService返回StandardService酌伊;
  • StandardService調(diào)用getContainer返回StandardEngine;
  • StandardEngine調(diào)用getPipeline返回與其關(guān)聯(lián)的StandardPipeline缀踪;
  • Tomcat啟動(dòng)分析(七) - 容器組件與Engine組件中曾提到StandardEngine的構(gòu)造函數(shù)為自己的Pipeline添加了基本閥StandardEngineValve居砖,StandardPipeline調(diào)用getFirst得到第一個(gè)閥去處理請(qǐng)求,由于基本閥是最后一個(gè)驴娃,所以最后會(huì)由基本閥去處理請(qǐng)求奏候,后續(xù)處理流程請(qǐng)看下一篇文章

參考文獻(xiàn)

Apache Tomcat 8 Request Process Flow

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末唇敞,一起剝皮案震驚了整個(gè)濱河市蔗草,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌疆柔,老刑警劉巖咒精,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異旷档,居然都是意外死亡模叙,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門鞋屈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來范咨,“玉大人故觅,你說我怎么就攤上這事∏。” “怎么了逻卖?”我有些...
    開封第一講書人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長昭抒。 經(jīng)常有香客問我评也,道長,這世上最難降的妖魔是什么灭返? 我笑而不...
    開封第一講書人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任盗迟,我火速辦了婚禮,結(jié)果婚禮上熙含,老公的妹妹穿的比我還像新娘罚缕。我一直安慰自己,他們只是感情好怎静,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開白布邮弹。 她就那樣靜靜地躺著,像睡著了一般蚓聘。 火紅的嫁衣襯著肌膚如雪腌乡。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,772評(píng)論 1 290
  • 那天夜牡,我揣著相機(jī)與錄音与纽,去河邊找鬼。 笑死塘装,一個(gè)胖子當(dāng)著我的面吹牛急迂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蹦肴,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼僚碎,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了阴幌?” 一聲冷哼從身側(cè)響起勺阐,我...
    開封第一講書人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎裂七,沒想到半個(gè)月后皆看,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡背零,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年腰吟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡毛雇,死狀恐怖嫉称,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情灵疮,我是刑警寧澤织阅,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站震捣,受9級(jí)特大地震影響荔棉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蒿赢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一润樱、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧羡棵,春花似錦壹若、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至秃流,卻和暖如春赂蕴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背剔应。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來泰國打工睡腿, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人峻贮。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像应闯,于是被迫代替她去往敵國和親纤控。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容