《深入剖析Tomcat 》第3章 连接器(Connector)

2024-06-05 20:48

本文主要是介绍《深入剖析Tomcat 》第3章 连接器(Connector),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

第3章  连接器(Connector)

3.1  概述

在简介一章里说明了,tomcat由两大模块组成:连接器(connector)和容器(container)。本章将使用连接器来增强application 2的功能。一个支持servlet2.3和2.4规范的连接器必须要负责创建javax.servlet.http.HttpServletRequest和javax.servlet.http.HttpServletResponse实例,并将它们作为参数传递给要调用的某个的servlet的service方法。在第2章中的servlet容器仅仅能运行实现了javax.servlet.Servlet接口,并想service方法中传入了javax.servlet.ServletRequest和javax.servlet.ServletResponse实例的servlet。由于连接器并不知道servlet的具体类型(例如,该servlet是否javax.servlet.Servlet接口,还是继承自javax.servlet.GenericServlet类,或继承自javax.servlet.http.HttpServlet类),因此连接器总是传入HttpServletRequest和HttpServletResponse的实例对象。

         本章中所要建立的connector实际上是tomcat4中的默认连接器(将在第4章讨论)的简化版。本章中,connector和container将分离开。

         在开始说明本章的程序之前,先花点时间介绍下org.apache.catalina.util.StringManager类,它被tomcat用来处理不同模块内错误信息的国际化。在本章中,也是这样用的。

3.2  StringManager类

tomcat将错误信息写在一个properties文件中,这样便于读取和编辑。但若是将所有类的错误信息都写在一个properties文件,优惠导致文件太大,不便于读写。为避免这种情况,tomcat将properties文件按照不同的包进行划分,每个包下都有自己的properties文件。例如,org.apache.catalina.connector包下的properties文件包含了该包下所有的类中可能抛出的错误信息。每个properties文件都由一个org.apache.catalina.util.StringManager实例来处理。在tomcat运行时,会建立很多StringManager类的实例,每个实例对应一个properties文件。

         当包内的某个类要查找错误信息时,会先获取对应的StringManager实例。StringManager被设计为在包内是共享的一个单例,功过hashtable实现。如下面的代码所示:

 

java代码:
查看 复制到剪贴板 打印
  1. private static Hashtable managers = new Hashtable();   
  2. public synchronized static StringManager   
  3.     getManager(String packageName) {   
  4.    StringManager mgr = (StringManager)managers.get(packageName);    if (mgr == null) {   
  5.      mgr = new StringManager(packageName);   
  6.      managers.put(packageName, mgr);   
  7.    }   
  8.    return mgr;   
  9. }  
  10. StringManager sm = StringManager.getManager("ex03.pyrmont.connector.http");  

3.3  Application

从本章开始,每章的应用程序都会按照模块进行划分。本章的应用程序可分为3个模块:connector、startup、core。

         startup模块仅包括一个StartUp类,负责启动应用程序。

         connector模块的类可分为以下5个部分:

l         连接器及其支持类(HttpConnector和HttpProcessor);

l         表示http请求的类(HttpRequest)及其支持类;

l         表示http响应的类(HttpResponse)及其支持类;

l         外观装饰类(HttpRequestFacade和HttpResponseFacade);

l         常量类。

core模块包括ServletProcessor类和StaticResourceProcessor类。

下面是程序的uml图:

图表 4  application的uml图

相比于第2章中的程序,HttpServer在本章中被分成了HttpConnector和HttpProcessor两个类。Request和Response分别被HttpRequest和HttpResponse代替。此外,本章的应用程序中还使用了一些其他的类。

         在第2章中,HttpServer负责等待http请求,并创建request和response对象。本章中,等待http请求的工作由HttpConnector完成,创建request和response对象的工作由HttpProcessor完成。

本章中,http请求用HttpRequest对象表示,该类实现了javax.servlet.http.HttpServletRequest接口。一个HttpRequest对象在传给servlet的service方法前,会被转型为HttpServletRequest对象。因此,需要正确设置每个HttpRequest对象的成员变量,方便servlet使用。需要设置的值包括,uri,请求字符串,参数,cookie和其他一些请求头信息等。由于连接器并不知道servlet中会使用那些变量,素以它会将从http请求中获取的变量都设置到HttpRequest对象中。但是,处理一个http请求会设计到一些比较耗时的操作,如字符串处理等。因此,若是connector仅仅传入servlet需要用到的值就会节省很多时间。tomcat的默认connector对这些值的处理是等到servlet真正用到的时候才处理的。

tomcat的默认connector和本程序的connector通过SocketInputStream类来读取字节流,可通过socket的getInputStream方法来获取该对象。它有两个重要的方法readRequestLine和readHeader。readRequestLine方法返回一个http请求的第一行,包括uri,请求方法和http协议版本。从socket的inputStream中处理字节流意味着要从头读到尾(即不能返回来再读前面的内容),因此,readRequestLine方法一定要在readHeader方法前调用。readRequestLine方法返回的是HttpRequestLine对象,readHeader方法返回的是HttpHeader对象(key-value形式)。获取HttpHeader对象时,应重复调用readHeader方法,直到再也无法获取到。

HttpProcessor对象负责创建HttpRequest对象,并填充它的成员变量。在其parse方法中,将请求行(request line)和请求头(request header)信息填充到HttpRequest对象中,但并不会填充请求体(request body)和查询字符串(query string)。

3.3.1  启动

在Bootstrap类的main方法内实例化一个HttpConnector类的对象,并调用其start方法就可以启动应用程序。

3.3.2  connector

HttpConnector类实现了java.lang.Runnable接口,这样它可以专注于自己的线程。启动应用程序时,会创建一个HttpConnector对象,其run方法会被调用。其run方法中是一个循环体,执行以下三件事:

l         等待http请求;

l         为每个请求创建一个HttpPorcessor对象;

l         调用HttpProcessor对象的process方法。

         HttpProcessor类的process方法从http请求中获取socket。对每个http请求,它要做一下三件事:

l         创建一个HttpRequest对象和一个HttpResponse对象;

l         处理请求行(request line)和请求头(request headers),填充HttpRequest对象;

l         将HttpRequest对象和HttpResponse对象传给ServletProcessor或StaticResourceProcessor的process方法。

 

3.3.3  创建HttpRequest对象

HttpRequest类实现了javax.servlet.http.HttpServletRequest接口。其伴随的外观类是HttpRequestFacade。日uml图如下所示:


图表 5  HttpRequest类的uml图

其中HttpRequest的很多方法都是空方法,但已经可以从hhtp请求中获取headers,cookies和参数信息了。这三种数据分别以HashMap、ArrayList和ParameterMap(后面介绍)存储。

3.3.3.1  SocketInputStream类

本章的应用程序中,使用的SocketInputStream就是org.apache.catalina.connector.http.SocketInputStream。该类提供了获取请求行(request line)和请求头(request header)的方法。通过传入一个InputStream对象和一个代表缓冲区大小的整数值来创建SocketInputStream对象。

3.3.3.2  解析请求行(request line)

HttpProcessor的process调用其私有方法parseRequest来解析请求行(request line,即http请求的第一行)。下面是一个请求行(request line)的例子:

         GET /myApp/ModernServlet?userName=tarzan&password=pwd HTTP/1.1

         注意:“GET”后面和“HTTP”前面各有一个空格。

         请求行的第2部分是uri加上查询字符串。在上面的例子中,uri是:

/myApp/ModernServlet

问号后面的都是查询字符串,这里是:

userName=tarzan&password=pwd

在servlet/jsp编程中,参数jsessionid通常是嵌入到cookie中的,也可以将其嵌入到查询字符串中。parseRequest方法的具体内容参见代码。

         

3.3.3.3  解析请求头(request header)

请求头(request header)由HttpHeader对象表示。可以通过HttpHeader的无参构造方法建立对象,并将其作为参数传给SocketInputStream的readHeader方法,该方法会自动填充HttpHeader对象。parseHeader方法内有一个循环体,不断的从SocketInputStream中读取header信息,直到读完。获取header的name和value值可使用下米娜的语句:

 

java代码:
查看 复制到剪贴板 打印
  1. String name = new String(header.name, 0, header.nameEnd);   
  2. String value = new String(header.value, 0, header.valueEnd);  
  3. 获取到header的name和value后,要将其填充到HttpRequest的header属性(hashMap类型)中:  
  4. request.addHeader(name, value);  
  5. 其中某些header要设置到request对象的属性中,如contentLength等。  
3.3.3.4  解析cookie

ookie是由浏览器作为请求头的一部分发送的,这样的请求头的名字是cookie,它的值是一个key-value对。举例如下:

 

java代码:
查看 复制到剪贴板 打印
  1. Cookie: userName=budi; password=pwd;  

对cookie的解析是通过org.apache.catalina.util.RequestUtil类的parseCookieHeader方法完成的。该方法接受一个cookie头字符串,返回一个javax.servlet.http.Cookie类型的数组。方法实现如下:

 

java代码:
查看 复制到剪贴板 打印
  1. public static Cookie[] parseCookieHeader(String header) {   
  2. if ((header == null) || (header.length 0 < 1) )   
  3.     return (new Cookie[0]);   
  4.         ArrayList cookies = new ArrayList();   
  5. while (header.length() > 0) {   
  6.             int semicolon = header.indexOf(';');   
  7.             if (semicolon < 0)   
  8.     semicolon = header.length();   
  9.     if (semicolon == 0)   
  10.     break;   
  11.     String token = header.substring(0, semicolon);   
  12.     if (semicolon < header.length())   
  13.        header = header.substring(semicolon + 1);   
  14.     else   
  15.                 header = "";   
  16.     try {   
  17.        int equals = token.indexOf('=');   
  18.                if (equals > 0) {   
  19.        String name = token.substring(0, equals).trim();   
  20.                    String value = token.substring(equals+1).trim();   
  21.        cookies.add(new Cookie(name, value));   
  22.                 }   
  23. catch (Throwable e) { ; }   
  24. }   
  25. return ((Cookie[]) cookies.toArray (new Cookie [cookies.size ()]));   
  26. }  
3.3.3.5  获取参数

在调用javax.servlet.http.HttpServletRequest的getParameter、getParameterMap、getParameterNames或getParameterValues方法之前,都不会涉及到对查询字符串或http请求体的解析。因此,这四个方法的实现都是先调用parseParameter方法。

         参数只会被解析一次,因为,HttpRequest类会设置一个标志位表明是否已经完成参数解析了。参数可以出现在查询字符串或请求体中。若用户使用的GET方法,则所有的参数都会在查询字符串中;若是使用的POST方法,则请求体中也可能会有参数。所有的key-value的参数对都会存储在HashMap中,其中的值是不可修改的。tomcat中使用的是一个特殊的hashmap类,org.apache.catalina.util.ParameterMap。

         ParameterMap类继承自java.util.HashMap,使用一个标志位来表示锁定。如果该标志位为false,则可以对其中的key-value进行添加、修改、删除操作,否则,执行这些操作时,会抛出IllegalStateException异常。代码如下:

3.3.3.6  创建HttpResponse对象

HttpResponse类继承自javax.servlet.http.HttpServletResponse,其相应的外观类是HttpResponseFacade。其uml图如下所示:

图表 6  HttpResponse及其外观类的uml图示

在第2章中,HttpResponse的功能有限,例如,它的getWriter方法返回的java.io.PrintWriter对象执行了print方法时,并不会自动flush。本章的程序将解决此问题。在此之前,先说明一下什么是Writer。

         在servlet中,可以使用PrintWriter对象想输出流中写字符。可以使用任意编码格式,但在发送的时候,实际上都是字节流。

         在本章中,将要使用的是ex03.pyrmont.connector.ResponseStream类作为PrintWriter的输出流。该类直接继承自java.io.OutputStream类。

         类ex03.pyrmont.connector.ResponseWriter继承自PrintWriter,重写了其print和println方法,实现自动flush。因此,本章适用ResponseWriter作为输出对象。

         示例代码如下:

 

java代码:
查看 复制到剪贴板 打印
  1. public PrintWriter getWriter() throws IOException {   
  2.      ResponseStream newStream = new ResponseStream(this);   
  3.      newStream.setCommit(false);   
  4.      OutputStreamWriter osr =   
  5.        new OutputStreamWriter(newStream, getCharacterEncoding());   
  6.      writer = new ResponseWriter(osr);   
  7.      return writer;   
  8.    }  
3.3.3.7  静态资源处理器和servlet处理器

         本章的servlet处理器和第2章的servlet处理器类似,都只有一个process方法。但个,本章中,process方法接收的参数类型为HttpRequest和HttpResponse。方法签名如下:

         public void process(HttpRequest request, HttpResponse response);

         此外,process使用了request和response的外观类,并在调用了servlet的service方法后,再调用HttpResponse的finishResponse方法。示例代码如下:

 

java代码:
查看 复制到剪贴板 打印
  1. servlet = (Servlet) myClass.newInstance();   
  2.       HttpRequestFacade requestPacade = new HttpRequestFacade(request);   
  3.       HttpResponseFacade responseFacade = new   
  4.         HttpResponseFacade(response);   
  5.       servlet.service(requestFacade, responseFacade);   
  6.       ((HttpResponse) response).finishResponse();  

 

转载请注明出处【http://sishuok.com/forum/blogPost/list/0/4076.html】

这篇关于《深入剖析Tomcat 》第3章 连接器(Connector)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1034142

相关文章

深入解析C++ 中std::map内存管理

《深入解析C++中std::map内存管理》文章详解C++std::map内存管理,指出clear()仅删除元素可能不释放底层内存,建议用swap()与空map交换以彻底释放,针对指针类型需手动de... 目录1️、基本清空std::map2️、使用 swap 彻底释放内存3️、map 中存储指针类型的对象

深度剖析SpringBoot日志性能提升的原因与解决

《深度剖析SpringBoot日志性能提升的原因与解决》日志记录本该是辅助工具,却为何成了性能瓶颈,SpringBoot如何用代码彻底破解日志导致的高延迟问题,感兴趣的小伙伴可以跟随小编一起学习一下... 目录前言第一章:日志性能陷阱的底层原理1.1 日志级别的“双刃剑”效应1.2 同步日志的“吞吐量杀手”

深入理解go中interface机制

《深入理解go中interface机制》本文主要介绍了深入理解go中interface机制,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录前言interface使用类型判断总结前言go的interface是一组method的集合,不

深入解析Java NIO在高并发场景下的性能优化实践指南

《深入解析JavaNIO在高并发场景下的性能优化实践指南》随着互联网业务不断演进,对高并发、低延时网络服务的需求日益增长,本文将深入解析JavaNIO在高并发场景下的性能优化方法,希望对大家有所帮助... 目录简介一、技术背景与应用场景二、核心原理深入分析2.1 Selector多路复用2.2 Buffer

IDEA中配置Tomcat全过程

《IDEA中配置Tomcat全过程》文章介绍了在IDEA中配置Tomcat的六步流程,包括添加服务器、配置部署选项、设置应用服务器及启动,并提及Maven依赖可能因约定大于配置导致问题,需检查依赖版本... 目录第一步第二步第三步第四步第五步第六步总结第一步选择这个方框第二步选择+号,找到Tomca

Spring Boot从main方法到内嵌Tomcat的全过程(自动化流程)

《SpringBoot从main方法到内嵌Tomcat的全过程(自动化流程)》SpringBoot启动始于main方法,创建SpringApplication实例,初始化上下文,准备环境,刷新容器并... 目录1. 入口:main方法2. SpringApplication初始化2.1 构造阶段3. 运行阶

深入理解Go语言中二维切片的使用

《深入理解Go语言中二维切片的使用》本文深入讲解了Go语言中二维切片的概念与应用,用于表示矩阵、表格等二维数据结构,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧... 目录引言二维切片的基本概念定义创建二维切片二维切片的操作访问元素修改元素遍历二维切片二维切片的动态调整追加行动态

从原理到实战深入理解Java 断言assert

《从原理到实战深入理解Java断言assert》本文深入解析Java断言机制,涵盖语法、工作原理、启用方式及与异常的区别,推荐用于开发阶段的条件检查与状态验证,并强调生产环境应使用参数验证工具类替代... 目录深入理解 Java 断言(assert):从原理到实战引言:为什么需要断言?一、断言基础1.1 语

一文深入详解Python的secrets模块

《一文深入详解Python的secrets模块》在构建涉及用户身份认证、权限管理、加密通信等系统时,开发者最不能忽视的一个问题就是“安全性”,Python在3.6版本中引入了专门面向安全用途的secr... 目录引言一、背景与动机:为什么需要 secrets 模块?二、secrets 模块的核心功能1. 基

Go学习记录之runtime包深入解析

《Go学习记录之runtime包深入解析》Go语言runtime包管理运行时环境,涵盖goroutine调度、内存分配、垃圾回收、类型信息等核心功能,:本文主要介绍Go学习记录之runtime包的... 目录前言:一、runtime包内容学习1、作用:① Goroutine和并发控制:② 垃圾回收:③ 栈和