Tomcat using

In the webapps folder at the root of the Tomcat project, create a folder called HelloServlet, then create the Web-INF folder, classes, and class package folder, and put a compiled Servlet class file in it. Add a web. XML file to web-INF as follows:

<web-app>
    <display-name>my web app</display-name>
    <servlet>
        <servlet-name>servletDemo</servlet-name>
        <servlet-class>com.darkness.servlet.ServletDemo</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>servletDemo</servlet-name>
        <url-pattern>/servletDemo</url-pattern>
    </servlet-mapping>
</web-app>
Copy the code

Or in server.xml, add a Context tag to the tag

<Context path="/MyContextServlet" relaodable="false"
         docBase="D:\project\code\mycontext\HelloServlet"/>
Copy the code

Where docBase is the disk address of the project, the composition structure is the same as the HelloServlet project structure, which is the descriptor deployment. After the above operations, find the BootStrap class of Tomcat, call the main method to start Tomcat, access port 8080 of the host in the browser according to the URL-pattern of the servlet, and get the following response:

For example, tomcat deployable war package, in fact, in the webapps to see the war package, decompress into a folder, and then go to the inside of the web.xml, Servlet. Therefore, the first deployment mode above, just according to the Requirements of Tomcat resolution, to create folders, write the required files on the line. Can Tomcat deploy jar packages? Actually, no. The actual entry point to a Tomcat deployment project is the deployApps method in HostConfig. This approach provides three ways to deploy projects: 1, XML descriptor deployment; 2. Deploy the WAR package. 3. Folder deployment

The name of the class: org. Apache. Catalina. Startup. HostConfig/** * Deploy applications for any directories or WAR files that are found * in our "application root" directory. * Three ways to deploy an application * 1. War package deployment * 3. Folder deployment * In addition, Tomcat uses asynchronous multithreading to deploy applications */
protected void deployApps(a) {
    File appBase = appBase();
    File configBase = configBase();
    String[] filteredAppPaths = filterAppPaths(appBase.list());
    // Deploy XML descriptors from configBase
    // XML descriptor deployment
    deployDescriptors(configBase, configBase.list());
    // War package deployment
    deployWARs(appBase, filteredAppPaths);
    // Folder deployment
    deployDirectories(appBase, filteredAppPaths);
}
Copy the code

XML descriptor deployment is used to define the context tag in server.xml to specify project address war package deployment we can look at the deployWARs method

The name of the class: org. Apache. Catalina. Startup. HostConfig/** * Deploy WAR files. */
protected void deployWARs(File appBase, String[] files) {
    / /... Leaving out a lot of code
    for (int i = 0; i < files.length; i++) {
        if (files[i].equalsIgnoreCase("META-INF"))
            continue;
        if (files[i].equalsIgnoreCase("WEB-INF"))
            continue;
        File war = new File(appBase, files[i]);
        if (files[i].toLowerCase(Locale.ENGLISH).endsWith(".war") && war.isFile() && ! invalidWars.contains(files[i]) ) {/ /... Leaving out a lot of code
            }
            / /... Leaving out a lot of code}}/ /... Leaving out a lot of code
}
Copy the code

As you can see, deployWARs does not deploy files ending in.war, so jars cannot be deployed by Tomcat and folder deployment is the direct folder creation method described above.

So tomcat deployment project, in fact, just need to know the path of the project, Tomcat according to the project path, find the project, read the web. XML, read the class file, load the servlet into the context, can achieve access to the servlet.

Tomcat configuration file

The tomcat core configuration file is server.xml, which defines the port number, various components, etc. Tomcat is started by reading the configuration in server.xml to start the components and deploy the project.


      
<Server port="8005" shutdown="SHUTDOWN">
    <Listener className="org.apache.catalina.startup.VersionLoggerListener"/>
    <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on"/>
    <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener"/>
    <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener"/>
    <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener"/>
    <GlobalNamingResources>
        <Resource name="UserDatabase" auth="Container"
                  type="org.apache.catalina.UserDatabase"
                  description="User database that can be updated and saved"
                  factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
                  pathname="conf/tomcat-users.xml"/>
    </GlobalNamingResources>
    <Service name="Catalina">
        <Connector port="8080" protocol="HTTP / 1.1"
                   connectionTimeout="20000"
                   redirectPort="8443"/>
        <Connector port="8009" protocol="AJP / 1.3" redirectPort="8443"/>
        <Engine name="Catalina" defaultHost="localhost">
            <Realm className="org.apache.catalina.realm.LockOutRealm">
                <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
                       resourceName="UserDatabase"/>
            </Realm>
            <Host name="localhost" appBase="webapps"
                  unpackWARs="true" autoDeploy="true">
                <Context path="/MyServlet" relaodable="false"
                         docBase="F:/code/tomcat/webapps/HelloServlet"/>
                <Context path="/ServletDemoHello##1" docBase="F:/code/servlet/ServletDemo/target/classes" />
                <Context path="/ServletDemoHello##2" docBase="F:/code/servlet/ServletDemo/target/classes" />
                <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>
        </Engine>
    </Service>
</Server>
Copy the code

The configuration file of server. XML has a hierarchical structure. The top node is a server, which can have multiple listeners and services. Engine can have multiple hosts, host can have multiple contexts, and valve can have multiple valves.

In Tomcat, we know that a project/application is actually a node, so let’s go to the Tomcat code and see if there is a Context class.

The name of the class: org. Apache. Catalina. The Contextpublic interface Context extends Container
Copy the code

It turns out that there is such a thing as Context, but it’s an interface, and that interface inherits an interface called Container. Tomcat is commonly referred to as a servlet Container because it inherits the Container interface.

The name of the class: org. Apache. Catalina. Containerpublic interface Container extends Lifecycle
Copy the code

Instead of looking at the methods of the Container interface, let’s look at who inherits/implements it

Unfortunately, Context, Engine, Host, we’ve all seen them in server.xml, they’re all tags in server.xml. Wrapper we haven’t seen yet, but it’s not a problem, as those three are proof that the configuration in server.xml is basically parsed into the corresponding Container implementation class.

Let’s take a look at the role of each configuration node in server.xml

The Host node

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

The first property, which is a virtual Host, can have multiple hosts in one Engin, and name is the unique identifier for them, and when we see appBase=”webapps”, basically we can guess that appBase is basically telling Tomcat that in this Host, Where to find apps. Now I add a Host to Engine

<Host name="darkness.test.servlet" appBase="webapps_virtual_host"
      unpackWARs="true" autoDeploy="true">
    <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>
Copy the code

I changed name to darkness. Test. servlet and appBase to webapps_virtual_host. Of course I needed to add DNS resolution mapping for Darkness. Then create a webApps_virtual_host folder in the Tomcat root directory, as shown in the figure

My project name is also called HelloServlet, and the URl-pattern is also servletDemo, which is exactly the same as the HelloServlet in the Webapps folder, except that the return is different. Start Tomcat, visit it in your browser, and see what happens

Access the localhost effect

Access the Darkness. Test. servlet effect

As expected, localhost and Darkness. Test. servlet both map 127.0.0.1, the project name is also HelloServlet, and the URL-pattern is servletDemo. However, Tomcat differentiates the domain name I entered according to the virtual host, achieving the effect of isolating the two projects. So the virtual host is the isolation of the project, such as you want to put a number of projects to do a classification, but want to use a Tomcat to deploy, you can put the same project in the same virtual host, to achieve the purpose of isolation. Need to mention a mouth, isolation is not just a distinction, such as your different projects, need valve is not the same, at this time the role of the virtual Host is reflected, the same Host valve is of course the same, but if you make multiple hosts, the valve is not the same, of course. As for what Valve is, we’ll get to that later.

Engin node

Engine is the parent of a Host, and it can have multiple hosts below it. As we have already verified, Engine has a property called defaultHost that specifies which Host is the default virtual Host. What is the use of the default virtual host?

127.0.0.1 is the IP address where TOMCAT is started. It is exposed port 8080. I can actually send data to this socket, in which case Engine selects defaultHost=localhost. You can see the effect

You can see that accessing 127.0.0.1 returns the servlet in Webapps.

I’m going to change the Engine TAB to defaultHost:

<Engine name="Catalina" defaultHost="darkness.test.servlet">
Copy the code

Start Tomcat and visit 127.0.0.1 to see what happens:

Access 127.0.0.1 and get a response in webApps_virtual_host. DefaultHost is in effect.

So Engin doesn’t really do anything special, it just manages all the hosts and then specifies a default Host.

Context node and Wrapper interface

From the XML descriptor deployment project example, we can see that one Context corresponds to one application. So the inside of the Context must be a servlet. But tomcat doesn’t actually do that. The Wrapper Context is a list of wrappers, and a Wrapper pair is a list of servlets within the Wrapper.

Pipeline and Valve

Context, Engine, Host, and Wrapper are all Servlet containers, and each Servlet container has its own set of processing logic. These processing logic is packaged in the Pipeline, and these processing logic is Valve, Valve. The so-called valve can be popularly understood as a filter. Filters should be known, but valves and filters still have a certain difference. The filters we usually refer to are the general processing of requests at the business level. Valve is not yet at the business level. Valve is the general processing of requests by Tomcat before it actually processes servlets. For example, in this article’s server.xml configuration, a Valve is configured in Engine for logging.

So in Engine, Host, Context, and Wrapper, there is a Pipeline that encapsulates a series of valves. A request will go to Engine, find the corresponding virtual Host, execute Valve logic, enter Host layer, find the corresponding project, execute Valve logic in Host layer, enter Context layer, find the corresponding Wrapper. Then follow Valve’s logic. And so on, and you end up in real servlets to execute your business logic.

Customize a valve

Find the Valve interface and see its inheritance

Seeing that there is a ValveBase abstract class that implements Valve, we can write a class of our own that extends from ValveBase and overrides the abstract method Invoke to see if this works

The name of the class: com. Darkness. TestValvepublic class TestValve extends ValveBase {

    private String name;

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public void invoke(Request request, Response response) throws IOException, ServletException {
        System.out.println("I'm a customize Valve, my name is "+ name); getNext().invoke(request, response); }}Copy the code

In server.xml, I added a Valve to the Context

<Context path="/MyContextServlet" relaodable="false"
                         docBase="G:\project\code\mycontext\HelloServlet">
    <Valve className="com.darkness.TestValve" name = "The Outlaw John."/>
</Context>
Copy the code

Based on this, start Tomcat, access HelloServlet, and you don’t see Valve’s effect, but access MyContextServlet, and you do

Note that Valve’s invoke method must end with getNext().invoke(request, Response), otherwise the Valve chain will break.

Default valve in container

We get a sense of what valves do by writing a custom valve example ourselves. So in Engine, Host, Context, Wrapper, there will be a Pipeline that encapsulates a series of valves. A request will go to Engine, find the corresponding virtual Host, execute Valve logic, enter Host layer, find the corresponding project, execute Valve logic in Host layer, enter Context layer, find the corresponding Wrapper. Then follow Valve’s logic. So on, and ultimately to the real Servlet execute business logic “this passage. Looking at the role of the valve, it should be possible to guess that the transition from the upper container to the lower container is achieved by using the valve.

Each container comes with its own valve, called Standard XX Valve

No matter how many valves we customize in each container, they are added before standard valves. Let’s see how each standard valve invoke method invokes the valve of the next container

The name of the class: org. Apache. Catalina. Core. StandardEngineValve Host Host = request. GetHost ();/ /... Omit N more code
// Ask this Host to process this request
host.getPipeline().getFirst().invoke(request, response);
Copy the code
Class name: org. Apache. Catalina. Core. StandardHostValve Context Context = request. GetContext ()/ /... Omit N more code
context.getPipeline().getFirst().invoke(request, response);
Copy the code
The name of the class: org. Apache. Catalina. Core. StandardContextValve Wrapper Wrapper = request. GetWrapper ();/ /... Omit N more code
wrapper.getPipeline().getFirst().invoke(request, response);
Copy the code

As you can see, in the request object, the Host, Context, and Wrapper that the request corresponds to are already identified. Tomcat already defaults the last valve of each container to be its standard valve, so in its standard valve, GetFirst, the first valve of the next container, acts as a bridge between the upper container and the lower container.

How does Tomcat call the service method of a Servlet

The Valve call described above is actually a recursion-like call, the invoke of the next Valve is removed from the stack after the invoke of the next Valve is removed from the stack, so the Valve chain must always have a tail node, which is StandardWrapperValve. Let’s take a look at what the invoke method in this class does (the method is too long, and here we have removed a billion lines of code, leaving only the core code)

The name of the class: org. Apache. Catalina. Core.StandardWrapperValve
public final void invoke(Request request, Response response)
    throws IOException, ServletException {
    // Assign a servlet instance for this request
    try {
        if (!unavailable) {
            servlet = wrapper.allocate();
        }
    } catch (Throwable e) {
       
    }
    // Create a filter chain for this request
    ApplicationFilterFactory factory =
        ApplicationFilterFactory.getInstance();
    ApplicationFilterChain filterChain =
        factory.createFilterChain(request, wrapper, servlet);
    // Invoke the filter chain for this request
    // The servlet's service() method will also be called here
    try {
        if((servlet ! =null) && (filterChain ! =null)) {
            // Swallow output if needed
            if (context.getSwallowOutput()) {
                try {
                    SystemLogHandler.startCapture();
                    if (request.isAsyncDispatching()) {
                        request.getAsyncContextInternal().doInternalDispatch();
                    } else if (comet) {
                        filterChain.doFilterEvent(request.getEvent());
                        request.setComet(true);
                    } else{ filterChain.doFilter(request.getRequest(), response.getResponse()); }}finally {
                    String log = SystemLogHandler.stopCapture();
                    if(log ! =null && log.length() > 0) { context.getLogger().info(log); }}}else {
                if (request.isAsyncDispatching()) {
                    request.getAsyncContextInternal().doInternalDispatch();
                } else if (comet) {
                    request.setComet(true);
                    filterChain.doFilterEvent(request.getEvent());
                } else{ filterChain.doFilter (request.getRequest(), response.getResponse()); }}}}catch (Throwable e) {
        
    }
}
Copy the code

As you can see, in the final valve, StandardWrapperValve, the servlet instance is created/assigned (the servlet may be singleton or multi-instance), the filter chain of the servlet is created, Call filterchain. doFilter(request, response) to enter the filterChain. After calling internalDoFilter in ApplicationFilterChain, Execute the servlet’s service method.

The Request object

As mentioned above, in the process of calling the Valve chain, the Valve directly calls request.gethost (), request.getContext() and other methods from Valve to directly obtain the containers of each layer of this request. How is request encapsulated? Is the Request object really the Same HttpServletRequest object passed to the Servlet’s service method? This depends on how the request is read and wrapped from the input stream in the socket and how Valve-> filter chain calls the request.

When tomcat is started, it starts a thread that calls the Acceptor method of the socket to accept messages. This article uses BIO as an example (which is simpler, after all). Acceptor classes are protected non-static inner classes that reside in JioEndpoint. In the run method of this class

The name of the class: org.apache.tomcat.util.net.JIoEndpoint.Acceptor socket = serverSocketFactory. AcceptSocket (serverSocket);Copy the code
The name of the class: org.apache.tomcat.util.net.JIoEndpoint.Acceptor
if (! processSocket(socket))
Copy the code

In the processSocket method:

The name of the class: org.apache.tomcat.util.net.JIoEndpoint.Acceptor
getExecutor(a).execute(new SocketProcessor(wrapper));
Copy the code

So, SocketProcessor is a class that implements the Runnable interface. Take a look at the core code of SocketProcessor’s run method

The name of the class: org.apache.tomcat.util.net.JIoEndpoint.SocketProcessor state = handler. The process (socket, SocketStatus. OPEN_READ);Copy the code

In this code, the socket is passed down to the handler’s process method. Because is too complex, this article also not much, the handler in the BIO is org. Apache. Coyote. Http11. Http11Protocol. First calls the superclass abstract class org. Apache. Coyote. AbstractProtocol method, finally through the template method pattern calls to a specific Http protocol implementation class of process method to handle the request.

The specific Http protocol implementation class is only different in the way of parsing the input stream transmitted by the operating system (NIO, BIO). After all, the protocol itself is fixed, but Tomcat is only an application, and its data can only be sent in through the operating system. Therefore, different IO methods must correspond to different implementation classes. The following figure is a partial screenshot of the process method that abstracts the HTTP protocol parent class AbstractHttp11Processor. ParseRequestLine has several implementation methods for the input stream. There are different methods for parsing the input stream. However, all of them follow the Http protocol. So these public methods are written in the abstract parent class, which implements different parsing methods in the subclass.

The Request object is wrapped in a protocol handler class.

The name of the class: org. Apache. Coyote. AbstractProcessorprotected Request request; // public final class org.apache.coyote.Request
Copy the code

It is important to note that the Request object does not implement the HttpServletRequest interface, which means it does not implement the Servlet specification. The Request object is simply an internal Tomcat object that holds encapsulated information about the data read by the socket.

Therefore, in the parseRequestLine method, the request line part of the socket input stream is parsed into the request line attribute of the request object through the SPECIFICATION of Http protocol. After parsing the request line, there is also a parseRequestHeader method that works similarly. Encapsulate the read data into the Request object according to the Http protocol format. This is then passed to downstream components.

The name of the class: org. Apache. Coyote. Http11. AbstractHttp11Processor adapter. Service (request, response);Copy the code

It is in the process method of the abstract superclass that this step is taken to hand over the request to the container.

The name of the class: org. Apache. Catalina. The CoyoteAdapter Request Request = (Request) the req. GetNote (ADAPTER_NOTES);// Request-->Request
Copy the code

Here, a Request object to Request object conversion is implemented. What does that mean? External service Adapter object for the incoming parameters in the request is actually org. Apache. Coyote. The request object, and in the service methods of Adapter, Convert this Request object to a business-level Request object that implements the HttpServletRequest interface, that is, it implements the Servlet specification:

Package name: org. Apache. Catalina. Connectorpublic class Request implements HttpServletRequest
Copy the code

After the adapter’s Service method takes the internal Request object and transforms it into a Request object that implements the servlet specification, it then takes the pipe of the top-level container and the first Valve in the pipe of the top-level container to start the Valve chain execution. Pass the Request and Response objects that implement the Servlet specification down the hierarchy.

The name of the class: org. Apache. Catalina. The CoyoteAdapter method name:public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)
connector.getService(a).getContainer(a).getPipeline(a).getFirst(a).invoke( request, response);
Copy the code

In fact, the request that implements the HttpServletRequest interface is not the request object that ends up being passed to our own Servlet. < span style = “margin: 0pt 0pt 0pt 0pt; padding: 0px;

The name of the class: org. Apache. Catalina. Core. StandardWrapperValve method name:public final void invoke(Request request, Response response)
filterChain.doFilter
    (request.getRequest(), response.getResponse(a));
Copy the code

It uses the request.getrequest () method to get a RequestFacade object, which should be clear from the name that this is a request facade. After all, even though the previous Request object implements the Servlet specification, it is still much closer to the socket, it has a lot of protected methods and so on. In short, it is not secure! Tomcat definitely wants to give the outside world an object that can only act as it wants, so Tomcat provides a RequestFacade class:

Package name: org. Apache. Catalina. Connectorpublic class RequestFacade implements HttpServletRequest
Copy the code

This facade class also implements the HttpServletRequest interface, which encapsulates the actual Request object. Operations on the facade object eventually lead to operations on the internal Request object. This facade is the request object that we actually pass to our own Servlet object. For the Response object, Tomcat also performs a similar operation. Print the proof and see if that’s true.

It is true that what is passed to the Servlet instance is a facade.

conclusion

This article has only covered the basic and simple application of Tomcat and the general flow of how a Request is changed from a socket to a Request object and eventually to a servlet. The details are too complicated, such as how to parse socket streams, how to load classes, the difference between NIO and BIO. It’s not covered in this article. So it’s an introductory study note.

An incoming request flow:

  1. The operating system takes the data from other applications and feeds it into the socket’s input stream
  2. After the socket receives the data, the socket.accept method in an Acceptor receives the information
  3. The data is parsed from the socket input stream, encapsulated in the Request object, and the first valve of the first-layer container (Engine) is called
  4. The valves are called one layer at a time, and the filter chain is called after the last valve (StandardWrapperValve) in the last Wrapper layer
  5. After the filter chain is called, the service method of the parent HttpServlet class is called, and then the service method of the subclass (which we overwrote) is called.