《深入剖析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语言的void*

《深入理解C语言的void*》本文主要介绍了C语言的void*,包括它的任意性、编译器对void*的类型检查以及需要显式类型转换的规则,具有一定的参考价值,感兴趣的可以了解一下... 目录一、void* 的类型任意性二、编译器对 void* 的类型检查三、需要显式类型转换占用的字节四、总结一、void* 的

深入理解Redis大key的危害及解决方案

《深入理解Redis大key的危害及解决方案》本文主要介绍了深入理解Redis大key的危害及解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着... 目录一、背景二、什么是大key三、大key评价标准四、大key 产生的原因与场景五、大key影响与危

深入理解C++ 空类大小

《深入理解C++空类大小》本文主要介绍了C++空类大小,规定空类大小为1字节,主要是为了保证对象的唯一性和可区分性,满足数组元素地址连续的要求,下面就来了解一下... 目录1. 保证对象的唯一性和可区分性2. 满足数组元素地址连续的要求3. 与C++的对象模型和内存管理机制相适配查看类对象内存在C++中,规

最新版IDEA配置 Tomcat的详细过程

《最新版IDEA配置Tomcat的详细过程》本文介绍如何在IDEA中配置Tomcat服务器,并创建Web项目,首先检查Tomcat是否安装完成,然后在IDEA中创建Web项目并添加Web结构,接着,... 目录配置tomcat第一步,先给项目添加Web结构查看端口号配置tomcat    先检查自己的to

Apache Tomcat服务器版本号隐藏的几种方法

《ApacheTomcat服务器版本号隐藏的几种方法》本文主要介绍了ApacheTomcat服务器版本号隐藏的几种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需... 目录1. 隐藏HTTP响应头中的Server信息编辑 server.XML 文件2. 修China编程改错误

Node.js 中 http 模块的深度剖析与实战应用小结

《Node.js中http模块的深度剖析与实战应用小结》本文详细介绍了Node.js中的http模块,从创建HTTP服务器、处理请求与响应,到获取请求参数,每个环节都通过代码示例进行解析,旨在帮... 目录Node.js 中 http 模块的深度剖析与实战应用一、引言二、创建 HTTP 服务器:基石搭建(一

若依部署Nginx和Tomcat全过程

《若依部署Nginx和Tomcat全过程》文章总结了两种部署方法:Nginx部署和Tomcat部署,Nginx部署包括打包、将dist文件拉到指定目录、配置nginx.conf等步骤,Tomcat部署... 目录Nginx部署后端部署Tomcat部署出现问题:点击刷新404总结Nginx部署第一步:打包

Nginx、Tomcat等项目部署问题以及解决流程

《Nginx、Tomcat等项目部署问题以及解决流程》本文总结了项目部署中常见的four类问题及其解决方法:Nginx未按预期显示结果、端口未开启、日志分析的重要性以及开发环境与生产环境运行结果不一致... 目录前言1. Nginx部署后未按预期显示结果1.1 查看Nginx的启动情况1.2 解决启动失败的

tomcat在nginx中的配置方式

《tomcat在nginx中的配置方式》文章介绍了如何在Linux系统上安装和配置Tomcat,并通过Nginx进行代理,首先,下载并解压Tomcat压缩包,然后启动Tomcat并查看日志,接着,配置... 目录一、下载安装tomcat二、启动tomcat三、配置nginx总结提示:文章写完后,目录可以自动

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06