preface

Chapter 1 explains the principle of Tomcat step by step. In the following chapters, Tomcat is improved based on the new functions in chapter 1.

The result is a simplified version of Tomcat. So if you are interested, please read it in order. This article is to record the knowledge points of chapter 2

And source code implementation (make wheels).

Copy the code

Review the content

In the last chapter, we implemented a simple static resource Web server, which can read user-defined HTML/ CSS/JS/images and display them to the browser and display 404 pages.

This chapter content

This chapter will implement a simple Servlet container that can invoke and execute the corresponding Servlet’s service() method based on the user’s request URI. The init ()/destory () method and it/HttpServletResponse inside most of the methods still not implemented, this chapter will gradually improve in the following chapters.

Prior to the start

  • javax.servlet.Servlet

    We web development students all know, just learn web development when the first implementation of the Servlet interface to customize their own

    Servlet class, so here is a brief review of the Servlet interface.

    Copy the code

    Add a dependency to the project:

    <dependency>

    <groupId>javax.servlet</groupId>

    <artifactId>javax.servlet-api</artifactId>

    <version>3.0.1</version>

    </dependency>

    Copy the code

    Servlet interface methods:

    public interface Servlet {

    public void init(ServletConfig config) throws ServletException;

    public ServletConfig getServletConfig(a);

    public void service(ServletRequest req, ServletResponse res)throws ServletException, IOException;

    public String getServletInfo(a);public void destroy(a);

    }

    Copy the code

  • How to implement

    Here, based on the code from the previous chapter, we extract the URI out of the concrete servlet name as soon as the user enters 127.0.0.1:8080/servlet/{servletName}, Use the Java.net URLClassLoader to load and instantiate this Servlet class, and then call its service() method.

Code implementation

1. Implement corresponding interfaces

We first implement the ServletRequest and ServletResponse interfaces of Request and Response in the last chapter respectively (this is the Servlet specification), and we will do nothing about the specific implementation methods, and we will improve them later.

public class Request implements ServletRequest {

. Omit N methods

}

public class Response implements ServletResponse {

/*Response only implements this method, encapsulating our socket outputStream as a PrintWriter*/

@Override

public PrintWriter getWriter(a) throws IOException {

PrintWriter writer = new PrintWriter(outputStream,true);

return writer;

}

}

Copy the code

2. Use different actuators for different resources

Our Tomcat is ready to support servlet calls, so servlets are not the same as normal static resources, so we should isolate them at the code level to facilitate future extension. Here we implement the following two actuators:

- ServletProcess Specifies an executor that executes servlets

- StaticResourceProcess Executor that executes static resources

Copy the code

So let’s look at our current request execution flow:



In fact, as you can see, there is not much change from the previous, just more if judgment, and then the corresponding execution process is thrown into the executor to execute.

  • HttpServer

    You may remember that HttpServer is our main entry point for launching programs and the ServerSocket listening implementation. It doesn’t change much, except to add an if judgment:

public static void main(String[] args) {

ServerSocket serverSocket = new ServerSocket(8080.1, InetAddress.getByName("127.0.0.1"));

.

// Parse the user's request

Request request = new Request();

request.setRequestStream(inputStream);

request.parseRequest();

// Generate the corresponding response

Response response = new Response(outputStream, request);

// Call a different handler to process the request based on the URI

if (request.getUri().startsWith("/servlet/")) {

new ServletProcess().process(request, response);

} else {

new StaticResourceProcess().process(request, response);

}

.

}

Copy the code
  • StaticResourceProcess

    The StaticResourceProcess does nothing but call the method used in the previous section to read the static resource

public class StaticResourceProcess {

public void process(Request request, Response response) throws IOException {

response.accessStaticResources();

}

}

Copy the code
  • ServletProcess

    The ServletProcess holds a static URLClassLoader variable that is specifically used to load servlets:

    private static final URLClassLoader URL_CLASS_LOADER;

    static {

    /* Locate to our webroot/servlet/ folder */

    URL servletClassPath = new File(HttpServer.WEB_ROOT, "servlet").toURI().toURL();

    // Initialize the classloader

    URL_CLASS_LOADER = new URLClassLoader(new URL[]{servletClassPath});

    }

    Copy the code

    Now that we know that URI requests starting with /servlet/ call servlet resources, how do we extract the servlet name and initialize it? Let’s start with a URI:

    /servlet/TestServlet

    Copy the code

    The lastIndexOf and subString methods of String can be used directly:

    uri = uri.substring(uri.lastIndexOf("/") + 1);

    Copy the code

    Now that the previous challenges are resolved, let’s look at how process executes:

public void process(Request request, Response response) throws IOException {

// This is the string interception method above

String servletName = this.parseServletName(request.getUri());

// Load the Servlet with URLClassLoader and instantiate it

Class servletClass = = URL_CLASS_LOADER.loadClass(servletName);

Servlet servlet = (Servlet) servletClass.newInstance();

response.getWriter().println(new String(response.responseToByte(HttpStatusEnum.OK)));

// Call the servlet's service method

servlet.service(request,response);

}

Copy the code

The penultimate line of code that calls the Response.PrintWriter object sends a Response header to the browser (chrome would consider the Response invalid if it didn’t). The servlet output is invisible

)

ServletProcess roughly calls the flow:

3. Prepare a custom Servlet

Now that the Servlet container is ready, let’s experiment with a Servlet

public class TestServlet implements Servlet {

public void init(ServletConfig config) throws ServletException {

}

public ServletConfig getServletConfig(a) {

return null;

}

public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {

System.out.println("Start invoke TestServlet ... ");

res.getWriter().println("Hello Servlet!");

}

public String getServletInfo(a) {

return null;

}

public void destroy(a) {

}

}

Copy the code

It simply outputs a record to the console and a message back to the browser (we’ll do that in the next few chapters), files the class into a class file, throws it into our resource/webroot/servlet folder, and opens the browser and goes:

Done! No… There are serious flaws in the above design

Enhance Request and Response security

  • Where are the defects

Careful brothers must have found: When we call our user-defined servlet in the ServletProcess, We pass Request/Response directly to the user’s service method (because our reuqest /Response implements the ServletRequest/ServletResponse interface). So if our Tomcat is distributed to others, the servlet of someone who has read our Tomcat source code could write:

public class TestServlet {

public void service(HttpServletRequest request,HttpServletResponse response){

((Request)request).parseRequest("");

((Response)response).accessStaticResources();

}

}

Copy the code

The above two methods are designed to be used by process and others (so methods can’t be made private), not to be called by users, which breaks encapsulation!!

  • The solution

    Have seen or read Tomcat source code, find the Tomcat has been in use for a design pattern to solve the defects, is a design pattern (facade design pattern), the specific design patterns you can go to search out, here we also refer to this design pattern to deal with this defect, relationship between UML class diagram is as follows:



The code is also very simple to call the corresponding method of the internal Request object:

public class RequestFacade implements ServletRequest{

private Request request;

@Override

public Object getAttribute(String name) {

return request.getAttribute(name);

}

Other implementations are similar...

}

Copy the code

We wrap the Facade class when the ServletProcess method calls the servlet:

.

Servlet servlet = (Servlet) servletClass.newInstance();

servlet.service(new RequestFacade(request), new ResponseFacade(response));

.

Copy the code

That’s it!

The user might only have the ServletRequest/ServletResponse transformation for RequestFacade/ResponseFacade downward

But we don't provide the getReuqest()/getResponse() methods, so it can call the corresponding ServletRequest,

Methods defined by the ServletResponse interface so that our internal methods are not called by the user

Copy the code

At this point, our Tomcat 2.0 Web server has been developed (funny Face) and can implement simple custom Servlet calls, but many features are still incomplete:

- Every time you asknewA Servlet, which should be initialized when the project is initialized, is singleton.

- Not following the Servlet specification to implement the corresponding lifecycle, such as init()/destory() methods we did not call.

- ServletRequest/ServletResponse interface methods we still unfulfilled

- Other unimplemented functions

Copy the code

In the next section we will implement Request to parse parameters, HTTPHeader, cookies and other parameters and reconstruct the schema:

Parsing Request parameters, Request headers, cookies

PS: The source of this chapter has been uploaded to Github SimpleTomcat