编程知识 cdmana.com

How Tomcat works reading notes 1-4

How tomcat works

chapter 1 ordinary web The server

The main content of this one is to implement a simple Static resource server ,socket Programming , utilize java Provided socket and serverSocket Programming

The whole process is as follows :

HttpServer adopt serverSocket Listening port , By blocking accept Method to receive the request , Then create resquest and response object ,

// Server start main method 
public static void main(String[] args) {
    HttpServer server = new HttpServer();
    server.await();
  }

//await Method , Almost all the tasks are done here 
public void await() {
    ServerSocket serverSocket = null;
    int port = 8080;
    try {
      serverSocket =  new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
    }
    catch (IOException e) {
      e.printStackTrace();
      System.exit(1);
    }

    // Loop waiting for a request
    //shutdown For a member variable , The more elegant way to stop such a server is to receive a uri by "/SHUTDOWN_COMMAND" Request 
    while (!shutdown) {
      Socket socket = null;
      InputStream input = null;
      OutputStream output = null;
      try {
        socket = serverSocket.accept();
        input = socket.getInputStream();
        output = socket.getOutputStream();

        // establish request object , hold inputstream Give it to him 
        // create Request object and parse
        Request request = new Request(input);
        request.parse();

        // create Response object
        Response response = new Response(output);
        response.setRequest(request);
        response.sendStaticResource();

        // Close the socket
        socket.close();

        //check if the previous URI is a shutdown command
        shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
      }
      catch (Exception e) {
        e.printStackTrace();
        continue;
      }
    }
  }

request The object mainly does one thing , from inputstream Chinese analysis uri

parse()

from inputstream Read data into a string , And then call parseUri Method

public void parse() {
    // Read a set of characters from the socket
    StringBuffer request = new StringBuffer(2048);
    int i;
    byte[] buffer = new byte[2048];
    try {
      i = input.read(buffer);
    }
    catch (IOException e) {
      e.printStackTrace();
      i = -1;
    }
    for (int j=0; j<i; j++) {
      request.append((char) buffer[j]);
    }
    System.out.print(request.toString());
    uri = parseUri(request.toString());
  }

parseUri(String requestString)

private String parseUri(String requestString) {
    int index1, index2;
    index1 = requestString.indexOf(' ');
    if (index1 != -1) {
      index2 = requestString.indexOf(' ', index1 + 1);
      if (index2 > index1)
          // Cut out uri
        return requestString.substring(index1 + 1, index2);
    }
    return null;
  }

response object , Get request and outputstream, according to request Medium uri Find the corresponding resource , And stuff it in outputstream In the middle of the day

sendStaticResource()

public void sendStaticResource() throws IOException {
  byte[] bytes = new byte[BUFFER_SIZE];
  FileInputStream fis = null;
  try {
    File file = new File(HttpServer.WEB_ROOT, request.getUri());
    if (file.exists()) {
      fis = new FileInputStream(file);
      int ch = fis.read(bytes, 0, BUFFER_SIZE);
      while (ch!=-1) {
        output.write(bytes, 0, ch);
        ch = fis.read(bytes, 0, BUFFER_SIZE);
      }
    }
    else {
      // file not found
      String errorMessage = "HTTP/1.1 404 File Not Found\r\n" +
              "Content-Type: text/html\r\n" +
              "Content-Length: 23\r\n" +
              "\r\n" +
              "<h1>File Not Found</h1>";
      output.write(errorMessage.getBytes());
    }
  }
  catch (Exception e) {
    // thrown if cannot instantiate a File object
    System.out.println(e.toString() );
  }
  finally {
    if (fis!=null)
      fis.close();
  }
}

tips: Since this response has no response header , therefore chrome The current version will not accept it , however firefox Sure , But it's just plain text

chapter 2 A simple servlet Containers

Chapter one makes use of java Of socket Programming builds a response that can only respond to static resources http The server , This chapter implements a simple servlet Containers , It can only deal with simple servlet And respond to static resources

be familiar with servlet Interface

servlet The interface declares 5 A way

// Initialization method 
public void init(ServletConfig config)throws ServletException;

// The main method , Accept one request Objects and response object , And then deal with it 
public void service(ServletRequest request,ServletResponse response)throws ServletException,Java.io.IOException;

// Will be servlet When an instance is removed from a service ,servlet The container will call servlet Example of destory() Method , Clean up some resources 
public void destory();

public ServletConfig getServletConfig();

public java.lang.String getServletInfo();

A fully functional servlet Container handling process

  • Call for the first time servlet when , Load it into , And then execute init() Method
  • For each request request , establish ServletRequest Instance and ServletResponse example , Pass to service() Method
  • close servlet when , call destroy() Method , And unload the class

This chapter is a simple implementation , You don't call init Methods and destroy Method , Create good ServletRequest and ServletResponse And then passed as a parameter to service The method can

tips: In this way servlet In the container , Every time for the servlet Request , Will result in loading the corresponding servlet class , As one can imagine , Efficiency is very low

Main classes and process analysis

This chapter mainly includes the following categories :

  • HttpServer1
  • Request
  • Response
  • StaticResourceProcessor
  • ServletProcessor1
  • Constants

The servlet Container of UML as follows

image.png

Processing flow

Entrance class ,HttpServer1, Listening port 8080, structure request,response, Depending on whether to request static resources or servlet Deal with... Separately

// Server main method 
public static void main(String[] args) {
    HttpServer1 server = new HttpServer1();
    server.await();
  }

//await
public void await() {
    ServerSocket serverSocket = null;
    int port = 8080;
    try {
      serverSocket =  new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
    }
    catch (IOException e) {
      e.printStackTrace();
      System.exit(1);
    }

    // Loop waiting for a request
    while (!shutdown) {
      Socket socket = null;
      InputStream input = null;
      OutputStream output = null;
      try {
        socket = serverSocket.accept();
        input = socket.getInputStream();
        output = socket.getOutputStream();

        // create Request object and parse
        Request request = new Request(input);
        request.parse();

        // create Response object
        Response response = new Response(output);
        response.setRequest(request);

        // check if this is a request for a servlet or a static resource
        // a request for a servlet begins with "/servlet/"
        if (request.getUri().startsWith("/servlet/")) {
          ServletProcessor1 processor = new ServletProcessor1();
          processor.process(request, response);
        }
        else {
          StaticResourceProcessor processor = new StaticResourceProcessor();
          processor.process(request, response);
        }

        // Close the socket
        socket.close();
        //check if the previous URI is a shutdown command
        shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
      }
      catch (Exception e) {
        e.printStackTrace();
        System.exit(1);
      }
    }
  }

request.parse()

As in Chapter 1 uri

public void parse() {
    // Read a set of characters from the socket
    StringBuffer request = new StringBuffer(2048);
    int i;
    byte[] buffer = new byte[2048];
    try {
      i = input.read(buffer);
    }
    catch (IOException e) {
      e.printStackTrace();
      i = -1;
    }
    for (int j=0; j<i; j++) {
      request.append((char) buffer[j]);
    }
    System.out.print(request.toString());
    uri = parseUri(request.toString());
  }

//uri = parseUri(request.toString());
private String parseUri(String requestString) {
    int index1, index2;
    index1 = requestString.indexOf(' ');
    if (index1 != -1) {
      index2 = requestString.indexOf(' ', index1 + 1);
      if (index2 > index1)
        return requestString.substring(index1 + 1, index2);
    }
    return null;
  }

processor.process(request, response);

request servlet Branch , Accept request,response As a parameter , And then call ServletProcessor1 Of process Method , Load the corresponding servlet then , Instantiate the servlet And actually handle requests

public void process(Request request, Response response) {

    String uri = request.getUri();
    String servletName = uri.substring(uri.lastIndexOf("/") + 1);
    URLClassLoader loader = null;

    try {
      // create a URLClassLoader
      URL[] urls = new URL[1];
      URLStreamHandler streamHandler = null;
      File classPath = new File(Constants.WEB_ROOT);
      // the forming of repository is taken from the createClassLoader method in
      // org.apache.catalina.startup.ClassLoaderFactory
      String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString() ;
      // the code for forming the URL is taken from the addRepository method in
      // org.apache.catalina.loader.StandardClassLoader class.
      urls[0] = new URL(null, repository, streamHandler);
      loader = new URLClassLoader(urls);
    }
    catch (IOException e) {
      System.out.println(e.toString() );
    }
    Class myClass = null;
    try {
      // load servlet
      myClass = loader.loadClass(servletName);
    }
    catch (ClassNotFoundException e) {
      System.out.println(e.toString());
    }

    Servlet servlet = null;

    try {
      // Instantiation servlet And process the request 
      servlet = (Servlet) myClass.newInstance();
      servlet.service((ServletRequest) request, (ServletResponse) response);
    }
    catch (Exception e) {
      System.out.println(e.toString());
    }
    catch (Throwable e) {
      System.out.println(e.toString());
    }

  }

processor.process(request, response);

Send static resource branches , Here we just call the following response Of sendStaticResource Method

public void process(Request request, Response response) {
    try {
      response.sendStaticResource();
    }
    catch (IOException e) {
      e.printStackTrace();
    }
  }

chapter 3 The connector (Connector)

Create a connector , To enhance the function of Chapter 2 , Mainly Create... In a better way request and response object

The application in this chapter consists of three modules : Connector module 、 Start module and core module

The startup module contains only one class :BootStrap

Connector modules can be divided into the following 5 A type of :

  • Connectors and their supporting classes (HttpConnector and HttpProcessor)
  • Express Http Requested class (HttpRequest) And its supporting classes
  • Express Http Response class (HttpResponse) And its supporting classes
  • Appearance class (HttpRequestFacade and HttpResponseFacade)
  • Constant class (Constants)

The core module contains two classes ,servletProcessor and StaticResourceProcessor, Specifically UML as follows

image.png

Main classes and process analysis

Start class BootStrap,new One connector And then call connector.start(), It's not hard to guess connector Of start Another thread started

public static void main(String[] args) {
    HttpConnector connector = new HttpConnector();
    connector.start();
  }

HttpConnector

Connector module main class , Responsible for listening port , Receiving request , Then pass yourself on to HttpProcessor, And will socket As a parameter , Pass to HttpProcessor Of process Method

run()

public void run() {
    ServerSocket serverSocket = null;
    int port = 8080;
    try {
      serverSocket =  new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
    }
    catch (IOException e) {
      e.printStackTrace();
      System.exit(1);
    }
    while (!stopped) {
      // Accept the next incoming connection from the server socket
      Socket socket = null;
      try {
        socket = serverSocket.accept();
      }
      catch (Exception e) {
        continue;
      }
      // Hand this socket off to an HttpProcessor
      HttpProcessor processor = new HttpProcessor(this);
      processor.process(socket);
    }
  }

public void start() {
    Thread thread = new Thread(this);
    thread.start();
  }

HttpProcessor

Responsible for generating resquest as well as response, And then to ServletProcessor perhaps StaticResourceProcessor Handle

public void process(Socket socket)

public void process(Socket socket) {
    //SocketInputStream yes InputStream The wrapper class , Mainly provides readRequestLine Methods and readHeader Method 
    SocketInputStream input = null;
    OutputStream output = null;
    try {
      //2048 For the specified buffer array size 
      input = new SocketInputStream(socket.getInputStream(), 2048);
      output = socket.getOutputStream();

      // create HttpRequest object and parse
      request = new HttpRequest(input);

      // create HttpResponse object
      response = new HttpResponse(output);
      response.setRequest(request);

      response.setHeader("Server", "Pyrmont Servlet Container");

      // Parse request line 
      parseRequest(input, output);
      // Parse the first line 
      parseHeaders(input);
	
      // And then you can ask for uri Decide what to hand over processor 了 
      // The two are the same as in Chapter 2 
      //check if this is a request for a servlet or a static resource
      //a request for a servlet begins with "/servlet/"
      if (request.getRequestURI().startsWith("/servlet/")) {
        ServletProcessor processor = new ServletProcessor();
        processor.process(request, response);
      }
      else {
        StaticResourceProcessor processor = new StaticResourceProcessor();
        processor.process(request, response);
      }

      // Close the socket
      socket.close();
      // no shutdown for this application
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
Parse request line parseRequest

private void parseRequest(SocketInputStream input, OutputStream output)

private void parseRequest(SocketInputStream input, OutputStream output)
    throws IOException, ServletException {

    // Parse the incoming request line
    //HttpRequestLine For the request line object , utilize readRequestLine Method fill HttpProcessor Of HttpRequestLine Member variables 
    input.readRequestLine(requestLine);
    // After the above treatment , You can get the method name here 
    String method =
      new String(requestLine.method, 0, requestLine.methodEnd);
    //uri I can't get it yet , Because it's possible to bring ginseng , At this time uri The character array holds the parameter uri
    String uri = null;
    String protocol = new String(requestLine.protocol, 0, requestLine.protocolEnd);

    // Validate the incoming request line
    if (method.length() < 1) {
      throw new ServletException("Missing HTTP request method");
    }
    else if (requestLine.uriEnd < 1) {
      throw new ServletException("Missing HTTP request URI");
    }
    // Parse any query parameters out of the request URI
    // Processing with reference uri
    int question = requestLine.indexOf("?");
    if (question >= 0) {
        // Packet request parameters are pushed in queryString, When you need to go back and analyze 
      request.setQueryString(new String(requestLine.uri, question + 1,
        requestLine.uriEnd - question - 1));
      uri = new String(requestLine.uri, 0, question);
    }
    else {
      request.setQueryString(null);
      uri = new String(requestLine.uri, 0, requestLine.uriEnd);
    }

	// Check , Skipping 
    // Checking for an absolute URI (with the HTTP protocol)
    if (!uri.startsWith("/")) {
      int pos = uri.indexOf("://");
      // Parsing out protocol and host name
      if (pos != -1) {
        pos = uri.indexOf('/', pos + 3);
        if (pos == -1) {
          uri = "";
        }
        else {
          uri = uri.substring(pos);
        }
      }
    }
	
    // Handle jessionId
    // Parse any requested session ID out of the request URI
    String match = ";jsessionid=";
    int semicolon = uri.indexOf(match);
    if (semicolon >= 0) {
      String rest = uri.substring(semicolon + match.length());
      int semicolon2 = rest.indexOf(';');
      if (semicolon2 >= 0) {
        request.setRequestedSessionId(rest.substring(0, semicolon2));
        rest = rest.substring(semicolon2);
      }
      else {
        request.setRequestedSessionId(rest);
        rest = "";
      }
      request.setRequestedSessionURL(true);
      uri = uri.substring(0, semicolon) + rest;
    }
    else {
      request.setRequestedSessionId(null);
      request.setRequestedSessionURL(false);
    }

    // Normalize URI (using String operations at the moment)
    String normalizedUri = normalize(uri);

    // Set the corresponding request properties
    // Insert all the attributes of the request line , At this point, the data of the request line is parsed 
    ((HttpRequest) request).setMethod(method);
    request.setProtocol(protocol);
    if (normalizedUri != null) {
      ((HttpRequest) request).setRequestURI(normalizedUri);
    }
    else {
      ((HttpRequest) request).setRequestURI(uri);
    }

    if (normalizedUri == null) {
      throw new ServletException("Invalid URI: " + uri + "'");
    }
  }

HttpRequestLine

HttpRequestLine For the request line object , A character array is used to store the data of the request line , At a glance at the code

public HttpRequestLine(char[] method, int methodEnd,
                           char[] uri, int uriEnd,
                           char[] protocol, int protocolEnd) {

        this.method = method;
        this.methodEnd = methodEnd;
        this.uri = uri;
        this.uriEnd = uriEnd;
        this.protocol = protocol;
        this.protocolEnd = protocolEnd;

}

    public char[] method;
    public int methodEnd;
    public char[] uri;
    public int uriEnd;
    public char[] protocol;
    public int protocolEnd;

SocketInputStream Of readRequestLine Method

public void readRequestLine(HttpRequestLine requestLine)

public void readRequestLine(HttpRequestLine requestLine)
        throws IOException {

        // Recycling check
        if (requestLine.methodEnd != 0)
            requestLine.recycle();

        // Checking for a blank line
    	// Skip empty lines 
        int chr = 0;
        do { // Skipping CR or LF
            try {
                // call read Method ,
                chr = read();
            } catch (IOException e) {
                chr = -1;
            }
        } while ((chr == CR) || (chr == LF));
        if (chr == -1)
            throw new EOFException
                (sm.getString("requestStream.readline.error"));
    	// because read method pos It points to the next byte , Need to step back 
        pos--;

        // Reading the method name

        int maxRead = requestLine.method.length;
        int readStart = pos;
        int readCount = 0;

        boolean space = false;

        while (!space) {
            // exceed method The maximum length of the character array 
            // if the buffer is full, extend it
            if (readCount >= maxRead) {
                if ((2 * maxRead) <= HttpRequestLine.MAX_METHOD_SIZE) {
                    char[] newBuffer = new char[2 * maxRead];
                    System.arraycopy(requestLine.method, 0, newBuffer, 0,
                                     maxRead);
                    requestLine.method = newBuffer;
                    maxRead = requestLine.method.length;
                } else {
                    throw new IOException
                        (sm.getString("requestStream.readline.toolong"));
                }
            }
            // We're at the end of the internal buffer
            // If you have read inputstream Read it out buffer In the last position of 
            if (pos >= count) {
                int val = read();
                if (val == -1) {
                    throw new IOException
                        (sm.getString("requestStream.readline.error"));
                }
                pos = 0;
                readStart = 0;
            }
            // That's where normal comes from 
            if (buf[pos] == SP) {
                space = true;
            }
            // here method The character array is a space section , Not in buf[pos]=SP When break
            requestLine.method[readCount] = (char) buf[pos];
            readCount++;
            pos++;
        }
		// End with a space ,readCount Remove space 
        requestLine.methodEnd = readCount - 1;
		// Here is the analysis uri and protocol 了 

    }

SocketInputStream Of read、fill Method

public int read()
        throws IOException {
   		// Starting value pos by 0,count Also for the 0
        if (pos >= count) {
            // modify count Is the length of bytes read this time 
            fill();
            if (pos >= count)
                return -1;
        }
    	// return buf[pos] That byte of 
        return buf[pos++] & 0xff;
    }

protected void fill()
        throws IOException {
        pos = 0;
        count = 0;
    	//
        int nRead = is.read(buf, 0, buf.length);
        if (nRead > 0) {
            count = nRead;
        }
    }
Parse the first line parseHeaders

private void parseHeaders(SocketInputStream input)

private void parseHeaders(SocketInputStream input)
    throws IOException, ServletException {
    // Loop through each request header 
    while (true) {
      HttpHeader header = new HttpHeader();;

      // Read the next header
      //socketInputStream Provided readHeader Method , It has been read in detail above readRequestLine The method , There is no detailed interpretation of , fill header
      //HttpHeader and HttpRequestLine The structure is similar 
      input.readHeader(header);
      if (header.nameEnd == 0) {
        if (header.valueEnd == 0) {
          return;
        }
        else {
          throw new ServletException
            (sm.getString("httpProcessor.parseHeaders.colon"));
        }
      }

      String name = new String(header.name, 0, header.nameEnd);
      String value = new String(header.value, 0, header.valueEnd);
      request.addHeader(name, value);
      // about cookie and content-length Two header It needs special treatment 
      // do something for some headers, ignore others.
      if (name.equals("cookie")) {
        Cookie cookies[] = RequestUtil.parseCookieHeader(value);
        for (int i = 0; i < cookies.length; i++) {
          if (cookies[i].getName().equals("jsessionid")) {
            // Override anything requested in the URL
            if (!request.isRequestedSessionIdFromCookie()) {
              // Accept only the first session id cookie
              request.setRequestedSessionId(cookies[i].getValue());
              request.setRequestedSessionCookie(true);
              request.setRequestedSessionURL(false);
            }
          }
          request.addCookie(cookies[i]);
        }
      }
      else if (name.equals("content-length")) {
        int n = -1;
        try {
          n = Integer.parseInt(value);
        }
        catch (Exception e) {
          throw new ServletException(sm.getString("httpProcessor.parseHeaders.contentLength"));
        }
        request.setContentLength(n);
      }
      else if (name.equals("content-type")) {
        request.setContentType(value);
      }
    } //end while
  }
Lazy loading parameter

Request parameters are not parsed until they are called , stay request Of getParameter、getParameterMap、getParameterNames When the method is called , The first call parseParameter Method

One thing to note is that ,ParameterMap Modification is not supported , It's locked , It's simple , Use one lock Variable , The modification is direct return

protected void parseParameters()

protected void parseParameters() {
    if (parsed)
      return;
    ParameterMap results = parameters;
    if (results == null)
      results = new ParameterMap();
    // Lock the lock first 
    results.setLocked(false);
    String encoding = getCharacterEncoding();
    if (encoding == null)
      encoding = "ISO-8859-1";

    //parse Parameters in the request header 
    // Parse any parameters specified in the query string
    String queryString = getQueryString();
    try {
      RequestUtil.parseParameters(results, queryString, encoding);
    }
    catch (UnsupportedEncodingException e) {
      ;
    }

    //parse Parameters in request body 
    // Parse any parameters specified in the input stream
    String contentType = getContentType();
    if (contentType == null)
      contentType = "";
    int semicolon = contentType.indexOf(';');
    if (semicolon >= 0) {
      contentType = contentType.substring(0, semicolon).trim();
    }
    else {
      contentType = contentType.trim();
    }
    if ("POST".equals(getMethod()) && (getContentLength() > 0)
      && "application/x-www-form-urlencoded".equals(contentType)) {
      try {
        int max = getContentLength();
        int len = 0;
        byte buf[] = new byte[getContentLength()];
        ServletInputStream is = getInputStream();
        while (len < max) {
          int next = is.read(buf, len, max - len);
          if (next < 0 ) {
            break;
          }
          len += next;
        }
        is.close();
        if (len < max) {
          throw new RuntimeException("Content length mismatch");
        }
        RequestUtil.parseParameters(results, buf, encoding);
      }
      catch (UnsupportedEncodingException ue) {
        ;
      }
      catch (IOException e) {
        throw new RuntimeException("Content read fail");
      }
    }

    // Store the final results
    // And then parameterMap Lock up , And will parsed Set as true Avoid repetition parse
    results.setLocked(true);
    parsed = true;
    parameters = results;
  }

chapter 4 Tomcat Default connector

General introduction

Chapter 3 implements a simple connector , Chapter four is about Tomcat Default connector , Although the connector has been discarded , Replaced by a faster connector ——Coyote—— replace , But it's worth seeing , Compared to the simple connector in Chapter 3 ,Tomcat4 The default connector for has the following optimizations :

  • More comprehensive support for Http agreement , Ranging from 0.9 To 1.1, Also support 1.1 New features
  • every last connector No longer just bind one HttpProcessor, Will use multithreading to support multiple Http Connect
  • Make a lot of use of character arrays , Reduce the expensive string operations
  • Using pool technology , Cached HttpProcessor object , Avoid creating every time you come , It saves the cost of object creation

But there's still a way to go straight up parse be-all Header No matter whether it is used or not , The problem of low efficiency

Its main working principle is similar to the connector in Chapter 3 , The details are different, which will be discussed later in the source code

Default connector UML The graph is as follows :

image.png

The first thing to notice is , Different from chapter three ,HttpConnector To be responsible for Request、Response Created , and HttpProcessor Focus on parse Information , That is, filling request and response

Http1.1 New features

Persistent connection

Less frequent creation of TCP System consumption caused by connection , It also reduces the number of handshakes , Speed up , stay Http1.1 in , Persistent connection is adopted by default , You can also use... Explicitly , stay request First line plus

Connection: Keep-alive
Block coding

With a persistent connection , One TCP Connections may be shared , The data of so many objects may be mixed together , So how do you tell them apart ? utilize transfer-encoding Special request header for , It is used to indicate that the byte stream will be sent in blocks , For each piece , The length of the block is followed by a CRLF, And then there's the data , A transaction has a length of 0 The block mark of ,“0\r\n” Indicates that the transaction has ended

//e.g Use two blocks to send  I'am as helpess as a kitten up a tree.

// The message is as follows 
1D\r\n
I'am as helpess as a kitten u
9\r\n
p a tree.
0\r\n
Status code 100

When the browser wants to send a larger request body , I don't know if the server can receive it , You can first send a request with the following header

Except: 100-continue

The expected response from the service is 100-continue, Receiving this response indicates that you can receive , At this time, the browser will actually send out this relatively large request , Avoid wasting bandwidth 、 System resources

If the server receives a request with the above request header , And support it , Send the following reply

HTTP/1.1 100 continue

So the browser will send the real request

Main classes and process analysis

establish ServerSocket

First call initialize Method

void initialize()

public void initialize()
    throws LifecycleException {
        if (initialized)
            throw new LifecycleException (
                sm.getString("httpConnector.alreadyInitialized"));
        this.initialized=true;
        Exception eRethrow = null;
        // Establish a server socket on the specified port
        try {
            // call open Method 
            serverSocket = open();
        } catch (IOException ioe) {
         	...   
        }
        if ( eRethrow != null )
            throw new LifecycleException(threadName + ".open", eRethrow);
}

ServerSocket open()

private ServerSocket open()
    throws IOException, KeyStoreException, NoSuchAlgorithmException,
           CertificateException, UnrecoverableKeyException,
           KeyManagementException
    {

        // Acquire the server socket factory for this Connector
        ServerSocketFactory factory = getFactory();
		// According to whether there is a limit to IP Address selection different factory methods 
        // If no address is specified, open a connection on all addresses
        if (address == null) {
            log(sm.getString("httpConnector.allAddresses"));
            try {
                // Unlimited port address , That is to say, monitor any IP Request 
                return (factory.createSocket(port, acceptCount));
            } catch (BindException be) {
                throw new BindException(be.getMessage() + ":" + port);
            }
        }

        // Open a server socket on the specified address
        try {
            InetAddress is = InetAddress.getByName(address);
            log(sm.getString("httpConnector.anAddress", address));
            try {
                return (factory.createSocket(port, acceptCount, is));
            } catch (BindException be) {
                throw new BindException(be.getMessage() + ":" + address +
                                        ":" + port);
            }
        } catch (Exception e) {
            log(sm.getString("httpConnector.noAddress", address));
            try {
                return (factory.createSocket(port, acceptCount));
            } catch (BindException be) {
                throw new BindException(be.getMessage() + ":" + port);
            }
        }

    }

factory.createSocket(port, acceptCount)

//ServerSocketFactory In the definition of , stay DefalutServerSocketFactory To realize 
public ServerSocket createSocket (int port, int backlog)
    throws IOException, KeyStoreException, NoSuchAlgorithmException,
           CertificateException, UnrecoverableKeyException,
           KeyManagementException {

        return (new ServerSocket(port, backlog));

    }

So far ServerSocket And with HttpConnector Of serverSocket Member variable binding

Monitor requests

start()

public void start() throws LifecycleException {

        // Validate and update our current state
        if (started)
            throw new LifecycleException
                (sm.getString("httpConnector.alreadyStarted"));
        threadName = "HttpConnector[" + port + "]";
        lifecycle.fireLifecycleEvent(START_EVENT, null);
        started = true;

        // Start our background thread
    	//new One. thread  And called thread Of start Method 
        threadStart();

        // Create the specified minimum number of processors
        while (curProcessors < minProcessors) {
            if ((maxProcessors > 0) && (curProcessors >= maxProcessors))
                break;
            HttpProcessor processor = newProcessor();
            recycle(processor);
        }

    }

void run()

start After entering run Method

public void run() {

        // Loop until we receive a shutdown command
    	//stopped Member variables 
        while (!stopped) {

            // Accept the next incoming connection from the server socket
            Socket socket = null;
            try {
                // Receiving request 
                socket = serverSocket.accept();
                // Set timeout duration 
                if (connectionTimeout > 0)
                    socket.setSoTimeout(connectionTimeout);
            } catch (AccessControlException ace) {
                log("socket accept security exception: " + ace.getMessage());
                continue;
            } catch (IOException e) {
                if (started && !stopped)
                    log("accept: ", e);
                break;
            }

            // Hand this socket off to an appropriate processor
            // From member variable processors, This processor Take one in the pool HttpProcessor come out 
            // If there's no , Then create a 
            HttpProcessor processor = createProcessor();
            if (processor == null) {
                try {
                    log(sm.getString("httpConnector.noProcessor"));
                    // If you are not allowed to create , Then directly put socket close fall , That is to abandon the request directly 
                    socket.close();
                } catch (IOException e) {
                    ;
                }
                continue;
            }
            // hold socket hand processor
            processor.assign(socket);

            // The processor will recycle itself when it finishes

        }

        // Notify the threadStop() method that we have shut ourselves down
        synchronized (threadSync) {
            threadSync.notifyAll();
        }

    }

HttpProcessor createProcessor()

private HttpProcessor createProcessor() {

        synchronized (processors) {
            if (processors.size() > 0)
                return ((HttpProcessor) processors.pop());
            if ((maxProcessors > 0) && (curProcessors < maxProcessors)) {
                //stack There's no free processor 了 , And the current Processor The quantity has not exceeded the maximum allowed Processor Number , Create a new one and return to 
                return (newProcessor());
            } else {
                // If maxProcessors<0  explain processor The quantity is unlimited , You can create 
                if (maxProcessors < 0) {
                    return (newProcessor());
                } else {
                    // explain curProcessors > maxProcessors  There is no free processor 了 , It's not allowed to create 
                    return (null);
                }
            }
        }

    }

HttpProcessor newProcessor()

private HttpProcessor newProcessor() {
		// establish processor And bind connector
        HttpProcessor processor = new HttpProcessor(this, curProcessors++);
        if (processor instanceof Lifecycle) {
            try {
                // Be careful !!!  Here's the launch of HttpProcessor Threads 
                ((Lifecycle) processor).start();
            } catch (LifecycleException e) {
                log("newProcessor", e);
                return (null);
            }
        }
        created.addElement(processor);
        return (processor);

    }

processor.assign(socket)

connector hold socket Deliver to HttpProcessor It's over , It doesn't have to wait like that before HttpProcessor The results of the implementation of , It can continue to listen for other requests , It's just responsible for Here's the wake-up call in run Because there is no socket Obstructed processor

synchronized void assign(Socket socket) {

        // Wait for the Processor to get the previous Socket
    	//available The default value is false, Represents whether there is a new socket To come over 
        while (available) {
            try {
                wait();
            } catch (InterruptedException e) {
            }
        }

        // Store the newly available Socket and notify our thread
        this.socket = socket;
        available = true;
    	//notifyAll Who awakened ? awakened HttpProcessor The main thread 
    	// It's in newProcessor Method , And then in await Wait in method 
        notifyAll();

        if ((debug >= 1) && (socket != null))
            log(" An incoming request is being assigned");

    }
Processing requests

HttpProcessor

run()

public void run() {

        // Process requests until we receive a shutdown signal
    	//stopped Member variables 
        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
            // hold processor Back again connector Reuse in the stack of 
            connector.recycle(this);

        }

        // Tell threadStop() we have shut ourselves down successfully
        synchronized (threadSync) {
            threadSync.notifyAll();
        }

    }

await()

private synchronized Socket await() {

        // Wait for the Connector to provide a new Socket
        while (!available) {
            try {
                wait();
            } catch (InterruptedException e) {
            }
        }
		// After being awakened availabe It was modified to true 了 , take socket Assign to local variable , And will availabe It is amended as follows false
        // 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);

    }

image.png

Here is the explanation of the original text

Why we need to return a local variable here ?

In doing so , You can do it right now socket Before being processed , Take over the next socket??? Black question mark , This process Methods are not single threaded ? It doesn't have to be parsed before it can be recycled ? How to receive two at the same time socket Of

Why do we still need notifyAll Well ?

prevent connector In carrying out the processor.assign() Methods block , here available by true,connector Keep blocking , know processor Wake up it

recycle(HttpProcessor processor)

void recycle(HttpProcessor processor) {

        //        if (debug >= 2)
        //            log("recycle: Recycling processor " + processor);
        processors.push(processor);

    }

The main method to process the request

**private void process(Socket socket) **

private void process(Socket socket) {
        boolean ok = true;
        boolean finishResponse = true;
        SocketInputStream input = null;
        OutputStream output = null;

        // Construct and initialize the objects we will need
        try {
            input = new SocketInputStream(socket.getInputStream(),
                                          connector.getBufferSize());
        } catch (Exception e) {
            log("process.create", e);
            ok = false;
        }

        keepAlive = true;
		// It turns out that the new feature of persistent connection that has been promoted for a long time is keepAlive Variable 
        while (!stopped && ok && keepAlive) {

            finishResponse = true;

            try {
                request.setStream(input);
                request.setResponse(response);
                output = socket.getOutputStream();
                response.setStream(output);
                response.setRequest(request);
                ((HttpServletResponse) response.getResponse()).setHeader
                    ("Server", SERVER_INFO);
            } catch (Exception e) {
                log("process.create", e);
                ok = false;
            }


            // Parse the incoming request
            try {
                if (ok) {
					// As in chapter three , This is where filling is handled Request, If you want to investigate how to deal with Http1.1 New features , You can see , I won't go into details here 
                    parseConnection(socket);
                    parseRequest(input, output);
                    if (!request.getRequest().getProtocol()
                        .startsWith("HTTP/0"))
                        parseHeaders(input);
                    if (http11) {
                        // Sending a request acknowledge back to the client if
                        // requested.
                        ackRequest(output);
                        // If the protocol is HTTP/1.1, chunking is allowed.
                        if (connector.isChunkingAllowed())
                            response.setAllowChunking(true);
                    }

                }
            } catch (EOFException e) {
                ...
            } 

            // Ask our Container to process this request
            try {
                ((HttpServletResponse) response).setHeader
                    ("Date", FastHttpDateFormat.getCurrentDate());
                if (ok) {
                    // from processor The binding of connector In order to get contianer And then put parse well request and response Pass it on to him , Processing requests 
                    connector.getContainer().invoke(request, response);
                }
            } catch (ServletException e) {
                ...
            } 

            // Finish up the handling of the request
            if (finishResponse) {
                try {
                    //
                    response.finishResponse();
                } catch (IOException e) {
                    ok = false;
                } catch (Throwable e) {
                    log("process.invoke", e);
                    ok = false;
                }
                try {
                    request.finishRequest();
                } catch (IOException e) {
                    ok = false;
                } catch (Throwable e) {
                    log("process.invoke", e);
                    ok = false;
                }
                try {
                    if (output != null)
                        output.flush();
                } catch (IOException e) {
                    ok = false;
                }
            }

            // We have to check if the connection closure has been requested
            // by the application or the response stream (in case of HTTP/1.0
            // and keep-alive).
            if ( "close".equals(response.getHeader("Connection")) ) {
                keepAlive = false;
            }

            // End of request processing
            status = Constants.PROCESSOR_IDLE;

            // Recycling the request and the response objects
            //request and response Objects also need to be recycled , stay newProcessor Called when HttpConnector Of createxxx Method created as processor Member variables of 
            request.recycle();
            response.recycle();

        }

        try {
            shutdownInput(input);
            socket.close();
        } catch (IOException e) {
            ;
        } catch (Throwable e) {
            log("process.invoke", e);
        }
        socket = null;



    }
Processing requests (Container)

The core approach invoke, That's what happened before servletProcessor Do the things , Find the corresponding through the class loader servlet And then let him deal with

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) {
      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());
    }



  }

版权声明
本文为[It's not easy for the south wind to know me]所创,转载请带上原文链接,感谢

Scroll to Top