In fact, in Chapter 3, we already have a connector, but it is just a learning tool. In this chapter, we will begin to analyze the default connector in tomcat4.
Connector
The Tomcat Connector must meet the following requirements:
1. Implement the org. Apache. cataline. connector Interface
2. Creates a request object that implements the org. Apache. cataline. Request interface.
3. Creates a response object that implements the org. Apache. cataline. Response interface.
The default connector principle here is very simple. It is to wait for an HTTP request, create a request and response object, and then hand over the task to the invoke method of the container interface for processing. (Here is the command mode. Let's talk about the problem later)
Public void invoke (Org. Apache. cataline. Request request, org. Apache. cataline. Response response );
New Features of http1.1
1 persistent connection
2 encoding
3 Status Code 100
These three points are also reflected in the connector in this chapter, but I think this part is not the focus of this blog, so there are only a lot of explanations.
Tomcat Structure
In fact, this part of content is in the preface of the book. I had to talk to my friends at the beginning of this series of books, but I didn't really understand it at the time, it can only be dragged to the present.
A servlet container (container) can correspond to several connectors ).
The main task of the connector is to receive HTTP requests, generate requests and response, and then deliver the processing process to the container;
What the container does is to call the corresponding servlet object (specifically, call the servlet service method and use the passed request and response as parameters)
The following figure shows the ctor interface class.
Note: Unlike Chapter 3, httpconnector and httpprocessor have one-to-many relationships. Why? Multi-threaded, efficient, one httpprocessor can only process one HTTP request at a time. This is also different from Chapter 3.
The interface is as follows:
According to the above, we should be able to guess that the methods I marked in the figure are the most important in the interface.
The httpconnector class implements both the connector interface and the lifecycle interface (this interface will be discussed later)
Unlike Chapter 3, the httpconnector class has three more functions.
1. Create a server socket
When the connector is initialized, the open method of the httpconnector class is called to fill in the serversocket member variable.
In open, a defaserverserversocketfactory is generated first, and the port number and acceptcount are used in the slave factory. (This connector ctor can simultaneously support HTTP requests) to create a serversocket object (in fact, I do not understand why the new socket is just for user producer separation)
2. Maintain an httpprocess instance
We just mentioned that a ctor Ctor is associated with multiple httpprocessor objects. These objects are stored in the stack data structure (Advanced and later)
private Stack processors = new Stack(); private HttpProcessor createProcessor() { synchronized (processors) { if (processors.size() > 0) return ((HttpProcessor) processors.pop()); if ((maxProcessors > 0) && (curProcessors < maxProcessors)) { return (newProcessor()); } } else { if (maxProcessors < 0) { return (newProcessor()); } else { return (null); } } } }
The above method generates a new processor with two parameters. maxprocessor is the maximum prcessor volume supported by ctor, and the other is the current process volume. The above logic meaning is not explained. Both parameters can be set through the set method.
3. Provide HTTP request service
Similar to the previous chapters, the main work is within the run method.
public void run() { // Loop until we receive a shutdown command while (!stopped) { // Accept the next incoming connection from the server socket Socket socket = null; try { socket = serverSocket.accept(); ....
Familiar accept methods
Then
HttpProcessor processor = createProcessor(); //分线程 if (processor == null) { try { socket.close(); } catch (IOException e) { ; } continue; //话说这个continue是干什么的? } processor.assign(socket); //主线程
Note that I have commented on the above Code and there are two threads, so processor. assign is called only in the main thread, and does not need to wait for the resolution result. The specific resolution is in another thread. The result is that multiple HTTP requests can be processed simultaneously.
Only createprocessor generates a new thread, because
private HttpProcessor newProcessor() { // if (debug >= 2) // log("newProcessor: Creating new processor"); HttpProcessor processor = new HttpProcessor(this, curProcessors++); if (processor instanceof Lifecycle) { try { ((Lifecycle) processor).start(); } catch (LifecycleException e) { log("newProcessor", e); return (null); } } created.addElement(processor); return (processor); }
The answer is (lifecycle) processor). Start ();
Httpprocessor class
Since we have mentioned above that the creatprcessor method will start a new thread, and there is also an asynchronous Assign Method, let's look at the assign and run methods of the httpprocessor class.
public void run() { // Process requests until we receive a shutdown signal while (!stopped) { // Wait for the next socket to be assigned Socket socket = await(); if (socket == null) continue; // Process the request from this socket try { process(socket); } catch (Throwable t) { log("process.invoke", t); } // Finish up this request connector.recycle(this); //将自己重新push进stack里 }synchronized void assign(Socket socket) { // Wait for the Processor to get the previous Socket while (available) { try { wait(); } catch (InterruptedException e) { } } // Store the newly available Socket and notify our thread this.socket = socket; available = true; notifyAll(); if ((debug >= 1) && (socket != null)) log(" An incoming request is being assigned"); }
There is another await method in run.
private synchronized Socket await() { // Wait for the Connector to provide a new Socket while (!available) { try { wait(); } catch (InterruptedException e) { } } // Notify the Connector that we have received this Socket Socket socket = this.socket; available = false; notifyAll(); if ((debug >= 1) && (socket != null)) log(" The incoming request has been awaited"); return (socket); }
There is an available in it, which seems to be the key. Let's take a look.
/** * Is there a new socket available? */ private boolean available = false;
I would like to say that available is false at first. When we creatprocessor, we called the run method and the await method. Because available is false, while the loop takes effect, the thread that calls the wait () of the object stops here.
Processor. Assign (socket); // main thread
Check this code, and then read assign (). If available is false, skip the loop, change available to true, and then notify all processes.
The await method in run can be run because available is true, followed by process (socket );
Request object
The request object in the default connector is an instance of the org. Apache. cataline. Request interface. The UML class diagram is as follows:
Response object
The UML class diagram is as follows:
It is really troublesome to process requests. It can be understood that the whole process is a large loop. When the httpprocessor instance is terminated or the link is disconnected, the loop is stopped.
The initialization of the request object and the response object
Then the parseconnection (), parserequest (), and parseheaders () methods are used to parse the connection, request, and request headers respectively.
The resolution request is similar to the content in Chapter 3. A character array is used for parsing the request header to avoid costly string operations.
After parsing, the process method passes the request and response objects as parameters to the invoke method of the servlet container.
Connector. getcontainer (). Invoke (request, response );
The simple container application is now talking about container. The implementation class we use is simplecontainer (the staticresourceprocessor class has been removed, so no static resources can be accessed)
Let's just look at his invoke method.
public void invoke(Request request, Response response) throws IOException, ServletException { String servletName = ( (HttpServletRequest) request).getRequestURI(); servletName = servletName.substring(servletName.lastIndexOf("/") + 1); URLClassLoader loader = null; try { URL[] urls = new URL[1]; URLStreamHandler streamHandler = null; File classPath = new File(WEB_ROOT); String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString() ; urls[0] = new URL(null, repository, streamHandler); loader = new URLClassLoader(urls); } catch (IOException e) { System.out.println(e.toString() ); } Class myClass = null; try { myClass = loader.loadClass(servletName); } catch (ClassNotFoundException e) { e.printStackTrace(); System.out.println(e.toString()); } Servlet servlet = null; try { servlet = (Servlet) myClass.newInstance(); servlet.service((HttpServletRequest) request, (HttpServletResponse) response); } catch (Exception e) { System.out.println(e.toString()); } catch (Throwable e) { System.out.println(e.toString()); } }
Indeed, if you read the first three chapters of the book, you will find that there is nothing to say here, very simple.
Bootstrap
public final class Bootstrap { public static void main(String[] args) { HttpConnector connector = new HttpConnector(); SimpleContainer container = new SimpleContainer(); connector.setContainer(container); try { connector.initialize(); connector.start(); //注意这里还只是单纯的函数调用,目前跟线程还没关系,还没run呢 // make the application wait until we press any key. System.in.read(); } catch (Exception e) { e.printStackTrace(); } }}
The initialization part contains the lifecircle issue. don't worry too much. We will talk about it later.
Next, let's talk about the command mode in Tomcat.
Images in the blog are from how Tomcat works
How Tomcat works Reading Notes 4 default connector for Tomcat