предисловие
Предыдущий"Как устроены коннекторы в Tomcat"Представляет структуру коннектора в Tomcat. Мы знаем, что коннектор отвечает за мониторинг сетевых портов, получение запросов на подключение, а затем преобразование запросов, соответствующих стандарту Servlet, и передачу их контейнеру для обработки. Затем наша статья будет следуйте предыдущей статье Идея видеть запрос к контейнеру, как контейнер запрашивает.
Примечание. Версия этой статьи для Tomcat — 9.0.21, и ее не рекомендуется читать читателям с отсчетом от нуля.
Начать с адаптера
Продолжаем следить за предыдущей статьейAdapter
Исходный код, продолжаем анализировать, исходный код в конце предыдущей статьи выглядит следующим образом:
//源码1.类: CoyoteAdapter implements Adapter
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);
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);
}
}
Основная функция приведенного выше исходного кода — получить контейнер, а затем вызватьgetPipeline()
ПолучатьPipeline
, и, наконец, идтиinvoke
звоните, давайте посмотрим на этоPipeline
Что это делает.
//源码2.Pipeline接口
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);
}
//源码3. Valve接口
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
Именно вентиль, собственно, роль в Tomcat аналогична буквальному значению. Каждый контейнер имеет трубу, которая, в свою очередь, имеет несколько клапанов. Докажем это с помощью следующего анализа.
Трубопровод-клапан
Мы видим, что исходный код вышеPipeline
а такжеValve
Интерфейс,Pipeline
В основном установленValve
,а такжеValve
это связанный список, то вы можете сделатьinvoke
вызов метода. Давайте рассмотрим этот исходный код:
//源码4
connector.getService().getContainer().getPipeline().getFirst().invoke(
request, response);
Вот конвейер, чтобы получить контейнер напрямую, а затем получить первыйValve
позвонить. мы упоминали ранееValve
Это связанный список, здесь вызывается только первый, то есть последний можно вызвать через Next. Давайте рассмотрим нашу первую статью »Как Tomcat запускается в SpringBoot》 упомянул, что контейнер разделен на 4 подконтейнера, которыеEngine
,Host
,Context
,Wrapper
, они также являются отношениями между родителем и ребенком,Engine
>Host
>Context
>Wrapper
.
Как я упоминал ранее, каждый контейнер имеет одинPipeline
, Так как это проявляется? Это мы можем узнать, посмотрев на исходный код интерфейса контейнера,Pipeline
является базовым свойством, определяемым интерфейсом контейнера:
//源码5.
public interface Container extends Lifecycle {
//省略其他代码
/**
* Return the Pipeline object that manages the Valves associated with
* this Container.
*
* @return The Pipeline
*/
public Pipeline getPipeline();
}
Мы знаем, что у каждого контейнера есть труба (Pipeline
), Есть много трубопроводных клапанов (Valve
),Valve
Можно сделать цепные вызовы, тогда проблема в конвейере родительского контейнераValve
Как вызвать подконтейнерValve
Шерстяная ткань? существуетPipeline
класс реализацииStandardPipeline
, мы нашли следующий исходный код:
/**
// 源码6.
* The basic Valve (if any) associated with this Pipeline.
*/
protected Valve basic = null;
/**
* The first valve associated with this Pipeline.
*/
protected Valve first = null;
public void addValve(Valve valve) {
//省略部分代码
// Add this Valve to the set associated with this Pipeline
if (first == null) {
first = valve;
valve.setNext(basic);
} else {
Valve current = first;
while (current != null) {
//这里循环设置Valve,保证最后一个是basic
if (current.getNext() == basic) {
current.setNext(valve);
valve.setNext(basic);
break;
}
current = current.getNext();
}
}
container.fireContainerEvent(Container.ADD_VALVE_EVENT, valve);
}
Согласно приведенному выше коду, мы знаемbasic
это трубка(Pipeline
), само собой разумеется, что пока последний клапан является первым клапаном следующего контейнера, весь цепной вызов может быть завершен. Мы используем запрос для отладки, чтобы увидеть, совпадает ли он с нашим предположением, мы находимся вCoyoteAdapter
серединаservice
Ставим точку останова в методе, эффект следующий:
Здесь мы можем знать, что когда адаптер вызывает контейнер, он вызываетсяEngine
В трубопроводе имеется только один вентиль, то есть основной, значение которого равноStandardEngineValve
. Мы обнаружили, что метод вызова этого клапана выглядит следующим образом:
//源码7.
public final void invoke(Request request, Response response)
throws IOException, ServletException {
// Select the Host to be used for this Request
Host host = request.getHost();
if (host == null) {
// HTTP 0.9 or HTTP 1.0 request without a host when no default host
// is defined. This is handled by the CoyoteAdapter.
return;
}
if (request.isAsyncSupported()) {
request.setAsyncSupported(host.getPipeline().isAsyncSupported());
}
// Ask this Host to process this request
host.getPipeline().getFirst().invoke(request, response);
}
Мы продолжаем отлаживать, чтобы увидеть следующие результаты:
Итак, вотbasic
на самом деле позвонитHost
Контейнерные трубы (Pipeline
) и клапан (Valve
), то есть в каждом контейнерном конвейереbasic
клапан, отвечающий за вызов следующего подконтейнера. Я использую изображение для представления:
На этом рисунке четко показано, как контейнер внутри Tomcat передает запросы от коннектора (Connector
) входящие запросы будут поступатьEngine
контейнер,Engine
через трубу(Pieline
) в клапане (Valve
) для цепных вызовов, последнийbasic
Клапан отвечает за вызов первого клапана следующего контейнера и вызывается до тех пор, покаWrapper
,ПотомWrapper
повторно выполнитьServlet
.
Давайте посмотримWrapper
Исходный код, это действительно так, как мы говорим:
//源码8.
public final void invoke(Request request, Response response)
throws IOException, ServletException {
//省略部分源码
Servlet servlet = null;
if (!unavailable) {
servlet = wrapper.allocate();
}
// Create the filter chain for this request
ApplicationFilterChain filterChain =
ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
filterChain.doFilter(request.getRequest(),
response.getResponse());
}
Увидев это, вы можете сказать, что это просто создание фильтра (Filter
) и называть его, а не называтьServlet
, да тут действительно не до звонкаServlet
, но мы знаем, что фильтр (Filter
) вServlet
выполнено ранее, т.filterChain.doFilter
После выполнения он будет выполненServlet
. Давайте посмотримApplicationFilterChain
Исходный код, как мы сказали:
//源码9.
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
//省略部分代码
internalDoFilter(request,response);
}
//源码10.
private void internalDoFilter(ServletRequest request,
ServletResponse response)
throws IOException, ServletException {
//省略部分代码
// Call the next filter if there is one
if (pos < n) {
//省略部分代码
ApplicationFilterConfig filterConfig = filters[pos++];
Filter filter = filterConfig.getFilter();
filter.doFilter(request, response, this);
return;
}
//调用servlet
// We fell off the end of the chain -- call the servlet instance
servlet.service(request, response);
Через исходный код мы обнаружили, что после вызова всех фильтров (Filter
)Позже,servlet
начать звонитьservice
. Давайте посмотримservlet
класс реализации
Здесь мы знакомыHttpServlet
а такжеGenericServlet
даTomcat
класс пакета, который на самом деле имеет толькоHttpServlet
,потому чтоGenericServlet
даHttpServlet
родительский класс. Последний передается фреймворку для обработки, и здесь запрос внутри Tomcat завершается.
Реализация изоляции нескольких приложений в Tomcat
Мы знаем, что Tomcat поддерживает развертывание нескольких приложений, так как же Tomcat поддерживает развертывание нескольких приложений? Как убедиться, что не будет путаницы между несколькими приложениями? Чтобы понять это, нам все же придется вернуться к адаптеру, вернуться кservice
метод
//源码11.类:CoyoteAdapter
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)
throws Exception {
//省略部分代码
// Parse and set Catalina and configuration specific
// request parameters
//处理URL映射
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);
}
}
Мы говорили только об этом в предыдущем исходном кодеconnector.getService().getContainer().getPipeline().getFirst().invoke( request, response)
Этот код, эта часть кода предназначена для вызова контейнера, но перед вызовом контейнера естьpostParseRequest
Этот метод используется для обработки запроса сопоставления, давайте посмотрим на исходный код:
//源码12.类:CoyoteAdapter
protected boolean postParseRequest(org.apache.coyote.Request req, Request request,
org.apache.coyote.Response res, Response response) throws IOException, ServletException {
省略部分代码
boolean mapRequired = true;
while (mapRequired) {
// This will map the the latest version by default
connector.getService().getMapper().map(serverName, decodedURI,
version, request.getMappingData());
//没有找到上下文就报404错误
if (request.getContext() == null) {
// Don't overwrite an existing error
if (!response.isError()) {
response.sendError(404, "Not found");
}
// Allow processing to continue.
// If present, the error reporting valve will provide a response
// body.
return true;
}
}
Вот цикл обработки сопоставления URL, еслиContext
Если он не найден, возвращает ошибку 404, и мы продолжаем смотреть исходный код:
//源码13.类:Mapper
public void map(MessageBytes host, MessageBytes uri, String version,
MappingData mappingData) throws IOException {
if (host.isNull()) {
String defaultHostName = this.defaultHostName;
if (defaultHostName == null) {
return;
}
host.getCharChunk().append(defaultHostName);
}
host.toChars();
uri.toChars();
internalMap(host.getCharChunk(), uri.getCharChunk(), version, mappingData);
}
//源码14.类:Mapper
private final void internalMap(CharChunk host, CharChunk uri,
String version, MappingData mappingData) throws IOException {
//省略部分代码
// Virtual host mapping 处理Host映射
MappedHost[] hosts = this.hosts;
MappedHost mappedHost = exactFindIgnoreCase(hosts, host);
//省略部分代码
if (mappedHost == null) {
mappedHost = defaultHost;
if (mappedHost == null) {
return;
}
}
mappingData.host = mappedHost.object;
// Context mapping 处理上下文映射
ContextList contextList = mappedHost.contextList;
MappedContext[] contexts = contextList.contexts;
//省略部分代码
if (context == null) {
return;
}
mappingData.context = contextVersion.object;
mappingData.contextSlashCount = contextVersion.slashCount;
// Wrapper mapping 处理Servlet映射
if (!contextVersion.isPaused()) {
internalMapWrapper(contextVersion, uri, mappingData);
}
}
Поскольку приведенный выше исходный код относительно велик, я пропустил большую часть кода и оставил код, который может понять основную логику.В общем, обработка URL включает в себя три части, сопоставлениеHost
, отображениеContext
и отображениеServlet
(В целях экономии места заинтересованные студенты должны изучить конкретные детали исходного кода).
Здесь мы можем найти деталь, то есть три логики обработки тесно связаны, толькоHost
Он будет обработан, если он не пуст.Context
,дляServlet
То же самое справедливо. Так вот мы простоHost
Если конфигурация другая, то и все последующие подконтейнеры другие, что дополняет эффект изоляции приложения. Однако для встроенного в SpringBoot метода Tomcat (начиная с пакета jar) нет режима для реализации нескольких приложений, и само приложение является Tomcat.
Для простоты понимания я также нарисовал схему мультиприложения изоляции, здесь мы предполагаем, что есть два доменных имениadmin.luozhou.com
а такжеweb.luozhou.com
Затем я развертываю 2 приложения под каждым доменным именем, а именноUser
,log
,blog
,shop
. Затем, когда я думаю о добавлении пользователей, я прошуadmin.luozhou.com
под доменным именемUser
изContext
последующийadd
Сервлет(Примечание: дизайн примера здесь не соответствует фактическому принципу разработки.Дробность добавления должна выполняться контроллером в фреймворке, а не сервлетом.).
Суммировать
В этой статье мы изучили, как контейнер в Tomcat обрабатывает запросы. Давайте рассмотрим следующее:
- Коннектор бросает запрос адаптеру и вызывает контейнер (
Engine
) - Внутри контейнер через трубу (
Pieline
)-клапан(Valve
) для завершения вызова контейнера родительский контейнер вызывает дочерний контейнер в основном черезbasic
клапан до конца. - последний подконтейнер
wrapper
После завершения вызова будет создан фильтр для выполнения вызова фильтра.После завершения вызова последним шагом внутри Tomcat является вызов сервлета. Также можно понять наше обычно используемоеHttpServlet
, все на основеServlet
Здесь канонический фреймворк входит в процесс фреймворка (включая SpringBoot). - Наконец, мы также проанализировали, как Tomcat обеспечивает изоляцию нескольких приложений.Благодаря анализу изоляции нескольких приложений мы также понимаем, почему Tomcat необходимо проектировать так много подконтейнеров.Несколько подконтейнеров могут обеспечивать разную степень детализации уровней изоляции в зависимости от потребностей. Требования к сцене.
Заявление об авторских правах: Оригинальная статья, пожалуйста, укажите источник для перепечатки.