《HowTomcatWork》笔记总结(一)

前言

这一篇是howTomcatWork的书籍笔记内容。上面是依据书中的内容做的一部分集体笔记。

书籍地址:

链接:https://pan.baidu.com/s/1jazo...
提取码:lp96
--来自百度网盘超级会员V6的分享

集体评估

没啥好说的,tomcat作者写的书,看了下海淘竟然要500多的确吓到了。尽管代码应用的是tomcat5的版本,然而能够根本了解tomcat的外部工作机制。也能够看到作者是如何用一个十多行的肉鸡服务器代码一直降级成为当初的tomcat的模样。

本文是集体依据看书记录的一些笔记,两头逻辑不肯定连贯,因为有些内容过于根底没有记录的价值,所以挑了一些集体关注的点。

一个最简略的Servlet是如何工作的

  1. 创立Request 对象,并且解析HTTP的申请信息,通过Request对象封装了这些信息的具体细节

具体接口:

javax.servlet.ServletRequest javax.servlet.http.ServletRequest
  1. 创立Response对象,封装了客户需须要的真正数据,封装了响应体的相干信息
javax.servlet.ServletResponsejavax.servlet.http.ServletResponse
  1. servlet的service 办法,依据此办法对于申请头进行解析,同时创立response将数据回传给客户端

tomcat的根本构造

Tomcat把服务器在大体上能够拆分为两局部,一部分叫做容器,另一部分叫做连接器

连接器

作用:接管到每一个 HTTP 申请结构一个 ==request== 和 ==response== 对象

容器

作用:承受连接器的申请依据service办法进行响应给对应的客户端

Tomcat 4 和 和 5的次要区别

  • Tomcat 5 反对 Servlet 2.4 和 JSP 2.0 标准,而 Tomcat 4 反对 Servlet 2.3 和 JSP 1.2。
  • 比起 Tomcat 4,Tomcat 5 有一些更有效率的默认连接器。
  • Tomcat 5 共享一个后盾解决线程,而 Tomcat 4 的组件都有属于本人的后盾解决线程。
    因而,就这一点而言,Tomcat 5 耗费较少的资源。
  • Tomcat 5 并不需要一个映射组件(mapper component)用于查找子组件,因而简化了代码。

构建一个最简略的web程序

构建对象

下方的代码简略浏览即可,无需本人入手试验

HttpServer

用于构建一个服务器,同时建设serverSocket套接字期待链接

  • 调用httprequest.parse()办法

代码如下:

public class HttpServer {    /**     * 敞开容器的申请门路     */    private static final String SHUTDOWN = "/SHUTDOWN";    /**     * 动态资源根门路     */    public static final String WEBROOT = System.getProperty("user.dir") + File.separator + "webroot";    /**     * 是否敞开标识     */    private boolean SHUTDOWN_FLAG = false;    public static void main(String[] args) {        new HttpServer().await();    }    /**     * 具体的server 办法,期待socket申请     */    public void await() {        // 默认为8080端口        int port = 8080;        String host = "127.0.0.1";        ServerSocket serverSocket = null;        try {            // 创立套接字            serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));        } catch (IOException e) {            e.printStackTrace();            System.exit(-1);        }        while (!SHUTDOWN_FLAG) {            try {                // 期待套接字                Socket accept = serverSocket.accept();                HttpRequest httpRequest = new HttpRequest(accept.getInputStream());                // 解决申请数据                httpRequest.parse();                // 创立响应对象,解决响应信息                HttpResponse httpResponse = new HttpResponse(accept.getOutputStream());                // 设置动态资源                httpResponse.setRequest(httpRequest);                httpResponse.setResource();                // 敞开的套接字                accept.close();                // 判断申请Url是否为 /shutdown                SHUTDOWN_FLAG = httpRequest.getUri().equalsIgnoreCase(SHUTDOWN);            } catch (IOException e) {                e.printStackTrace();                continue;            }        }    }}

HttpRequest

以httpserver 的申请inputstream, 解析申请内容,合成申请uri

应用parse()办法解析申请信息,设置到stringbuffer外面

应用parseUri(str)截取申请信息的申请uri,设置到属性外面

public class HttpRequest {    /**     * 缓冲区的大小为 1M     */    private static final int BUFFER_COUNT = 1024;    /**     * 申请门路     */    private String uri;    /**     * 申请流     */    private InputStream inputStream;    public HttpRequest(InputStream inputStream) {        this.inputStream = inputStream;    }    /**     * 解析inputstream 对于内容进行解析     */    public void parse() {        // 字符串缓冲池        StringBuffer stringBuffer = new StringBuffer(BUFFER_COUNT);        byte[] byteBuffer = new byte[BUFFER_COUNT];        if (inputStream == null) {            System.err.println("未找到套接字");            return;        }        int read = 0;        try {            // 读取数据到byte数组            read = inputStream.read(byteBuffer);        } catch (IOException e) {            e.printStackTrace();            System.exit(-1);        }        //读取byte数组的数据进入到stringbuffer        for (int i = 0; i < read; i++) {            stringBuffer.append((char)byteBuffer[i]);        }        // 打印stringbuffer        System.err.println(stringBuffer.toString());        // 获取uri        uri = parseUri(stringBuffer.toString());    }    /**     * 解析申请,获取申请Uri     * @param requestString 须要解决的uri     */    public String parseUri(String requestString){        // 建设index1 和 2        int index1, index2;        // 获取到第一个空行        index1 = requestString.indexOf(' ');        if(index1 != -1){            // 从index1 开始找            index2 = requestString.indexOf(' ', index1 + 1);            if(index2 > index1){                // 获取申请门路                return requestString.substring(index1 + 1, index2);            }        }        return null;    }    public String getUri() {        return uri;    }}

HttpResonse

以 httpserver 的申请outputstream ,获取输出流,将数据返回给客户端

要害办法为setResouces,获取申请Uri,同时应用file 读取文件

public class HttpResponse {    /**     * 组合httprequest     * 依据request返回对应到信息     */    private HttpRequest request;    /**     * 输入流     */    private OutputStream outputStream;    /**     * 缓冲区大小     */    private static final int BUFFER_COUNT = 1024;    public HttpResponse(OutputStream outputStream) {        this.outputStream = outputStream;    }    /**     * 设置动态资源     */    public void setResource() throws IOException {        String errMsg = "404 msg";        // 字节缓存区        byte[] bytes = new byte[BUFFER_COUNT];        // 读取动态资源        File file = new File(HttpServer.WEBROOT, request.getUri());        if (file.exists()) {            // 文件流            try {                FileInputStream fileInputStream = new FileInputStream(file);                // 读取字节                int ch = fileInputStream.read(bytes, 0, BUFFER_COUNT);                // 输入                while (ch != -1) {                    // 写入流                    outputStream.write(bytes, 0, ch);                    // 反复读取数据到缓冲区                    ch = fileInputStream.read(bytes, 0, BUFFER_COUNT);                }            } catch (IOException e) {                e.printStackTrace();            } finally {                if (outputStream != null) {                    outputStream.close();                }            }        } else {            try {                outputStream.write(errMsg.getBytes());            } catch (IOException e) {                e.printStackTrace();            } finally {                if (outputStream != null) {                    outputStream.close();                }            }        }    }    /**     * 设置request     *     * @param httpRequest     */    public void setRequest(HttpRequest httpRequest) {        this.request = httpRequest;    }}

根本步骤:

上面是代码的根本交互步骤:

  1. 创立httpserver 对象
  2. 绑定端口和主机,建设套接字连贯
  3. accept()办法期待申请,阻塞以后线程
  4. 创立reuqest 申请对象
  5. 获取inputstream 解析申请信息
  6. 获取申请到uri,设置到Request对象
  7. 创立 response 响应对象
  8. 设置响应对象的request,拿到uri, 同时应用Io,读取申请对应到文件
  9. outputstream 解析文件流数据,应用write 返回到客户端
  10. 无论胜利还是失败,敞开流(重要)

补充内容:

这里重点关注对于套接字的一些常识

ServerSocket

服务器套接字的另一个重要的属性是 backlog,这是服务器套接字开始==回绝==传入的申请之前,传入的连贯申请的==最大队列长度==。

parseUri() 解决逻辑

GET /index.html HTTP/1.1。parse 办法从传递给 Requst 对象的套接字的 InputStream 中读取整个字节流并在一个缓冲区中存储字节数组。而后它应用缓冲区字节数据的字节来填入一个 StringBuffer 对象,并且把代表 StringBuffer 的字符串传递给 parseUri 办法。

Http申请对于servlet的操作

当第一次调用 servlet 的时候,加载该 servlet 类并调用 servlet 的 init 办法(仅仅一次)。

  • 对每次申请,结构一个 javax.servlet.ServletRequest 实例和一个
    javax.servlet.ServletResponse 实例。
  • 调用 servlet 的 service 办法,同时传递 ServletRequest 和 ServletResponse 对象。
  • 当 servlet 类被敞开的时候,调用 servlet 的 destroy 办法并卸载 servlet 类。
    本章的第一个servlet容器不是全功能的。因而,她不能运行什么除了非常简单的servlet,
    而且也不调用 servlet 的 init 办法和 destroy 办法。相同它做了上面的事件:
  • 期待 HTTP 申请。
  • 结构一个 ServletRequest 对象和一个 ServletResponse 对象。
  • 如果该申请须要一个动态资源的话,调用 StaticResourceProcessor 实例的 process 方
    法,同时传递 ServletRequest 和 ServletResponse 对象。
  • 如果该申请须要一个 servlet 的话,加载 servlet 类并调用 servlet 的 service 办法,
    同时传递 ServletRequest 和 ServletResponse 对象

StringManager

特点:

  1. 应用单例模式
  2. 每个实例会读取包对应的一个属性文件
  3. StringManager 类被设计成一个 StringManager实例能够被包里边的所有类共享
  4. getManager() 办法被 同步润饰,并且应用hashtable对于manager进行治理(tomcat4)

外围办法解释

SocketInputStream:套接字读取流,次要用于解决Http申请中的各种参数,为了提高效率,应用懒加载个性读取

  1. 回收查看流数据
  2. 查看空行,如果呈现-1抛出结尾异样
  3. 获取servlet办法名称

    1. 如果缓冲区曾经满了,则进行扩大

      1. 咱们在外部缓冲区的止境
      2. 如果到了缓冲区结尾,将指针归位
    2. 这里有一个关键点:System.arraycopy 用来扩大缓冲区
  4. 浏览协定

模块解释

对于局部模块的相干解释。

解析头部

  • 你能够通过应用类的无参数构造方法结构一个 HttpHeader 实例。
  • 一旦你领有一个HttpHeader实例,你能够把它传递给SocketInputStream的readHeader
    办法。如果这里有头部须要读取,readHeader 办法将会相应的填充 HttpHeader 对象。
    如果再也没有头部须要读取了,HttpHeader实例的nameEnd和valueEnd字段将会置零。
  • 为了获取头部的名称和值,应用上面的办法:
  • String name = new String(header.name, 0, header.nameEnd);
  • String value = new String(header.value, 0, header.valueEnd);

启动器

  • 启动应用程序
  • 连接器
  • 创立一个 HttpRequest 对象
  • 创立一个 HttpResponse 对象
  • 动态资源处理器和 servlet 处理器
  • 运行应用程序

startup 模块只有一个类,Bootstrap,用来启动利用的。connector 模块的类能够分为五组:

  • 连接器和它的撑持类(HttpConnector 和 HttpProcessor)。
  • 指代 HTTP 申请的类(HttpRequest)和它的辅助类。
  • 指代 HTTP 响应的类(HttpResponse)和它的辅助类。
  • Facade 类(HttpRequestFacade 和 HttpResponseFacade)。
  • Constant 类

问题以及解决

如何防止在servlet调用连接器的时候,不须要申请参数能够防止掉getParamMap,getAttribute等低廉开销的操作?

Tomcat 的默认连接器(和本章应用程序的连接器)试图不解析参数直到 servlet 真正须要它的时候,通过这样来取得更高效率

小常识补充

  1. System 在打印的时候 print 办法不会刷新输入。
  2. 在一个 servlet 容器里边,一个类加载器能够找到 servlet 的中央被称为资源库(repository)。
  3. 通过外观模式将Request对象的细节暗藏,setvlet调用外部无奈晓得,然而在解析的时候仍然能够互相通信,只须要应用faced将接口进行一层包裹, 即可保障getUri()办法安全性

总结

书中第一个章节内容比较简单,后续章节代码的难度会逐步回升,同时应用了不少的设计模式也是须要多加浏览了解和消化的

写在最初

这篇笔记目标是让更多人理解这本书,这本书算是一本神书,毕竟开发的原作者本人写的货色毫无疑问是一手常识了。