Chapter 2: A Simple Servlet Container

Overview

This chapter explains how you can develop your own servlet container by presenting two applications. The first application has been designed to be as simple as possible to make it easy for you to understand how a servlet container works. It then evolves into the second servlet container, which is slightly more complex.

本章将通过介绍两个应用程序的方式,解释如何开发自己的Servlet容器。第一个应用程序被设计得尽可能简单,以便让您更容易理解Servlet容器的工作原理。然后,它演变成稍微复杂一些的第二个Servlet容器。

Note Every servlet container application in each chapter gradually evolves from the application in the previous chapter, until a fully-functional Tomcat servlet container is built in Chapter 17.

每个章节中的Servlet容器应用程序都会逐渐从前一章节的应用程序演化而来,直到在第17章建立一个完全功能的Tomcat Servlet容器。

Both servlet containers can process simple servlets as well as static resources. You can use PrimitiveServlet to test this container. PrimitiveServlet is given in Listing 2.1 and its class file can be found in the webroot directory. More complex servlets are beyond the capabilities of these containers, but you will learn how to build more sophisticated servlet containers in the next chapters.

两个Servlet容器都可以处理简单的Servlet和静态资源。您可以使用PrimitiveServlet来测试这个容器。PrimitiveServlet在2.1节中给出,其类文件可以在webroot目录中找到。更复杂的Servlet超出了这些容器的能力范围,但您将在接下来的章节中学习如何构建更复杂的Servlet容器。

Listing 2.1: PrimitiveServlet.java

第2.1节:PrimitiveServlet.java

import javax.servlet.*;

import java.io.IOException;

import java.io.PrintWriter;

public class PrimitiveServlet implements Servlet {

    public void init(ServletConfig config) throws ServletException {
    
        System.out.println("init");
    
    }
    
    public void service(ServletRequest request, ServletResponse response)
    
    throws ServletException, IOException {
    
        System.out.println("from service");
        
        PrintWriter out = response.getWriter();
        
        out.println("Hello. Roses are red.");
        
        out.print("Violets are blue.");
    
    }
    
    public void destroy() {
    
        System.out.println("destroy");
    
    } public String getServletInfo() {
    
        return null;
    
    }
    
    public ServletConfig getServletConfig() {
    
        return null;
    
    }

}

The classes for both applications are part of the ex02.pyrmont package. To understand how the applications work, you need to be familiar with the javax.servlet.Servlet interface. To refresh your memory, this interface is discussed in the first section of this chapter. After that, you will learn what a servlet container has to do to serve HTTP requests for a servlet.

这两个应用程序的类都属于ex02.pyrmont包。

要理解这些应用程序的工作原理,您需要熟悉javax.servlet.Servlet接口。为了帮助您回忆,该接口在本章的第一节中有所介绍。

之后,您将了解一个servlet容器为了为servlet提供HTTP请求而必须完成的工作。

The javax.servlet.Servlet Interface

Servlet programming is made possible through the classes and interfaces in two packages: javax.servlet and javax.servlet.http. Of those classes and interfaces, the javax.servlet.Servlet interface is of the utmost importance. All servlets must implement this interface or extend a class that does.

Servlet 编程是通过以下两个软件包中的类和接口实现的:javax.servletjavax.servlet.http

在这些类和接口中,javax.servlet.Servlet 接口最为重要。所有 servlet 都必须实现该接口或扩展一个实现该接口的类。

The Servlet interface has five methods whose signatures are as follows.

Servlet 接口有五个方法,其签名如下。

public void init(ServletConfig config) throws ServletException

public void service(ServletRequest request, ServletResponse response)

throws ServletException, java.io.IOException

public void destroy()

public ServletConfig getServletConfig()

public java.lang.String getServletInfo()

Of the five methods in Servlet, the init, service, and destroy methods are the servlet's life cycle methods. The init method is called by the servlet container after the servlet class has been instantiated. The servlet container calls this method exactly once to indicate to the servlet that the servlet is being placed into service. The init method must complete successfully before the servlet can receive any requests. A servlet programmer can override this method to write initialization code that needs to run only once, such as loading a database driver, initializing values, and so on. In other cases, this method is normally left blank.

在Servlet中有五种方法,其中initservicedestroy方法是servlet的生命周期方法。init方法在servlet类被实例化后由servlet容器调用。

servlet容器只会调用一次这个方法,以向servlet表示将servlet放入服务中。在servlet接收任何请求之前,init方法必须成功完成。

程序员可以重写此方法来编写只需运行一次的初始化代码,例如加载数据库驱动程序、初始化值等。在其他情况下,通常将此方法保留为空白。

The servlet container calls the service method of a servlet whenever there is a request for the servlet. The servlet container passes a javax.servlet.ServletRequest object and a javax.servlet.ServletResponse object. The ServletRequest object contains the client's HTTP request information and the ServletResponse object encapsulates the servlet's response. The service method is invoked many times during the life of the servlet.

每当有一个对servlet的请求时,servlet容器会调用servlet的service方法。servlet容器传递一个javax.servlet.ServletRequest对象和一个javax.servlet.ServletResponse对象。

ServletRequest对象包含客户端的HTTP请求信息,而ServletResponse对象封装了servlet的响应。

在servlet的生命周期中,service方法会被多次调用。

The servlet container calls the destroy method before removing a servlet instance from service. This normally happens when the servlet container is shut down or the servlet container needs some free memory. This method is called only after all threads within the servlet's service method have exited or after a timeout period has passed. After the servlet container has called the destroy method, it will not call the service method again on the same servlet. The destroy method gives the servlet an opportunity to clean up any resources that are being held, such as memory, file handles, and threads, and make sure that any persistent state is synchronized with the servlet's current state in memory.

Servlet容器在从服务中移除一个servlet实例之前会调用destroy方法。通常情况下,这会在Servlet容器关闭或需要释放一些内存时发生。

该方法只会在servlet的service方法中的所有线程退出或超时期过后才会被调用。

在Servlet容器调用destroy方法之后,它将不会再次调用同一servlet的service方法。

destroy方法为servlet提供了清理任何正在使用的资源的机会,例如内存、文件句柄和线程,并确保任何持久状态与servlet在内存中的当前状态同步。

Listing 2.1 presents the code for a servlet named PrimitiveServlet, which is a very simple servlet that you can use to test the servlet container applications in this chapter. The PrimitiveServlet class implements javax.servlet.Servlet (as all servlets must) and provides implementations for all the five methods of Servlet. What PrimitiveServlet does is very simple. Each time any of the init, service, or destroy methods is called, the servlet writes the method's name to the standard console. In addition, the service method obtains the java.io.PrintWriter object from the ServletResponse object and sends strings to the browser

代码清单2.1展示了一个名为PrimitiveServlet的Servlet的代码,它是一个非常简单的Servlet,您可以用来测试本章中的Servlet容器应用程序。

PrimitiveServlet 类实现了javax.servlet.Servlet(所有Servlet都必须实现的接口),并为Servlet的所有五个方法提供了实现。PrimitiveServlet 的功能非常简单。

每次调用init、service或destroy方法时,Servlet都会将方法名写入标准控制台。此外,service方法从 ServletResponse 对象中获取java.io.PrintWriter对象,并将字符串发送到浏览器。

Application 1

Now, let's examine servlet programming from a servlet container's perspective. In a nutshell, a fully-functional servlet container does the following for each HTTP request for a servlet:

现在,让我们从Servlet容器的角度来看待Servlet编程。简而言之,一个完全功能的Servlet容器对于每个HTTP请求的Servlet会执行以下操作:

  • When the servlet is called for the first time, load the servlet class and call the servlet's init method (once only)
  • For each request, construct an instance of javax.servlet.ServletRequest and an instance of javax.servlet.ServletResponse.
  • Invoke the servlet's service method, passing the ServletRequest and ServletResponse objects.
  • When the servlet class is shut down, call the servlet's destroy method and unload the servlet class.
  • 当第一次调用Servlet时,加载Servlet类并调用Servlet的init方法(仅一次)
  • 对于每个请求,构造一个javax.servlet.ServletRequest实例和一个javax.servlet.ServletResponse实例。
  • 调用Servlet的service方法,传递ServletRequestServletResponse对象。
  • 当Servlet类关闭时,调用Servletdestroy方法并卸载Servlet类。

The first servlet container for this chapter is not fully functional. Therefore, it cannot run other than very simple servlets and does not call the servlets' init and destroy methods. Instead, it does the following:

本章的第一个Servlet容器功能并不完全。因此,它只能运行非常简单的Servlet,并且不会调用Servlet的init和destroy方法。相反,它会执行以下操作:

  • Wait for HTTP requests.
  • Construct a ServletRequest object and a ServletResponse object.
  • If the request is for a static resource, invoke the process method of the StaticResourceProcessor instance, passing the ServletRequest and ServletResponse objects.
  • If the request is for a servlet, load the servlet class and invoke the service method of the servlet, passing the ServletRequest and ServletResponse objects.
  • 等待HTTP请求。
  • 构造一个ServletRequest对象和一个ServletResponse对象。
  • 如果请求是针对静态资源的,则调用StaticResourceProcessor实例的process方法,传递ServletRequest和ServletResponse对象。
  • 如果请求是针对Servlet的,则加载Servlet类并调用Servlet的service方法,传递ServletRequest和ServletResponse对象。

Note In this servlet container, the servlet class is loaded every time the servlet is requested.

在这个Servlet容器中,每次请求Servlet时都会加载Servlet类。

The first application consists of six classes:

第一个应用程序由六个类组成:

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

Figure 2.1 displays the UML diagram of the first servlet container

图 2.1 显示了第一个 servlet 容器的 UML 图

image.png

The entry point of this application (the static main method) is in the HttpServer1 class. The main method creates an instance of HttpServer1 and calls its await method. The await method waits for HTTP requests, creates a Request object and a Response object for every request, and dispatch them either to a StaticResourceProcessor instance or a ServletProcessor instance, depending on whether the request is for a static resource or a servlet.

这个应用程序的入口点(静态的main方法)位于HttpServer1类中。main方法创建了一个HttpServer1的实例,并调用它的await方法。

await方法等待HTTP请求,为每个请求创建一个Request对象和一个Response对象,并根据请求是静态资源还是servlet,将它们分发给StaticResourceProcessor实例或ServletProcessor实例。

The Constants class contains the static final WEB_ROOT that is referenced from other classes. WEB_ROOT indicates the location of PrimitiveServlet and the static resource that can be served by this container.

常量类包含静态最终 WEB_ROOT,可被其他类引用。WEB_ROOT 表示 PrimitiveServlet 的位置和该容器可提供的静态资源。

The HttpServer1 instance keeps waiting for HTTP requests until a shutdown command is received. You issue a shutdown command the same way as you did it in Chapter 1.

HttpServer1 实例会一直等待 HTTP 请求,直到收到关闭命令。发出关闭命令的方法与第 1 章中相同。

Each of the classes in the application is discussed in the following sections.

下文将讨论应用程序中的每个类。

The HttpServer1 Class

The HttpServer1 class in this application is similar to the HttpServer class in the simple web server application in Chapter 1. However, in this application the HttpServer1 class can serve both static resources and servlets. To request a static resource, you type a URL in the following format in your browser's Address or URL box:

这个应用程序中的HttpServer1类类似于第1章中简单Web服务器应用程序中的HttpServer类。然而,在这个应用程序中,HttpServer1类既可以提供静态资源,也可以提供servlet。要请求静态资源,请在浏览器的地址或URL框中键入以下格式的URL:

http://machineName:port/staticResource

This is exactly how you requested a static resource in the web server application in Chapter 1.

这正是第 1 章中在网络服务器应用程序中请求静态资源的方式。

To request a servlet, you use the following URL:

要请求一个 servlet,需要使用以下 URL:

http://machineName:port/servlet/servletClass

Therefore, if you are using a browser locally to request a servlet called PrimitiveServlet, you enter the following URL in the browser's Address or URL box:

因此,如果您在本地使用浏览器请求名为 PrimitiveServlet 的 servlet,您可以在浏览器的 "地址 "或 "URL "框中输入以下 URL:

http://localhost:8080/servlet/PrimitiveServlet

This servlet container can serve PrimitiveServlet. However, if you invoke the other servlet, ModernServlet, the servlet container will throw an exception. At the later chapters, you will build applications that can process both.

这个 servlet 容器可以为 PrimitiveServlet 提供服务。但是,如果调用另一个 servlet(ModernServlet),该 servlet 容器就会抛出异常。在后面的章节中,你将构建能同时处理这两种程序的应用程序。

The HttpServer1 class is presented in Listing 2.2.

HttpServer1 类如清单 2.2 所示。

Listing 2.2: The HttpServer1 Class's await method

清单 2.2: HttpServer1 类的 await 方法

  
package ex02.pyrmont;  
        import java.net.Socket;  
        import java.net.ServerSocket;  
        import java.net.InetAddress;  
        import java.io.InputStream;  
        import java.io.OutputStream;  
        import java.io.IOException;  
public class HttpServer1 {  
    /** WEB_ROOT is the directory where our HTML and other files reside.  
     * For this package, WEB_ROOT is the "webroot" directory under the     * working directory.     * The working directory is the location in the file system     * from where the java command was invoked.     */    // shutdown command  
    private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";  
    // the shutdown command received  
    private boolean shutdown = false;  
    public static void main(String[] args) {  
        HttpServer1 server = new HttpServer1();  
        server.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 {  
                    StaticResoureProcessor 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);  
            }  
        }  
    }  
}

The class's await method waits for HTTP requests until a shutdown command is issued, and reminds you of the await method in Chapter 1. The difference between the await method in Listing 2.2 and the one in Chapter 1 is that in Listing 2.2 the request can be dispatched to either a StaticResourceProcessor or a ServletProcessor. The request is forwarded to the latter if the URI contains the string /servlet/.

该类的await方法会等待HTTP请求,直到发出关闭命令,并提醒您第一章中的await方法。Listing 2.2中await方法与第一章中的方法的区别在于,在Listing 2.2中,请求可以被分派到StaticResourceProcessor或ServletProcessor。如果URI包含字符串/servlet/,则该请求将被转发到ServletProcessor。

Otherwise, the request is passed to the StaticResourceProcessor instance. Notice that that part is greyed in Listing 2.2.

否则,该请求将传递给StaticResourceProcessor实例。请注意,Listing 2.2中的这部分是灰色的。

The Request Class

A servlet's service method receives a javax.servlet.ServletRequest instance and a javax.servlet.ServletResponse instance from the servlet container. This is to say that for every HTTP request, a servlet container must construct a ServletRequest object and a ServletResponse object and pass them to the service method of the servlet it is serving.

一个servlet的service方法从servlet容器接收一个javax.servlet.ServletRequest实例和一个javax.servlet.ServletResponse实例。

这意味着对于每个HTTP请求,servlet容器必须构造一个ServletRequest对象和一个ServletResponse对象,并将它们传递给正在提供服务的servlet的service方法。

The ex02.pyrmont.Request class represents a request object to be passed to the servlet's service method. As such, it must implement the javax.servlet.ServletRequest interface. This class has to provide implementations for all methods in the interface. However, we would like to make it very simple and provide the implementations of some of the methods only we leave the full method implementations for the chapters to come. In order to compile the Request class, you need to provide "blank" implementations for those methods. If you look at the Request class in Listing 2.3, you will see that all methods whose signatures return an object instance return a null.

ex02.pyrmont.Request类表示要传递给servlet的service方法的请求对象。

因此,它必须实现javax.servlet.ServletRequest接口。该类必须为接口中的所有方法提供实现。然而,我们希望它非常简单,并且只提供一些方法的实现,而将完整的方法实现留给后面的章节。

为了编译Request类,您需要为这些方法提供“空白”的实现。如果您查看清单2.3中的Request类,您会发现所有返回对象实例的方法的签名都返回null。

Listing 2.3: The Request class

清单 2.3:请求类

  
package ex02.pyrmont;  
        import java.io.InputStream;  
        import java.io.IOException;  
        import java.io.BufferedReader;  
        import java.io.UnsupportedEncodingException;  
        import java.util.Enumeration;  
        import java.util.Locale;  
        import java.util.Map;  
        import javax.servlet.RequestDispatcher;  
        import javax.servlet.ServletInputStream;  
        import javax.servlet.ServletRequest;  
  
public class Request implements ServletRequest {  
    private InputStream input;  
    private String uri;  
  
    public Request(InputStream input) {  
        this.input = input;  
    }  
  
    public String getUri() {  
        return uri;  
    }  
  
    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;  
    }  
  
    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());  
    }  
  
    /* implementation of ServletRequest */  
    public Object getAttribute(String attribute) {  
        return null;  
    }  
  
    public Enumeration getAttributeNames() {  
        return null;  
    }  
  
    public String getRealPath(String path) {  
        return null;  
    }  
  
    public RequestDispatcher getRequestDispatcher(String path) {  
        return null;  
    }  
  
    public boolean isSecure() {  
        return false;  
    }  
  
    public String getCharacterEncoding() {  
        return null;  
    }  
  
    public int getContentLength() {  
        return 0;  
    }  
  
    public String getContentType() {  
        return null;  
    }  
  
    public ServletInputStream getInputStream() throws IOException {  
        return null;  
    }  
  
    public Locale getLocale() {  
        return null;  
    }  
  
    public Enumeration getLocales() {  
        return null;  
    }  
  
    public String getParameter(String name) {  
        return null;  
    }  
  
    public Map getParameterMap() {  
        return null;  
    }  
  
    public Enumeration getParameterNames() {  
        return null;  
    }  
  
    public String[] getParameterValues(String parameter) {  
        return null;  
    }  
  
    public String getProtocol() {  
        return null;  
    }  
  
    public BufferedReader getReader() throws IOException {  
        return null;  
    }  
  
    public String getRemoteAddr() {  
        return null;  
    }  
  
    public String getRemoteHost() {  
        return null;  
    }  
  
    public String getScheme() {  
        return null;  
    }  
  
    public String getServerName() {  
        return null;  
    }  
  
    public int getServerPort() {  
        return 0;  
    }  
  
    public void removeAttribute(String attribute) {  
    }  
  
    public void setAttribute(String key, Object value) {  
    }  
  
    public void setCharacterEncoding(String encoding)  
            throws UnsupportedEncodingException {  
    }  
}

In addition, the Request class still has the parse and the getUri methods which were discussed in Chapter 1.

此外,Request 类仍具有第 1 章讨论过的 parse 和 getUri 方法。

The Response Class 响应类

The ex02.pyrmont.Response class, given in Listing 2.4, implements javax.servlet.ServletResponse. As such, the class must provide implementations for all the methods in the interface. Similar to the Request class, we leave the impl ementations of all methods "blank", except for the getWriter method.

清单 2.4 中的 ex02.pyrmont.Response 类实现了 javax.servlet.ServletResponse。因此,该类必须为接口中的所有方法提供实现。与 Request 类类似,除了 getWriter 方法外,我们将所有方法的实现都留为 "空白"。

Listing 2.4: The Response class

清单 2.4:Response 类

  
   package ex02.pyrmont;  
            import java.io.OutputStream;  
            import java.io.IOException;  
            import java.io.FileInputStream;  
            import java.io.FileNotFoundException;  
            import java.io.File;  
            import java.io.PrintWriter;  
            import java.util.Locale;  
            import javax.servlet.ServletResponse;  
            import javax.servlet.ServletOutputStream;  
public class Response implements ServletResponse {  
    private static final int BUFFER_SIZE = 1024;  
    Request request;  
    OutputStream output;  
    PrintWriter writer;  
    public Response(OutputStream output) {  
        this.output = output;  
    }  
    public void setRequest(Request request) {  
        this.request = request;  
    }  
    /* This method is used to serve static pages */  
    public void sendStaticResource() throws IOException {  
        byte[] bytes = new byte[BUFFER_SIZE];  
        FileInputstream fis = null;  
        try {  
            /* request.getUri has been replaced by request.getRequestURI */  
            File file = new File(Constants.WEB_ROOT, request.getUri());  
            fis = new FileInputstream(file);  
    /*  
    HTTP Response = Status-Line    *(( general-header | response-header | entity-header ) CRLF)    CRLF    [ message-body ]    Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF    */            int ch = fis.read(bytes, 0, BUFFER_SIZE);  
            while (ch!=-1) {  
                output.write(bytes, 0, ch);  
                ch = fis.read(bytes, 0, BUFFER_SIZE);  
            }  
        }  
        catch (FileNotFoundException e) {  
            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());  
        }  
        finally {  
            if (fis!=null)  
                fis.close();  
        }  
    }  
    /** implementation of ServletResponse */  
    public void flushBuffer() throws IOException ( }  
    public int getBufferSize() {  
        return 0;  
    }  
    public String getCharacterEncoding() {  
        return null;  
    }  
    public Locale getLocale() {  
        return null;  
    }  
    public ServletOutputStream getOutputStream() throws IOException {  
        return null;  
    }  
    public PrintWriter getWriter() throws IOException {  
        // autoflush is true, println() will flush,  
        // but print() will not.        writer = new PrintWriter(output, true);  
        return writer;  
    }  
    public boolean isCommitted() {  
        return false;  
    }  
    public void reset() { }  
    public void resetBuffer() { }  
    public void setBufferSize(int size) { }  
    public void setContentLength(int length) { }  
    public void setContentType(String type) { }  
    public void setLocale(Locale locale) { }  
}

In the getWriter method, the second argument to the PrintWriter class's constructor is a boolean indicating whether or not autoflush is enabled. Passing true as the second argument will make any call to a println method flush the output. However, a print method does not flush the output.

在getWriter方法中,PrintWriter类构造函数的第二个参数是一个布尔值,用于指示是否启用自动刷新。将true作为第二个参数传递将使任何对println方法的调用刷新输出。然而,print方法不会刷新输出。

Therefore, if a call to a print method happens to be the last line in a servlet's service method, the output will not be sent to the browser. This imperfection will be fixed in the later applications.

因此,如果在servlet的service方法中,调用打印方法恰好是最后一行代码,输出将不会发送到浏览器。这个缺陷将在后续的应用程序中修复。

The Response class still has the sendStaticResource method discussed in Chapter 1.

Response 类仍具有第 1 章中讨论的 sendStaticResource 方法。

The StaticResourceProcessor Class(静态资源处理器类)

The ex02.pyrmont.StaticResourceProcessor class is used to serve requests for static resources. The only method it has is the process method. Listing 2.5 offers the StaticResourceProcessor class.

ex02.pyrmont.StaticResourceProcessor 类用于处理静态资源请求。该类唯一的方法是 process 方法。清单 2.5 提供了 StaticResourceProcessor 类。

Listing 2.5: The StaticResourceProcessor class

清单 2.5:静态资源处理器类

package ex02.pyrmont;
import java.io.IOException;
public class StaticResourceProcessor {
     public void process(Request request, Response response) {
     try {
         response.sendStaticResource();
     }
     catch (IOException e) {
         e.printStackTrace();
     }
 }
}

The process method receives two arguments: an ex02.pyrmont.Request instance and an ex02.pyrmont.Response instance. This method simply calls the sendStaticResource method on the Response object.

process 方法接收两个参数:一个 ex02.pyrmont.Request 实例和一个 ex02.pyrmont.Response 实例。这个方法只需在 Response 对象上调用 sendStaticResource 方法。

The ServletProcessor1 Class

The ex02.pyrmont.ServletProcessor1 class in Listing 2.6 is there to process HTTP requests for servlets.

清单 2.6 中的 ex02.pyrmont.ServletProcessor1 类用于处理 Servlet 的 HTTP 请求。

Listing 2.6: The ServletProcessor1 class

清单 2.6:ServletProcessor1 类

package ex02.pyrmont;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLStreamHandler;
import java.io.File;
import java.io.IOException;
import javax.servlet.Servlet;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class ServletProcessor1 {
     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.
             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((ServletRequest) request,
             (ServletResponse) response);
         }
         catch (Exception e) {
             System.out.println(e.toString());
         }
         catch (Throwable e) {
             System.out.println(e.toString());
         }
 }
}

The ServletProcessor1 class is surprisingly simple, consisting only of one method: process. This method accepts two arguments: an instance of javax.servlet.ServletRequest and an instance of javax.servlet.ServletResponse. From the ServletRequest, the method obtains the URI by calling the getRequestUri method:

ServletProcessor1类非常简单,只包含一个方法:process。该方法接受两个参数:javax.servlet.ServletRequest的一个实例和javax.servlet.ServletResponse的一个实例。从ServletRequest中,该方法通过调用getRequestUri方法获取URI。

String uri = request.getUri();

Remember that the URI is in the following format:

请记住,URI 的格式如下:

/servlet/servletName

where servletName is the name of the servlet class.

其中 servletName 是 servlet 类的名称。

To load the servlet class, we need to know the servlet name from the URI. We can get the servlet name using the next line of the process method:

要加载 servlet 类,我们需要知道 URI 中的 servlet 名称。我们可以通过 process 方法的下一行获取 servlet 名称:

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

Next, the process method loads the servlet. To do this, you need to create a class loader and tell this class loader the location to look for the class to be loaded. For this servlet container, the class loader is directed to look in the directory pointed by Constants.WEB_ROOT, which points to the webroot directory under the working directory.

接下来,处理方法会加载 servlet。为此,需要创建一个类加载器,并告诉该类加载器要加载类的位置。在此 servlet 容器中,类加载器将在 Constants.WEB_ROOT 指向的目录中查找,而 Constants.WEB_ROOT 指向的是工作目录下的 webroot 目录。

Note Class loaders are discussed in detail in Chapter 8.

注意:类加载器在第8章中有详细讨论。

To load a servlet, you use the java.net.URLClassLoader class, which is an indirect child class of the java.lang.ClassLoader class. Once you have an instance of URLClassLoader, you use its loadClass method to load a servlet class. Instantiating the URLClassLoader class is straightforward. This class has three constructors, the simplest of which being:

要加载一个servlet,您使用的是java.net.URLClassLoader类,它是java.lang.ClassLoader类的间接子类。

一旦您有了URLClassLoader的实例,您可以使用它的loadClass方法来加载一个servlet类。实例化URLClassLoader类很简单。

这个类有三个构造方法,其中最简单的是:

public URLClassLoader(URL[] urls);

where urls is an array of java.net.URL objects pointing to the locations on which searches will be conducted when loading a class. Any URL that ends with a / is assumed to refer to a directory. Otherwise, the URL is assumed to refer to a JAR file, which will be downloaded and opened as needed.

urls是一个java.net.URL对象的数组,指向在加载类时进行搜索的位置。任何以/结尾的URL都被认为是指向一个目录。否则,URL被认为是指向一个JAR文件,将根据需要下载并打开。

Note In a servlet container, the location where a class loader can find servlet classes is called a repository.

注意 在 servlet 容器中,类加载器可以找到 servlet 类的位置称为资源库。

In our application, there is only one location that the class loader must look, i.e. the webroot directory under the working directory. Therefore, we start by creating an array of a single URL. The URL class provides a number of constructors, so there are many ways of constructing a URL object. For this application, we used the same constructor used in another class in Tomcat. The constructor has the following signature.

在我们的应用程序中,只有一个位置是类加载器必须查找的,即工作目录下的webroot目录。因此,我们首先创建一个只包含一个URL的数组。

URL类提供了许多构造函数,因此有多种构造URL对象的方式。

对于这个应用程序,我们使用了Tomcat中另一个类中使用的相同构造函数。该构造函数具有以下签名。

public URL(URL context, java.lang.String spec, URLStreamHandler hander) throws MalformedURLException

You can use this constructor by passing a specification for the second argument and null for both the first and the third arguments. However, there is another constructor that accepts three arguments:

只要为第二个参数传递一个规范,为第一个和第三个参数传递空值,就可以使用这个构造函数。

不过,还有一种构造函数可以接受三个参数:

public URL(java.lang.String protocol, java.lang.String host, java.lang.String file) throws MalformedURLException

Therefore, the compiler will not know which constructor you mean if you simply write the following code:

因此,如果您只编写以下代码,编译器将无法知道您指的是哪个构造函数:

new URL(null, aString, null);

You can get around this by telling the compiler the type of the third argument, like this

要解决这个问题,可以告诉编译器第三个参数的类型,如下所示

URLStreamHandler streamHandler = null; new URL(null, aString, streamHandler);

For the second argument, you pass a String containing the repository (the directory where servlet classes can be found), which you form by using the following code:

第二个参数是一个包含存储库(可找到 servlet 类的目录)的字符串,使用下面的代码将其生成:

String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString() ;

Combining all the pieces together, here is the part of the process method that constructs the appropriate URLClassLoader instance:

将所有部分组合在一起,下面是流程方法中构建相应 URLClassLoader 实例的部分:

// create a URLClassLoader
 URL[] urls = new URL[1];
 URLStreamHandler streamHandler = null;
 File classPath = new File(Constants.WEB_ROOT);
 String repository = (new URL("file", null,
 classPath.getCanonicalPath() + File.separator)).toString() ;
 urls[0] = new URL(null, repository, streamHandler);
 loader = new URLClassLoader(urls);

Note The code that forms the repository is taken from the createClassLoader method in org.apache.catalina.startup.ClassLoaderFactory and the code for forming the URL is taken from the addRepository method in org.apache.catalina.loader.StandardClassLoader. However, you don't have to worry about these classes until the later chapters.

注意 形成版本库的代码来自 org.apache.catalina.startup.ClassLoaderFactory 中的 createClassLoader 方法,而形成 URL 的代码来自 org.apache.catalina.loader.StandardClassLoader 中的 addRepository 方法。不过,在后面的章节中才需要关注这些类。

Having a class loader, you can load a servlet class using the loadClass method:

有了类加载器,就可以使用 loadClass 方法加载 servlet 类:

Class myClass = null;
 try {
 myClass = loader.loadClass(servletName);
 }
 catch (ClassNotFoundException e) {
 System.out.println(e.toString());
 }

Next, the process method creates an instance of the servlet class loaded, downcasts it to javax.servlet.Servlet, and invokes the servlet's service method:

接下来,处理方法会创建一个已加载的 servlet 类实例,并将其下推至 javax.servlet.Servlet,然后调用 servlet 的服务方法:

Servlet servlet = null;
 try {
     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());
     }

Running the Application

To run the application on Windows, type the following command from the working directory: java -classpath ./lib/servlet.jar;./ ex02.pyrmont.HttpServer1 In Linux, you use a colon to separate two libraries: java -classpath ./lib/servlet.jar:./ ex02.pyrmont.HttpServer1 To test the application, type the following in your URL or Address box of your browser: http://localhost:8080/index.html or http://localhost:8080/servlet/PrimitiveServlet When invoking PrimitiveServlet, you will see the following text in your browser: Hello. Roses are red.

在Windows上运行该应用程序,从工作目录中键入以下命令:

java -classpath ./lib/servlet.jar;./ ex02.pyrmont.HttpServer1

在Linux中,使用冒号来分隔两个库:

java -classpath ./lib/servlet.jar:./ ex02.pyrmont.HttpServer1

要测试该应用程序,请在浏览器的URL或地址栏中键入以下内容:

http://localhost:8080/index.html

http://localhost:8080/servlet/PrimitiveServlet

在调用PrimitiveServlet时,您将在浏览器中看到以下文本:Hello. Roses are red.

Note that you cannot see the second string Violets are blue, because only the first string is flushed to the browser. We will fix this problem in Chapter 3, though

请注意,您看不到第二个字符串 "紫罗兰是蓝色的",因为只有第一个字符串被刷新到浏览器中。我们将在第 3 章中解决这个问题。

Application 2

There is a serious problem in the first application. In the ServletProcessor1 class's process method, you upcast the instance of ex02.pyrmont.Request to javax.servlet.ServletRequest and pass it as the first argument to the servlet's service method. You also upcast the instance of ex02.pyrmont.Response to javax.servlet.ServletResponse and pass it as the second argument to the servlet's service method.

第一个应用程序存在一个严重的问题。在ServletProcessor1类的process方法中,你将ex02.pyrmont.Request的实例向上转型为javax.servlet.ServletRequest,并将其作为第一个参数传递给servlet的service方法。你还将ex02.pyrmont.Response的实例向上转型为javax.servlet.ServletResponse,并将其作为第二个参数传递给servlet的service方法。

try {
 servlet = (Servlet) myClass.newInstance();
 servlet.service((ServletRequest) request,
 (ServletResponse) response);
 }

This compromises security. Servlet programmers who know the internal workings of this servlet container can downcast the ServletRequest and ServletResponse instances back to ex02.pyrmont.Request and ex02.pyrmont.Response respectively and call their public methods. Having a Request instance, they can call its parse method. Having a Response instance, they can call its sendStaticResource method.

这样会存在安全隐患。了解此servlet容器内部工作原理的Servlet程序员可以将ServletRequest和ServletResponse实例向下转换为ex02.pyrmont.Request和ex02.pyrmont.Response,并调用它们的公共方法。拥有Request实例,他们可以调用其parse方法。拥有Response实例,他们可以调用其sendStaticResource方法。

You cannot make the parse and sendStaticResource methods private because they will be called from other classes. However, these two methods are not supposed to be available from inside a servlet. One solution is to make both Request and Response classes have default access modifier, so that they cannot be used from outside the ex02.pyrmont package. However, there is a more elegant solution: by using facade classes. See the UML diagram in Figure 2.2.

你不能将parse和sendStaticResource方法设为私有,因为其他类将调用它们。然而,这两个方法不应该在servlet内部可用。

一个解决方案是将Request和Response类的访问修饰符设置为默认,这样它们就不能从ex02.pyrmont包外部使用。

然而,还有一个更优雅的解决方案:使用外观类。请参见图2.2中的UML图。

image.png

Figure 2.2: Façade classes

图 2.2: 外观类

In this second application, we add two façade classes: RequestFacade and ResponseFacade.RequestFacade implements the ServletRequest interface and is instantiated by passing a Request instance that it assigns to a ServletRequest object reference in its constructor. Implementation of each method in the ServletRequest interface invokes the corresponding method of the Request object. However, the ServletRequest object itself is private and cannot be accessed from outside the class. Instead of upcasting the Request object to ServletRequest and passing it to the service method, we construct a RequestFacade object and pass it to the service method. Servlet programmers can still downcast the ServletRequest instance back to RequestFacade, however they can only access the methods available in the ServletRequest interface. Now, the parseUri method is safe.

在这个第二个应用程序中,我们添加了两个外观类:RequestFacade和ResponseFacade。RequestFacade实现了ServletRequest接口,并通过传递一个Request实例来实例化,该实例在其构造函数中被分配给一个ServletRequest对象引用。

ServletRequest接口中的每个方法的实现都会调用Request对象的相应方法。

然而,ServletRequest对象本身是私有的,无法从类外部访问。

我们不再将Request对象向上转型为ServletRequest并传递给service方法,而是构造一个RequestFacade对象并将其传递给service方法。

Servlet程序员仍然可以将ServletRequest实例向下转型为RequestFacade,但是他们只能访问ServletRequest接口中可用的方法。

现在,parseUri方法是安全的。

Listing 2.7 shows an incomplete RequestFacade class.

清单 2.7 显示了一个不完整的 RequestFacade 类。

Listing 2.7: The RequestFacade class

清单 2.7:RequestFacade 类

package ex02.pyrmont;
public class RequestFacade implements ServletRequest {
 private ServleLRequest request = null;
 public RequestFacade(Request request) {
 this.request = request;
 }
 /* implementation of the ServletRequest*/
 public Object getAttribute(String attribute) {
 return request.getAttribute(attribute);
 }
 public Enumeration getAttributeNames() {
 return request.getAttributeNames();
 }
 ...
}

Notice the constructor of RequestFacade. It accepts a Request object but immediately assigns it to the private servletRequest object reference. Notice also each method in the RequestFacade class invokes the corresponding method in the ServletRequest object.

请注意 RequestFacade 的构造函数。它接受一个 Request 对象,但立即将其赋值给私有的 servletRequest 对象引用。

还要注意 RequestFacade 类中的每个方法都会调用 ServletRequest 对象中的相应方法。

The same applies to the ResponseFacade class.

这同样适用于 ResponseFacade 类。

Here are the classes used in Application 2:

以下是应用程序 2 中使用的类:

  • HttpServer2
  • Request
  • Response
  • StaticResourceProcessor
  • ServletProcessor2
  • Constants

The HttpServer2 class is similar to HttpServer1, except that it uses ServletProcessor2 in its await method, instead of ServletProcessor1:

HttpServer2 类与 HttpServer1 类类似,只是在其 await 方法中使用了 ServletProcessor2,而不是 ServletProcessor1:

if (request.getUri().startWith("/servlet/")) {
 servletProcessor2 processor = new ServletProcessor2();
 processor.process(request, response);
 }
 else {
 ...
 }

The ServletProcessor2 class is similar to ServletProcessor1, except in the following part of its process method:

ServletProcessor2 类与 ServletProcessor1 类相似,但其处理方法的以下部分除外:

Servlet servlet = null;
 RequestFacade requestFacade = new RequestFacade(request);
 ResponseFacade responseFacade = new ResponseFacade(response);
 try {
 servlet = (Servlet) myClass.newInstance();
 servlet.service((ServletRequest) requestFacade,
 (ServletResponse) responseFacade);
 }

Running the Application

To run the application on Windows, type this from the working directory:

要在 Windows 上运行应用程序,请在工作目录中键入以下内容:

java -classpath ./lib/servlet.jar;./ ex02.pyrmont.HttpServer2

In Linux, you use a colon to separate two libraries.

在 Linux 中,使用冒号分隔两个库。

java -classpath ./lib/servlet.jar:./ ex02.pyrmont.HttpServer2

You can use the same URLs as in Application1 and you will get the same result.

您可以使用与 Application1 相同的 URL,并会得到相同的结果。

Summary

This chapter discussed two simple servlet containers that can he used to serve static resources as well as process servlets as simple as PrimitiveServlet. Background information on the javax.servlet.Servlet interface and related types was also given.

本章讨论了两个简单的 servlet 容器,它们可用于提供静态资源以及处理像 PrimitiveServlet 一样简单的 servlet。此外,还介绍了 javax.servlet.Servlet 接口和相关类型的背景信息。


Xander
198 声望51 粉丝

引用和评论

0 条评论