Tomcat is introduced

Introduction to the

Tomcat is an open source Java Web application server that implements some technical specifications of Java EE(Java Platform Enterprise Edition). Examples include Java Servlets, Java Server Page, JSTL, and Java WebSocket. Java EE is a standard platform launched by Sun Company for enterprise-level applications. It defines a series of technical specifications for enterprise-level development. In addition to the above, there are EJB, Java Mail, JPA, JTA, JMS, etc., which all depend on the implementation of specific containers.

Some other Web server

The directory structure

These are some of the key Tomcat directories:

  • / bin-Startup, shutdown, and other scripts. Windows for*.batFile for Linux*.shFile.
  • / conf- Configuration files and associated DTDs. The most important file here is server.xml. It is the main configuration file for the container.
  • The/logs- log file is located here by default.
  • / webapps– This is where your WebApp is located.

The working process

When a client requests a resource, the Servlet container uses the ServletRequest object to encapsulate the client’s request information, and then calls some lifecycle methods of the Servlet defined in the Java Servlet API to complete the execution of the Servlet. Then, the result that the Servlet executes to return to the customer is encapsulated in the ServletResponse object. Finally, the Servlet container sends the customer’s request to the customer, completing a service process for the customer.

Organizational structure

Tomcat is a component-based server. Its components are configurable. The outermost layer is the Catalina Servlet container, and other components are configured in this top-level container according to certain format requirements.

Tomcat components are configured in the /conf/server. XML file in the Tomcat installation directory.

<Server> // Top class element, which can contain multiple Service <Service> // top class element, which can contain one Engine, multiple Connecter <Connector> // Connector class element, which represents communication interface <Engine> // container class element, Context <Context> // container elements for a specific virtual Host component. Context <Context> // container elements for a specific virtual Host component. Process all customer requests for a specific Web application </Context> </Host> </Engine> </Connector> </Service> </Server>Copy the code

So, Tomcat’s architecture is as follows:

As you can see from the figure above, Tomca’s heart is made up of two components: Connecter and Container. A Container can select multiple Connecters. Multiple Connectors and a Container form a Service. Service provides external services, while Server controls the Tomcat life cycle.

The container

The Servlet container handles the client’s request and populates the Response object. The Servlet Container implements the Container interface. There are four levels of containers in Tomcat: Engine, Host, Context, and Wrapper.

Engine: indicates the entire Catalina Servlet Engine.

Host: a virtual Host containing one or more Context containers;

Context: represents a Web application that can contain multiple wrappers;

Wrapper: represents a separate Servlet;

The standard implementations of the four tiers of interfaces are the StandardEngine class, StandardHost class, StandardContext class and StandardWrapper class. They are in the org.apache.catalina.core package.

Engine

Engine

Engine is the container that represents the entire Catalina Servlet Engine. It is useful in the following types of scenarios:

1) You want to use interceptors to see every processing request across the engine.

2) You want to run Catalina with a separate HTTP connector, but you still want to support multiple virtual hosts.

Typically, when deploying catalina connected to a Web server such as Apache, you won’t use an engine, because the connector will leverage the web server’s capabilities to determine which context (and possibly even which Wrapper) should be used to handle the request.

The subcontainer attached to Engine is usually an implementation of Host (representing a virtual Host) or context (representing a single servlet context), depending on the Engine implementation.

If used, Engine is always the top-level container in the Catalina hierarchy. Therefore, the implemented setParent () method should throw an IllegalArgumentException.

Host

Host is a container that represents a virtual Host in the Catalina Servlet engine. It is useful in the following types of scenarios:

1) You want to use Interceptors to see every request handled by this particular virtual Host.

2) You want to run Catalina with a separate HTTP connector, but you still want to support multiple virtual hosts.

Typically, you would not use Host when deploying catalina connected to a Web server, such as Apache, because the connector would leverage the Capabilities of the Web server to determine which context (and possibly even which Wrapper) should be used to handle the request.

The parent container for Host is usually an Engine, but it may be some other implementation, or it can be omitted if not needed.

The child container of the host is usually Context (representing a single servlet Context).

Context

Context is a container that represents a servlet Context in the Catalina Servlet engine and is therefore a separate Web application.

As a result, it is useful in almost any deployment of Catalina (even if a connector to a Web server, such as Apache, uses the Web server’s tools to identify the appropriate Wrapper to handle the request).

It also provides a convenient mechanism to use interceptors that can see each request processed by this particular Web application.

The parent container of a context is usually Host, but it may be some other implementation, or it can be omitted if not required.

The child container of the context is usually an implementation of a Wrapper (representing a single servlet definition).

Wrapper

Wrapper is a container that represents a separate servlet definition from the Deployment descriptor of the Web application. It provides a convenient mechanism to use interceptors that can see each request to the servlet represented by this definition.

The Wrapper implementation is responsible for managing the servlet life cycle of its underlying servlet class, including calling init () and destroy () when appropriate, and considering whether the servlet class itself has a single-threaded model declaration.

The parent container of the Wrapper is usually an implementation of the context, representing the servlet context (and therefore the Web application) in which the servlet executes.

Child containers are not allowed on the Wrapper implementation, so the addChild () method should throw an IllegalArgumentException.

Pipeline

Each pipe has a Valve on it, and the relationship between Pipeline and Valve is the same. A Valve represents a Valve in a pipe that controls the flow of the pipe. Of course, there can be more than one Valve in each pipe. If a Pipeline is like a road, Valve can be thought of as a toll station on the road. The cars represent the contents of the Pipeline, and each toll station does something with the contents (toll collection, verification, etc.).

The Pipeline describes the interfaces to the collection of valves that should be executed in sequence when the invoke () method is called. Require that one of the valves in the pipe (usually the last one) must process the request and create a response instead of trying to pass the request.

There is usually a separate instance of a pipe associated with each container. The normal request processing functions of the container are usually encapsulated in a container-specific valve that should always be performed at the end of the pipe. To achieve this, the setBasic () method is officially provided to set valve instances that are always executed last. Before executing the base valve, the other valves are executed in the order of addition.

The function of the base valve is to connect the next container of the current container (usually its own self-container), so to speak, the base valve is the bridge between the two containers. The operation diagram is as follows:

Pipeline
Valve
Valve
Pipeline
Valve
Request
Response
Pipeline
Valve

Source analysis

1) Valve

package org.apache.catalina;

import java.io.IOException;

import javax.servlet.ServletException;

import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;

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();
}
Copy the code

There are not many methods. First, there are many valves on the Pipeline, and these valves are stored more like a linked list. When one valve instance is obtained, the next one can be obtained by getNext(). SetNext sets the next valve instance of the current valve.

2) Pipeline

package org.apache.catalina;

import java.util.Set;

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);
}
Copy the code

It can be seen that many methods in Pipeline operate Valve, including obtaining, setting, To remove the Valve,getFirst() returns the first Valve on the Pipeline, while getBasic() and setBasic() get/set the base Valve. GetBasic () and setBasic() operate the base valve.

Next, let’s look at several important ways to implement the StandardPipeline class.

1: startInternal
protected synchronized void startInternal() throws LifecycleException {

    // Start the Valves in our pipeline (including the basic), if any
    Valve current = first;
    if (current == null) {
        current = basic;
    }
    while(current ! = null) {if (current instanceof Lifecycle)
            ((Lifecycle) current).start();
        current = current.getNext();
    }
 
    setState(LifecycleState.STARTING);
}
Copy the code

The component’s start() method assigns first(the first valve) to current, and basic(the base valve) to current if current is empty, then iterates through the one-way linked list, calling the start() method for each object, Finally, set the component state to STARTING.

2: setBasic
public void setBasic(Valve Valve) {// Valve oldBasic = this. Basic;if (oldBasic == valve)
        return; // Stop the old base valve if the conditions are metif(oldBasic ! = null) {if (getState().isAvailable() && (oldBasic instanceof Lifecycle)) {
            try {
                ((Lifecycle) oldBasic).stop();
            } catch (LifecycleException e) {
                log.error(sm.getString("standardPipeline.basic.stop"), e); }}if(oldBasic instanceof Contained) { try { ((Contained) oldBasic).setContainer(null); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); }}} // If the conditions are correct, start valveif (valve == null)
        return;
    if (valve instanceof Contained) {
        ((Contained) valve).setContainer(this.container);
    }
    if (getState().isAvailable() && valve instanceof Lifecycle) {
        try {
            ((Lifecycle) valve).start();
        } catch (LifecycleException e) {
            log.error(sm.getString("standardPipeline.basic.start"), e);
            return; } // update pipeline Valve current = first;while(current ! = null) {if (current.getNext() == oldBasic) {
            current.setNext(valve);
            break;
        }
        current = current.getNext();
    }

    this.basic = valve;

}
Copy the code

This is the method used to set up the base valve. This method is called in the constructor of each container, and the code logic is relatively simple, with a slight attention to the traversal of the valve list.

3: addValve
Public void addValve(Valve Valve) {// Verify that Valve is associated with a Containerif(valve instanceof Contained) ((Contained) valve).setContainer(this.container); // If so, start Valveif (getState().isAvailable()) {
        if (valve instanceof Lifecycle) {
            try {
                ((Lifecycle) valve).start();
            } catch (LifecycleException e) {
                log.error(sm.getString("standardPipeline.valve.start"), e); }} // If first is empty, assign valve to FIRST and set the next valve as the base valve (because empty means there is only one base valve)if (first == null) {
        first = valve;
        valve.setNext(basic);
    } else{// Go through the valve list and set the valve before the base valve;while(current ! = null) {if (current.getNext() == basic) {
                current.setNext(valve);
                valve.setNext(basic);
                break; } current = current.getNext(); } / / add event triggered container. FireContainerEvent (container ADD_VALVE_EVENT, steam); }Copy the code

This method is like adding Valve to the container, which is also called when server.xml is parsed.

4: getValves
public Valve[] getValves() {

    List<Valve> valveList = new ArrayList<>();
    Valve current = first;
    if (current == null) {
        current = basic;
    }
    while(current ! = null) { valveList.add(current); current = current.getNext(); }return valveList.toArray(new Valve[0]);

}
Copy the code

To get all the valves, you add a list of valves to a collection and return it as an array.

5: removeValve
public void removeValve(Valve valve) { Valve current; // If first is the node to be deleted, then first points to the next node and current is nullif(first == valve) {
        first = first.getNext();
        current = null;
    } else{// add current to first; }while(current ! = null) {// The first digit of the valve to be deleted points to the last digit of the valve.if (current.getNext() == valve) {
            current.setNext(valve.getNext());
            break; } current = current.getNext(); } // If first==basic, first is null. First is strictly defined as the first valve in addition to the base valve.if (first == basic) first = null;

    if(valve instanceof Contained) ((Contained) valve).setContainer(null); // Disable and destroy valveif (valve instanceof Lifecycle) {
        // Stop this valve if necessary
        if (getState().isAvailable()) {
            try {
                ((Lifecycle) valve).stop();
            } catch (LifecycleException e) {
                log.error(sm.getString("standardPipeline.valve.stop"), e);
            }
        }
        try {
            ((Lifecycle) valve).destroy();
        } catch (LifecycleException e) {
            log.error(sm.getString("standardPipeline.valve.destroy"), e); }} / / trigger a container to remove the container valve events. FireContainerEvent (container. REMOVE_VALVE_EVENT, steam); }Copy the code

This deletes the specified Valve and is called in destroyInternal.

6: getFirst
public Valve getFirst() {
    if(first ! = null) {return first;
    }

    return basic;
}
Copy the code

We have also seen in the method 5, first points to the first container of valve valve base, from the method can also be seen in 6, the first in only a basic valve does not point to the basic valve, because there is no need to judge if point to base valve is not empty and then return to base valve, it is a need to pay attention to the point!

AccessLog

Used by Valve to instruct Valve to provide access logging. It is used internally by Tomcat to identify the valve that logs access requests so that requests that are rejected early in the processing chain can still be added to the access log.

The implementation of this interface should be robust in case the provided Request and Response objects are empty, have empty attributes, or any other “exceptions” that might result from trying to log a request that is almost certain to be rejected because the request is incorrectly formatted.

The AccessLog configuration can be found in server.xml’s Host:

<Host name="localhost"  appBase="webapps"
      unpackWARs="true" autoDeploy="true"> <! -- SingleSignOn valve, share authentication between web applications Documentation at: /docs/config/valve.html --> <! -- <Valve className="org.apache.catalina.authenticator.SingleSignOn"/ > -- > <! -- Accesslog processes all example.
       Documentation at: /docs/config/valve.html
       Note: The pattern used is equivalent to using pattern="common" -->
  <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
         prefix="localhost_access_log" suffix=".txt"
         pattern="%h %l %u %t " %r" %s %b" />

</Host>
Copy the code

The Access Log Valve is used to create Log files that can be associated with any Catalina container to record all requests processed by that container. The output file will be placed in the directory specified by the directory property. A file name consists of a series of configured prefixes, time stamps, and suffixes. The format of the timestamp in the file name can be set using the FileDateFormat property. If file rotation is turned off by setting rotatable to false, this timestamp is omitted. You can modify the pattern item to change the log output content.

Parameter/Option description:

parameter meaning
className Implementation of the Java class name must be set to org. Apache. Catalina. Valves. AccessLogValve
directory The directory where the log files are stored. If a relative path is specified, it is interpreted as relative toCatalina_base).
pattern Format layout of log information. If the value is common or Combined, the format is a standard or a custom format, which is described in the following sections
prefix The prefix of the log file name. If not specified, the default value is access_log. (Note the little dot on the back)
resolveHosts Convert the IP address of the remote host to the host name through DNS query and set this parameter to true. If false, the DNS query is ignored and the IP address of the remote host is reported
sufix Log file name extension. (sufix = “log”); There’s also a little point
rotatable The default value is true, which determines whether the log should be flipped or never flipped if it is false, and fileDateFormat is ignored and used with caution.
condition Enable conditional logging
fileDateFormat Allows custom date formats in log file names. The format of the log also determines how often the log file is flipped.

The Pattern:

%a remote IP %a local IP %b Number of bytes sent, excluding HTTP header. If 0, use “-” %b number of bytes sent, excluding HTTP header %h remote host name (if resolveHosts=false), Remote IP %H Request protocol % L Remote logical user name returned from identd, Method that always returns’ – ‘%m request %p local port number on which the request was received %q Query string %r first line of the request %s status code of the response %s user’s sessionID %t log and time, using the usual log format %u after authentication of the remote user (if any, Otherwise ‘-‘) %U REQUEST URI path %v Name of the local server %D Request processing time in milliseconds %T Request processing time in seconds

Realm

Realm can be understood as a “domain” or as a “group” because it is similar to the concept of groups on UNIx-like systems.

Realm domains provide a mapping between user passwords and Web applications.

Because multiple applications can be deployed in Tomcat at the same time, not every manager has access to or use these applications, hence the concept of users. But think about how cumbersome it would be if every application had to configure users with permissions, hence the concept of role. With a role, you can access the application corresponding to that role, thus achieving a kind of domain effect.

We can set different roles for each user (configured in tomcat-users.xml).

The accessible roles are set in each application (configured in web.xml).

When Tomcat is started, it is authenticated through a Realm (configured in server.xml) to access the application for role security management.

server.xml

<Realm className="org.apache.catalina.realm.LockOutRealm"> <! -- This Realm uses the UserDatabase configuredin the global JNDI
       resources under the key "UserDatabase".  Any edits
       that are performed against this UserDatabase are immediately
       available for use by the Realm.  -->
  <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
         resourceName="UserDatabase"/>
</Realm>
Copy the code

By default, realms are configured inside the Engine tag, using the UserDatabase approach. Other methods are explained in the following sections.

The location of the Realm also affects its scope.

Inside the element — A Realm will be shared by all Web applications on the host unless it is overridden by or inside the element’s Realm element.

Inside the element – This Realm will be shared by all Web applications in the local virtual host, unless overridden by the element’s internal Realm element.

Inside the element — this Realm element is used only by applications specified by the Context.

configuration

1) Configure server.xml

The code in the figure above configures the directory file for UserDatabase, conf/tomcat-users.xml.

2) Configure user passwords and assign roles in tomcat-users. XML

3) Configure access roles and security restrictions in the application’s web.xml

<security-constraint>
    <web-resource-collection>
      <web-resource-name>HTML Manager interface (for humans)</web-resource-name>
      <url-pattern>/html/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
       <role-name>manager-gui</role-name>
    </auth-constraint>
  </security-constraint>
  <security-constraint>
    <web-resource-collection>
      <web-resource-name>Text Manager interface (forscripts)</web-resource-name> <url-pattern>/text/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>manager-script</role-name> </auth-constraint> </security-constraint> <security-constraint> <web-resource-collection> <web-resource-name>JMX Proxy interface</web-resource-name> <url-pattern>/jmxproxy/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>manager-jmx</role-name> </auth-constraint> </security-constraint> <security-constraint> <web-resource-collection> <web-resource-name>Status interface</web-resource-name> <url-pattern>/status/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>manager-gui</role-name> <role-name>manager-script</role-name> <role-name>manager-jmx</role-name> <role-name>manager-status</role-name> </auth-constraint> </security-constraint> <! -- Define the Login Configurationforthis Application --> <login-config> <auth-method>BASIC</auth-method> <realm-name>Tomcat Manager Application</realm-name>  </login-config> <! -- Security roles referenced by this web application --> <security-role> <description> The role that is required to access the HTML Manager pages </description> <role-name>manager-gui</role-name> </security-role> <security-role> <description> The role that is required to access the text Manager pages </description> <role-name>manager-script</role-name> </security-role> <security-role> <description> The role that is required to access  the HTML JMX Proxy </description> <role-name>manager-jmx</role-name> </security-role> <security-role> <description> The  role that is required to access to the Manager Status pages </description> <role-name>manager-status</role-name> </security-role>Copy the code

This is the content of web.xml in the Manager project, where role-name defines accessible roles. Among other things, the resources to which access is restricted are defined above, and the login-config below is more important.

It defines the way authentication is done, and BASIC is basically a popup dialog to enter a user name and password. Or DIGEST, which encrypts information transmitted over the network and is more secure.