0
点赞
收藏
分享

微信扫一扫

greatest单元测试框架

陆公子521 18小时前 阅读 1

现在开始《深入剖析Tomcat》第二章的内容,第一章中,我们编码实现了一个能正常接收HTTP请求并返回静态资源的Web容器,这一章开始引入Servlet的概念,使我们的服务能根据请求动态返回内容。

Servlet是什么?

这是首先要弄清楚的一个东西,有篇文章对它的历史由来介绍的很好【Servlet的历史与规范】,推荐阅读它的第一节:Servlet历史。

如果你是一个Java开发者,那么你经常写的Controller就可以视为一个Servlet。Servlet是处理某个特定HTTP请求的组件程序,它不能独立工作,由Servlet容器统一管理众多Servlet,Servlet容器负责将HTTP请求分发给指定的Servlet。

如上图所示,Tomcat大概分为连接器和Servlet容器两大块,连接器负责接收HTTP请求,并封装成Request与Response对象,然后将处理过程交给Servlet容器,Servlet容器会判断这个请求该交给哪个Servlet来处理,Servlet处理完后给客户端反馈。

Servlet规范是Sun公司制定的一套技术标准,包含与Web应用相关的一系列接口,是Web应用实现方式的宏观解决方案。而具体的Servlet容器则根据规范提供标准的实现。Tomcat,Jetty 等都是Servlet容器,它们都遵循Servlet规范,也就是说它们都是基于Servlet的标准接口来实现的自定义逻辑。

Servlet规范内容很多,这里仅提一下本章涉及到的知识,其他知识待用到时再补充。下面介绍几个规范中的接口

javax.servlet.Servlet

实现了 javax.servlet.Servlet 接口的类,就是一个Servlet。

package javax.servlet;

import java.io.IOException;
/**
 * 定义所有servlet必须实现的方法。
 * servlet是在Web服务器中运行的小Java程序。
 * servlet通常通过HTTP(超文本传输协议)接收和响应来自Web客户机的请求。
 * 要实现这个接口,可以编写一个通用的servlet,例如 继承javax.servlet.GenericServlet,也可以编写一个HTTP servlet通过继承javax.servlet.http.HttpServlet。
 * 这个接口定义了初始化servlet、处理请求和从容器中删除servlet的方法。这些方法被称为生命周期方法,按以下顺序调用:
 * 1.首先构造servlet,然后使用init方法进行初始化。
 * 2.service方法处理客户端的所有请求。
 * 3.使用destroy方法销毁servlet,然后进行垃圾收集并最终完成销毁过程。
 * 除了生命周期方法之外,这个接口还提供了getServletConfig方法(servlet可以使用该方法获取任何启动信息)
 * 和getServletInfo方法(允许servlet返回关于自身的基本信息,例如作者、版本和版权)。
 */
public interface Servlet {
    /**
     * 由servlet容器调用,以指示servlet正在被放入服务中。
     * servlet容器在实例化servlet之后只调用init方法一次。
     * init方法必须成功完成,servlet才能接收请求。
     * 如果init方法发生下列情况,servlet容器就不能将servlet放入容器
     * 1.抛出异常 ServletException
     * 2.没有在Web服务器定义的时间段内返回
     */
    void init(ServletConfig var1) throws ServletException;
    
    /**
     * 返回一个ServletConfig对象,其中包含此servlet的初始化和启动参数。返回的ServletConfig对象是传递给init方法的对象。 
     * 这个接口的实现负责存储ServletConfig对象,以便这个方法可以返回它。实现这个接口的GenericServlet类已经做到了这一点。
     */
    ServletConfig getServletConfig();

    /**
     * 由servlet容器调用,以允许servlet响应请求。
     * 此方法仅在servlet的init()方法成功完成后调用。
     * 响应的状态码应该始终为抛出或发送错误的servlet设置。
     * servlet通常运行在多线程servlet容器中,可以并发处理多个请求。开发人员必须注意同步访问任何共享资源,如文件、网络连接
     * 以及servlet的类和实例变量。有关Java中多线程编程的更多信息,请参见Java多线程编程教程。
     */
    void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    /**
     * 返回有关servlet的信息,如作者、版本和版权。 
     * 此方法返回的字符串应该是纯文本,而不是任何类型的标记(如HTML、XML等)。
     */
    String getServletInfo();
    
    /**
     * 由servlet容器调用,以指示servlet正在退出服务。此方法仅在servlet服务方法中的所有线程都退出或超时时间过后才调用。
     * 在servlet容器调用此方法之后,它将不会在此servlet上再次调用该服务方法。 
     * 此方法为servlet提供了一个机会来清理任何被占用的资源(例如,内存、文件句柄、线程),并确保任何持久状态都与servlet在内存中的当前状态同步。
     */
    void destroy();
}

javax.servlet.ServletConfig

package javax.servlet;

import java.util.Enumeration;

/**
 * 一个servlet配置对象,由servlet容器使用,在初始化期间向servlet传递信息
 */
public interface ServletConfig {

    /**
     * 返回此servlet实例的名称。该名称可以通过服务器管理提供,在web应用程序部署描述符中分配,或者对于未注册(因此未命名)的servlet实例,它将是servlet的类名。
     */
    public String getServletName();

    /**
     * 返回对调用者正在其中执行的ServletContext的引用。
     */
    public ServletContext getServletContext();

    /**
     * 返回一个包含初始化参数值的字符串,如果参数不存在则返回null。
     */
    public String getInitParameter(String name);

    /**
     * 返回servlet初始化参数的枚举,如果servlet没有初始化参数,则返回空枚举。
     */
    public Enumeration<String> getInitParameterNames();
}

javax.servlet.ServletContext

ServletContext接口定义的方法太多,就不在这里放代码了。

看一下这个接口的注释吧:

下面列举这个接口的一些常用方法

  • String getContextPath():获取Web应用程序的上下文路径。
  • String getRealPath(String path):将指定的虚拟路径映射到实际路径。
  • RequestDispatcher getRequestDispatcher(String path):获取用于将请求转发到另一个资源的请求分派器。
  • InputStream getResourceAsStream(String path):获取位于Web应用程序上下文的指定路径的输入流。
  • URL getResource(String path):返回指定路径的URL,用于从ServletContext获取资源。
  • ServletContext getContext(String uripath):获取指定URI路径的ServletContext。
  • String getMimeType(String file):获取指定文件的MIME类型。
  • Set<String> getResourcePaths(String path):获取指定路径下的所有资源路径的集合。
  • String getInitParameter(String name):获取指定名称的初始化参数的值。
  • Enumeration<String> getInitParameterNames():获取所有初始化参数的名称的枚举。
  • void setAttribute(String name, Object object):在ServletContext中设置一个属性。
  • Object getAttribute(String name):获取指定名称的ServletContext属性。
  • void removeAttribute(String name):从ServletContext中移除指定名称的属性。
  • String getServerInfo():获取Servlet容器的名称和版本信息。
  • int getMajorVersion() 和 int getMinorVersion():获取Servlet API的主版本号和次版本号。

javax.servlet.ServletRequest 与 javax.servlet.ServletResponse

这两个类是javax.servlet.Servlet#service 方法需要的两个参数,也就是说 servlet 需要这两个参数来处理请求与返回结果,所有的HTTP请求打到Servlet容器时都需要封装成这两个对象。

ServletRequest 接口代表客户端请求,并提供了访问请求参数、请求头和其他请求信息的方法。以下是一些常用的方法:

  • String getParameter(String name):根据参数名称获取请求参数的值。
  • Enumeration<String> getParameterNames():获取所有请求参数名称的枚举。
  • String[] getParameterValues(String name):根据参数名称获取请求参数的值数组。
  • Map<String, String[]> getParameterMap():获取所有请求参数的映射。
  • String getHeader(String name):根据头部名称获取请求头的值。
  • Enumeration<String> getHeaderNames():获取所有请求头名称的枚举。
  • String getMethod():获取请求的 HTTP 方法,例如 GET、POST 等。
  • String getRemoteAddr():获取客户端的 IP 地址。
  • String getRemoteHost():获取客户端的主机名。
  • int getContentLength():获取请求正文的长度。
  • String getContentType():获取请求正文的 MIME 类型。
  • InputStream getInputStream():获取请求正文的输入流。

ServletResponse 接口代表 Servlet 对客户端的响应,并提供了设置响应内容和发送响应的方法。以下是一些常用的方法:

  • void setContentType(String type):设置响应的内容类型。
  • String getContentType():获取响应的内容类型。
  • void setContentLength(int len):设置响应正文的长度。
  • void setCharacterEncoding(String charset):设置响应字符编码。
  • PrintWriter getWriter():获取一个可以向客户端发送字符数据的 PrintWriter 对象。
  • ServletOutputStream getOutputStream():获取一个可以向客户端发送二进制数据的 ServletOutputStream 对象。
  • void sendRedirect(String location):重定向响应到指定的 URL。
  • void setStatus(int sc):设置响应的状态码。
  • void setHeader(String name, String value):设置响应头的值。
  • void addHeader(String name, String value):添加响应头的值。

本章用到的接口就是上面这些了,下面来看看本章代码的设计

Servlet容器代码设计

本章内容中加入servlet的设计,立个规定:servlet的请求均以“/servlet”打头,格式为“/servlet/servletName”,其他请求皆认为还是请求静态资源。

本章通过两个小应用程序说明如何开发自己的servlet容器,第一个应用程序的设计非常简单,仅仅用于说明servlet容器是如何运行的。它然后演变为第二个servlet容器,后者稍微复杂一点。

第一个应用程序

借用一下书中的UML图,来看下本次代码设计

区别于第一章的代码,本章代码做了如下改造

  • Request类实现了javax.servlet.ServletRequest接口
  • Response类实现了javax.servlet.ServletResponse接口
  • HttpServer1中能够同时接收动态与静态资源的请求,动态资源交给ServletProcessor1处理,静态资源交给StaticResourceProcessor处理。

接下来看看具体的代码

启动类HttpServer1

package ex02.hml.one;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

public class HttpServer1 {

    // 声明一个结束标识,用来判断是否需要终止服务
    public static boolean shutDown = false;

    public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket(8080, 10, InetAddress.getByName("127.0.0.1"));
            Socket socket;
            while (!shutDown) {
                socket = serverSocket.accept();
                InputStream inputStream = socket.getInputStream();
                Request request = new Request();
                request.parse(inputStream);
                OutputStream outputStream = socket.getOutputStream();
                Response response = new Response(request, outputStream);

                if (request.getUri().startsWith("/servlet/")) {
                    ServletProcessor1 servletProcessor1 = new ServletProcessor1();
                    servletProcessor1.process(request, response);
                } else {
                    StaticResourceProcessor staticResourceProcessor = new StaticResourceProcessor();
                    staticResourceProcessor.process(request, response);
                }

                inputStream.close();
                socket.close();

                // 判断是否是结束服务的请求
                shutDown = request.getUri().equals("/shutdown");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }


}

Request类

因为Servlet接收的request相关参数为ServletRequest,所以第一章的Request类也要实现ServletRequest接口,但是接口的方法暂时都不做实现,都返回空。原第一章的代码仍然保留。

package ex02.hml.one;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Map;

public class Request implements ServletRequest {

    private String uri;

    public void parse(InputStream inputStream) {

        try {
            byte[] bytes = new byte[1024];
            int readLenth = inputStream.read(bytes);
            String content = "";
            while (readLenth != -1) {
                content += new String(bytes);
                if (readLenth < 1024) {
                    break;
                }
                readLenth = inputStream.read(bytes);
            }
            System.out.println("request body -------------->");
            System.out.println(content);
            //获取请求内容中第一个空格和第二个空格之间的内容
            setUri(content);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void setUri(String content) {
        int index1 = content.indexOf(" ");
        if (index1 == -1) {
            return;
        }
        int index2 = content.indexOf(" ", index1 + 1);
        String substring = content.substring(index1 + 1, index2);
        this.uri = substring;
    }

    public String getUri() {
        return uri;
    }

    /*下面是实现 ServletRequest 接口的方法 */
    @Override
    public Object getAttribute(String s) {
        return null;
    }

    @Override
    public Enumeration getAttributeNames() {
        return null;
    }

    @Override
    public String getCharacterEncoding() {
        return null;
    }

    @Override
    public void setCharacterEncoding(String s) throws UnsupportedEncodingException {

    }

    @Override
    public int getContentLength() {
        return 0;
    }

    @Override
    public String getContentType() {
        return null;
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        return null;
    }

    @Override
    public String getParameter(String s) {
        return null;
    }

    @Override
    public Enumeration getParameterNames() {
        return null;
    }

    @Override
    public String[] getParameterValues(String s) {
        return new String[0];
    }

    @Override
    public Map getParameterMap() {
        return null;
    }

    @Override
    public String getProtocol() {
        return null;
    }

    @Override
    public String getScheme() {
        return null;
    }

    @Override
    public String getServerName() {
        return null;
    }

    @Override
    public int getServerPort() {
        return 0;
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return null;
    }

    @Override
    public String getRemoteAddr() {
        return null;
    }

    @Override
    public String getRemoteHost() {
        return null;
    }

    @Override
    public void setAttribute(String s, Object o) {

    }

    @Override
    public void removeAttribute(String s) {

    }

    @Override
    public Locale getLocale() {
        return null;
    }

    @Override
    public Enumeration getLocales() {
        return null;
    }

    @Override
    public boolean isSecure() {
        return false;
    }

    @Override
    public RequestDispatcher getRequestDispatcher(String s) {
        return null;
    }

    @Override
    public String getRealPath(String s) {
        return null;
    }
}

Response类

因为Servlet接收的response相关参数为ServletResponse,所以第一章的Response类也要实现ServletResponse接口,接口的实现方法中仅实现一个getWriter方法,其他方法留空。原第一章的代码仍然保留。

package ex02.hml.one;

import javax.servlet.ServletOutputStream;
import javax.servlet.ServletResponse;
import java.io.*;
import java.util.Locale;

public class Response implements ServletResponse {

    private Request request;

    private OutputStream outputStream;

    private PrintWriter writer;

    public static final String rootDir = System.getProperty("user.dir") + File.separatorChar + "webroot";

    public Response(Request request, OutputStream outputStream) {
        this.request = request;
        this.outputStream = outputStream;
    }

    public void sendStaticResource() {
        try {
            if (request.getUri().equals("/shutdown")) {
                String msg = "HTTP/1.1 200 OK\r\n" +
                        "Content-Type: text/html\r\n" +
                        "Content-Length: 32\r\n" +
                        "\r\n" +
                        "<h1>server already shutdown</h1>";
                outputStream.write(msg.getBytes());
                return;
            }


            File file = new File(rootDir + request.getUri());
            if (file.exists()) {
                FileInputStream fileInputStream = new FileInputStream(file);
                byte[] bytes = new byte[fileInputStream.available()];
                fileInputStream.read(bytes);
                String successMsg = "HTTP/1.1 200 OK\r\n" +
                        "Content-Type: text/html\r\n" +
                        "Content-Length: " + bytes.length + "\r\n" +
                        "\r\n";
                outputStream.write(successMsg.getBytes());
                outputStream.write(bytes);
                fileInputStream.close();
            } else {
                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>";
                outputStream.write(errorMessage.getBytes());
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (outputStream != null) {
                try {
                    outputStream.flush();
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }


    }


    @Override
    public String getCharacterEncoding() {
        return null;
    }

    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        return null;
    }

    @Override
    public PrintWriter getWriter() throws IOException {
        writer = new PrintWriter(outputStream, false);
        return writer;
    }

    @Override
    public void setContentLength(int i) {

    }

    @Override
    public void setContentType(String s) {

    }

    @Override
    public void setBufferSize(int i) {

    }

    @Override
    public int getBufferSize() {
        return 0;
    }

    @Override
    public void flushBuffer() throws IOException {

    }

    @Override
    public void resetBuffer() {

    }

    @Override
    public boolean isCommitted() {
        return false;
    }

    @Override
    public void reset() {

    }

    @Override
    public void setLocale(Locale locale) {

    }

    @Override
    public Locale getLocale() {
        return null;
    }
}

getWriter() 方法是提供给 servlet 获取 PrintWriter 对象的,PrintWriter对象可以将各种内容写入到Socket 的 OutputStream 中。

代码中使用了这个构造函数:public PrintWriter(OutputStream out, boolean autoFlush) ,第二个参数传true时,调用 println, printf, format 三个方法会自动刷新(flush),我这里的代码不同与书中的,我将它设为了false, 需要刷新输出流时,手动刷新,防止遗漏刷新过程。

StaticResourceProcessor类

这是处理静态资源的类,此类很简单,静态资源的处理逻辑不变,仍然放在Response类中

public class StaticResourceProcessor {
    public void process(Request request,Response response) {
        response.sendStaticResource();
    }
}

ServletProcessor1类

Servlet处理器,或者也可以叫它Servlet分发类,它负责将HTTP请求分发到指定的Servlet中。

process方法中需要创建类加载器并加载需要用到的 servlet ,然后才能使用它 。

package ex02.hml.one;

import javax.servlet.Servlet;
import javax.servlet.ServletException;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;

/**
 * 处理servlet请求   /servlet/servletName
 *
 */
public class ServletProcessor1 {

    public static final String rootDir = System.getProperty("user.dir") + File.separatorChar + "webroot";

    public void process(Request request, Response response) {
        try {
            String uri = request.getUri();
            String servletName = uri.substring(uri.lastIndexOf("/") + 1);

            //首先获取类加载器,这里采用URLClassLoader
            File file = new File(rootDir);
            String repository = (new URL("file", null, file.getCanonicalPath() + File.separator)).toString();
            URL[] urls = new URL[1];
            urls[0] = new URL(null, repository);
            URLClassLoader urlClassLoader = new URLClassLoader(urls);

            //加载servlet对应的类
            Class<?> aClass = urlClassLoader.loadClass(servletName);
            Servlet servlet = (Servlet) aClass.newInstance();

            // 调用servlet的service方法,处理请求并返回结果
            servlet.service(request, response);

        } catch (IOException | ClassNotFoundException | InstantiationException | IllegalAccessException |
                 ServletException e) {
            e.printStackTrace();
        }

    }
}

PrimitiveServlet类

这就是一个具体的 servlet 了,实现了 Servlet 接口,这里仅仅实现了 service 一个方法,其他方法留空。

这个servlet类没有放在项目的src目录下,而是放在了webroot目录下,也就是说它不属于Tomcat的源码范围。使用过Tomcat部署项目的同学应该了解,servlet应该是我们自己根据各个项目的业务写出来的,项目打包后放在Tomcat的webapps目录下,Tomcat启动后自动扫描webapps目录下的项目,所以PrimitiveServlet也暂时放到一个非源码目录下。

PrimitiveServlet_origin.java是原书中的PrimitiveServlet类的源码,我给原书中的类都加了一个“_origin”后缀,但是里面的类名没改,所以不能编译,仅做对比参考。

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

        String body = "This is the response data。";
        String header = "HTTP/1.1 200 OK\r\n" +
                "Content-Type: text/html\r\n" +
                "Content-Length: " + body.length() + "\r\n" +
                "\r\n";

        out.print(header);
        out.print(body);

        out.flush();
        out.close();
    }

    public void service_chunked(ServletRequest request, ServletResponse response)
            throws ServletException, IOException {
        System.out.println("from service");
        PrintWriter out = response.getWriter();

        String msg = "HTTP/1.1 200 OK\r\n" +
                "Content-Type: text/plain\r\n" +
                "Transfer-Encoding: chunked\r\n" +
                "\r\n";
        out.print(msg);

        String msg1 = "This is the data in the first chunk。\r\n";
        out.print(Integer.toHexString(msg1.length()) + "\r\n");
        out.print(msg1 + "\r\n");

        String msg2 = "This is the data in the second chunk。\r\n";
        out.print(Integer.toHexString(msg2.length()) + "\r\n");
        out.print(msg2 + "\r\n");

        out.print("0\r\n\r\n");
        out.flush();
        out.close();
    }

    public void destroy() {
        System.out.println("destroy");
    }

    public String getServletInfo() {
        return null;
    }

    public ServletConfig getServletConfig() {
        return null;
    }

}

service方法我提供了两种实现,区别是在HTTP返回头中指定数据长度的方式不同,一种是使用 Content-Length 声明返回body体的总长度;另一种是使用 Transfer-Encoding: chunked 的方式分块声明每块数据的长度。两种方式皆可成功返回数据。

使用Content-Length方式的话,需要提前知道完整body体后才可拼写HTTP Header。

使用Transfer-Encoding: chunked 的话,不需要在Header中指定长度,只要在body体中声明每块数据的长度即可。

在HTTP返回格式的定义上也有些许不同

使用Content-Length时

使用 Transfer-Encoding: chunked 时

编译PrimitiveServlet类

在项目的webroot目录下有个complie.sh文件,里面写有可以编译PrimitiveServlet类的脚本,脚本如下

javac -cp "../lib/*" PrimitiveServlet.java

最终效果展示

启动HttpServer1,浏览器访问效果展示

至此,一个非常简易的servlet容器就完成了。但是这个程序也有一点问题,那就是ServletProcessor1在调用具体servlet的service方法时,将ex02.hml.one.Request与ex02.hml.one.Response对象传了过去,虽然PrimitiveServlet的service方法的入参类型是ServletRequest与ServletResponse,但是了解程序设计的人仍然能将它们向下转型为Request与Response对象,并调用其public方法,如Requst的 parse 方法与Response的sendStaticResource方法,这是违反我们的设计初衷的。怎么解决呢?使用外观类。借用一下原书中的UML图

从这两个外观类开始,下面进行本章第二个应用程序的设计

第二个应用程序

RequestFacade与ResponseFacade

RequestFacade类实现了ServletRequest接口并持有一个ServletRequest对象的引用->request,这个request属性用来存放我们的ex02.hml.two.Request对象。RequestFacade类中实现的ServletRequest接口方法的具体实现都调用request属性的对应实现方法。这样下来,RequestFacade类中就没有暴漏多余的方法了。即使servlet程序员将ServletRequest对象向下转型为RequestFacade对象,也访问不了Request类中的parse、getUri方法了。

ResponseFacade同理,实现了ServletResponse接口并持有一个ServletResponse对象的引用->response,这个response属性用来存放我们的ex02.hml.two.Response对象。

package ex02.hml.two;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Map;

public class RequestFacade implements ServletRequest {

    private ServletRequest request;

    public RequestFacade(Request request) {
        this.request = request;
    }
    
    @Override
    public Object getAttribute(String s) {
        return null;
    }

    @Override
    public Enumeration getAttributeNames() {
        return null;
    }

    @Override
    public String getCharacterEncoding() {
        return null;
    }

    @Override
    public void setCharacterEncoding(String s) throws UnsupportedEncodingException {

    }

    @Override
    public int getContentLength() {
        return 0;
    }

    @Override
    public String getContentType() {
        return null;
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        return null;
    }

    @Override
    public String getParameter(String s) {
        return null;
    }

    @Override
    public Enumeration getParameterNames() {
        return null;
    }

    @Override
    public String[] getParameterValues(String s) {
        return new String[0];
    }

    @Override
    public Map getParameterMap() {
        return null;
    }

    @Override
    public String getProtocol() {
        return null;
    }

    @Override
    public String getScheme() {
        return null;
    }

    @Override
    public String getServerName() {
        return null;
    }

    @Override
    public int getServerPort() {
        return 0;
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return null;
    }

    @Override
    public String getRemoteAddr() {
        return null;
    }

    @Override
    public String getRemoteHost() {
        return null;
    }

    @Override
    public void setAttribute(String s, Object o) {

    }

    @Override
    public void removeAttribute(String s) {

    }

    @Override
    public Locale getLocale() {
        return null;
    }

    @Override
    public Enumeration getLocales() {
        return null;
    }

    @Override
    public boolean isSecure() {
        return false;
    }

    @Override
    public RequestDispatcher getRequestDispatcher(String s) {
        return null;
    }

    @Override
    public String getRealPath(String s) {
        return null;
    }
}
package ex02.hml.two;

import javax.servlet.ServletOutputStream;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Locale;

public class ResponseFacade implements ServletResponse {

    private ServletResponse response;

    public ResponseFacade(Response response) {
        this.response = response;
    }

    @Override
    public String getCharacterEncoding() {
        return null;
    }

    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        return null;
    }

    @Override
    public PrintWriter getWriter() throws IOException {
        return response.getWriter();
    }

    @Override
    public void setContentLength(int i) {

    }

    @Override
    public void setContentType(String s) {

    }

    @Override
    public void setBufferSize(int i) {

    }

    @Override
    public int getBufferSize() {
        return 0;
    }

    @Override
    public void flushBuffer() throws IOException {

    }

    @Override
    public void resetBuffer() {

    }

    @Override
    public boolean isCommitted() {
        return false;
    }

    @Override
    public void reset() {

    }

    @Override
    public void setLocale(Locale locale) {

    }

    @Override
    public Locale getLocale() {
        return null;
    }
}

HttpServer2类

HttpServer2类与HttpServer1类相似,只是在main方法中会使用ServletProcessor2类,而不是ServletProcessor1类

package ex02.hml.two;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

public class HttpServer2 {

    public static boolean shutDown = false;

    public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket(8080, 10, InetAddress.getByName("127.0.0.1"));
            Socket socket;
            while (!shutDown) {
                socket = serverSocket.accept();
                InputStream inputStream = socket.getInputStream();
                Request request = new Request();
                request.parse(inputStream);
                OutputStream outputStream = socket.getOutputStream();
                Response response = new Response(request, outputStream);

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

                inputStream.close();
                socket.close();
                shutDown = request.getUri().equals("/shutdown");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }


}

ServletProcessor2类

ServletProcessor2类与ServletProcessor1类相似,只是在process方法中有一点不同

package ex02.hml.two;

import javax.servlet.Servlet;
import javax.servlet.ServletException;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;

/**
 * 处理servlet请求   /servlet/servletName
 */
public class ServletProcessor2 {

    public static final String rootDir = System.getProperty("user.dir") + File.separatorChar + "webroot";

    public void processor(Request request, Response response) {
        try {
            String uri = request.getUri();
            String servletName = uri.substring(uri.lastIndexOf("/") + 1);

            //首先获取类加载器
            File file = new File(rootDir);
            String repository = (new URL("file", null, file.getCanonicalPath() + File.separator)).toString();
            URL[] urls = new URL[1];
            urls[0] = new URL(null, repository);
            URLClassLoader urlClassLoader = new URLClassLoader(urls);

            //加载servlet对应的类
            Class<?> aClass = urlClassLoader.loadClass(servletName);
            Servlet servlet = (Servlet) aClass.newInstance();

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

        } catch (IOException | ClassNotFoundException | InstantiationException | IllegalAccessException |
                 ServletException e) {
            e.printStackTrace();
        }
        
    }
}

使用HttpServer2启动服务后的浏览器访问结果与HttpServer1相同,就不再贴图了。

好了一个稍加改造后的servlet容器就完成了,本章内容就到这里,下一章我们来实现一个精简版的连接器,敬请期待!

源码分享

https://gitee.com/huo-ming-lu/HowTomcatWorks

本章代码我按照两个应用程序分别放在了 one、two 两个包下

举报

相关推荐

0 条评论