Java Servlet规范
截自网址:http://blog.csdn.net/u010391029/article/details/46521051
Java Servlet Specification Version 2.3
第一章 概要
1.1什么是servlet
servlet是一种基于web组件的Java技术,由容器管理,产生动态内容。象其他基于java的组件一样,servlet是不依赖平台的的java类,被编译为中间字节码,可被动态装载运行于支持java的web服务器上。这里说的容器,有时也称它为servlet引擎,提供Servlet功能的web服务器扩展,servelt通过一种由servlet容器实现的request/response范式(paradigm)与web客户机交互。
Servlet 有以下几点优势:
- 性能明显更好。
- Servlet 在 Web 服务器的地址空间内执行。这样它就没有必要再创建一个单独的进程来处理每个客户端请求。
- Servlet 是独立于平台的,因为它们是用 Java 编写的。
- 服务器上的 Java 安全管理器执行了一系列限制,以保护服务器计算机上的资源。因此,Servlet 是可信的。
- Java 类库的全部功能对 Servlet 来说都是可用的。它可以通过 sockets 和 RMI 机制与 applets、数据库或其他软件进行交互
- Servlet 任务
Servlet 执行以下主要任务:
- 读取客户端(浏览器)发送的显式的数据。这包括网页上的 HTML 表单,或者也可以是来自 applet 或自定义的 HTTP 客户端程序的表单。
- 读取客户端(浏览器)发送的隐式的 HTTP 请求数据。这包括 cookies、媒体类型和浏览器能理解的压缩格式等等。
- 处理数据并生成结果。这个过程可能需要访问数据库,执行 RMI 或 CORBA 调用,调用 Web 服务,或者直接计算得出对应的响应。
- 发送显式的数据(即文档)到客户端(浏览器)。该文档的格式可以是多种多样的,包括文本文件(HTML 或 XML)、二进制文件(GIF 图像)、Excel 等。
- 发送隐式的 HTTP 响应到客户端(浏览器)。这包括告诉浏览器或其他客户端被返回的文档类型(例如 HTML),设置 cookies 和缓存参数,以及其他类似的任务。
1.2什么是servlet容器
servlet容器是web服务器或应用服务器的一部分,它们提供处理处理request并发送response的网络服务,解析基于MIME的request,准备基于MIME的response。servlet容器包含并管理着servlet对象的一生。
servlet容器可以被嵌入web服务器主机,或者通过服务器本地扩展API安装为web服务器的一个组件。同时,它也可以被嵌入到支持web功能的应用服务器中。
所有的servlet容器必须支持以HTTP作为request和response的协议,但额外的协议如HTTPS可以支持。应支持的HTTP最低版本为HTTP/1.0,强烈议也支持HTTP1.1规范。
servlet容器可以在其执行环境中放置安全制约。Java2平台,标准1.2或者J2EE1.3环境,应当使用由java2平台定义的许可架构下的约束。例如,高端应用服务器可以限制线程对象的创建,确保容器内的其他组件免收影响。J2SE1.2是构建servlet容器的最低版本。
1.3 例子
以下是一系列典型的场景序列:
1.客户机(即,浏览器)访问web服务器,并发送HTTP请求。
2.web服务器接收到了这个请求,并向下传递给了servlet容器。servlet容器可以与web服务器运行在同一进程里,也可以在同一主机上的不同进程里,或者运行于不同主机上。
3.servlet容器使用servlet配置信息而决定调用哪一个servlet,同时向其传递request和response对象参数。
4.servlet利用request对象得到远端用户信息,HTTP POST参数是什么,以及其他相关数据。servlet执行其编码逻辑,并产生数据,通过response对象送回客户机。
5.一旦servlet完成处理request,容器应确保response对象被flush,并将控制权返回web服务器。
1.4 和其他技术的比较
功能上,servlet居于CGI和服务器扩展(如Netscape Server API或Apache modules)之间。
但servlet还具有一些其他服务器扩展技术没有的先进性:
.它们通常比CGI脚本运行快。
.它们使用很多web服务器都支持的标准API。
.它们具有所有java编程语言具有的优点,包括开发简单平台独立等。
.它们可以访问java平台大量的API。
1.5与J2EE的关系
Servlet API v2.3 是JavaTM 2 平台, 企业版 v1.31的必需的API 。servlet容器和发布servlet必须满足其他的需求在J2EE规范中都有描述。
第二章 Servlet接口
servlet接口是servlet主要抽象的API。所有servlet都需要直接实现这一接口或者继承实现了该接口的类。servlet
API中有两个类实现了Servlet接口,GenericServlet和HttpServlet。大多数情况下,开发人员只需要在这两个类的基础上扩展来实现他们自己的Servlet。
2.1 request处理方法
servlet接口定义了service方法来处理客户机的请求。当容器将每个请求传递给servlet实例处理时都会调用该方法。为应付同时到达的请求,通常要求web开发人员编写的service方法可以多线程执行。
开发人员在不同线程内并发执行service方法来处理同时到达同一servlet的多个请求。
2.1.1 HTTP规范request处理方法
HttpServlet,实现了Servlet接口的抽象类,添加了一些附加的方法处理HTTP请求,由service方法自动调用。这些方法是:
.doGet处理HTTP GET request
.doPost处理HTTP POST request
.doPut处理HTTP PUT request
.doDelete处理HTTP DELETE request
.doHead处理HTTP HEAD request
.doOption处理HTTP OPTION request
.doTrace处理HTTP TRACE request
在开发HTTPServlet的时候,开发人员一般关注doGet和doPost方法.其他方法在HTTP开发时很少用.
2.1.2 附加方法
doGet和doPost方法允许开发人员支持HTTP1.1特性.doHead()方法实际是一种特殊的doGet方法,它只返回由doGet产生的头信息.doOption
方法返回servlet支持的所有HTTP方法的信息.doTrace方法产生的响应包含TRACE请求发送的所有头实例.
对于仅支持HTTP/1.0的容器,只需支持doGet,doPost,doHead方法.因为HTTP/1.0没有定义PUT,DELETE,OPTIONS,TRACE方法.
2.1.3 条件Get支持
HttpServlet接口定义了getLastModified方法,目的是支持有条件的GET操作.一个有条件的GET操作所请求的资源只有在指定时间内被修改了的情况下才需要发送.在某些情形下,实现该方法可以更加有效地利用网络资源.
2.2 实例的数量
servlet声明包含在web应用配置描述内,控制着容器如何提供servlet实例。因为servlet不运行于分布式环境(缺省),容器为每个Servlet声明维护一个实例.然而,当servlet实现SingleThreadModel接口时,容器可以持有多个servlet实例来应付繁重的请求,但要保证一个实例每次只处理一个请求.
当servlet被发布到分布式应用时,容器可以在每个虚拟机中为每个servlet声明持有一个实例.如果实现SingleThreadModel,可以在每个虚拟机中为每个声明持有多个servlet实例.
2.2.1 注意单线程模式
SingleThreadModel只允许某一时刻只能有一个线程执行指定的servlet实例的service方法.应该注意到,此保证只针对servlet实例而言,因为容器可以选择池化这些对象.多个servlet实例可以同时访问的对象,例如HttpSession对象,在任意时候对多个servlet都是可用的,哪怕这些servlet实现了singleThreadModel.
2.3 servlet生命周期
servlet的生命周期定义了装载,实例化,初始化,处理客户机请求,卸载等等。用API表示生命周期就是init,service,和destroy方法.
2.3.1 装载和实例化
容器负责servlet的装载和实例化.它们可以发生在容器启动时,也可以延迟到容器认为需要该servlet处理request时.
当容器启动时,容器必须可以定位所需要的servlet类,并使用Java类装载机构装载servlet类.从本地文件系统中,或从远程文件系统中或者从其他网络服务中都可以装载servlet类.
装载完成后,容器实例化该类.
servlet对象被实例化后,容器必须在使用它处理客户机的请求之前初始化这个servlet实例.初始化的意义是srvlet对象可以读取一些持久性的配置数据,或者初始化某些重量级型资源(比如JDBC连接),或是执行某些一次性的行为.容器使用init方法并传递一个单例SrvletConfig对象参数(每个servlet声明只有一个).该配置对象允许servlet访问应用配置信息中的初始化参数,同时提供了一个实现了ServletContext接口的对象来访问servlet运行环境的信息.
2.3.2.1 初始化的错误条件
在初始化期间,servlet实例可以抛出UnavailabelException或者ServletException异常.发生异常时不能将servlet放进可用服务中,容器必须释放它,因为未被成功初始化,所以无需调用destroy方法.
初始化失败后,应重新创建一个新的实例并初始化它.规则是,当UnavailabelException指示了最小不可用时间,容器必须等待这个最小时间才创建并初始化新的实例.
2.3.2.2 Tool Considerations
工具装载并内省web应用而触发类的静态初始化方法有别于调用servlet的init方法.在没有调用init方法前,开发人员不应假定sevlet是在一个活跃的servlet容器运行环境中。也就是说,servlet不应在静态初始化方法里试图建立数据库连接等.
2.3.3处理请求
当servlet被正确初始化后,容器就可以用它处理客户机的请求了.请求用ServletRequest类型的对象表示,servlet通过调用ServletResponse的对象上的方法填充该请求的响应。这些对象由service方法作为参数传递进来.
在HTTP请求中,容器提供的这些对象类型为HttpServletRequest和HttpServletResponse.
一个被容器放进服务序列的servlet实例也有可能在整个生命周期中都不处理请求.
2.3.3.1多线程
servlet容器可以向servlet的service方法发送并发请求.为了处理这些请求,开发人员必须在他们的service方法中遵守有关的多线程并发处理规则.
否则,一个可替代的办法是实现SingleThreadModel接口,它需要容器保证在service方法一次只有处理一个请求.容器通过序列化一个servlet上的请求来满足这个需求,还可以通过持有一个servlet实例池的方式.若servlet属于分布式应用的一部分,容器可以在应用分布的每个虚拟机上持有一个servlet实例池.
若servlet没有实现SingleThreadModel,而是使用synchronized关键字定义了service方法(或doGet,doPost),容器就不会使用实例池,但必须序列式地处理到达此servlet的请求,强烈建议开发人员不要同步service方法,否则会严重影响性能.
2.3.3.2 处理请求期间的异常
servlet在处理request期间可以抛出ServletException或UnavailableException异常.ServletException表明在处理request时发生了一些错误,此时容器应使用适当的方式清除此请求.
UnavailableException显示该servlet不能处理请求,有可能是暂时的,也有可能是永久性的.
如果是永久性的不可用,容器必须从服务中移除该servlet实例,并调用它的destroy()方法,然后释放实例.
如果是暂时性的不可用,容器可以在不可用期间不发送任何请求到该servlet处理.
在此期间被容器拒绝的请求,必须以SERVICE_UNAVAILABLE (503)状态码返回,同时在HTTP响应头Retry-After中指明何时服务可用.
容器也可以选择忽略这两种可用性的区别,将所有UnavailableExceptions当作永久性的,而直接移除该servlet实例.
2.3.3.3 线程安全
request和response对象不是线程安全的,这意味着它们应该只能在处理该请求南叱棠诓渴褂?
在其他线程中使用被引用的request和response对象将引起不可预期的后果.
2.3.4 终止服务
容器并不需要永久持有servlet. servlet实例在容器中可能只存在数毫秒,也可能在整个容器的生命周期中都存在(可能是几天,个月,几年)
当容器决定应该从服务中移除servlet时,它调用servlet接口上的destroy()方法,这样,servlet可以释放它所使用的资源,或者存储任何持久性的状态.
在容器调用的destroy()方法之前.必须允许正在运行service方法的线程完成执行,或者延迟运行一段服务器定义的时限.
一旦servlet实例的destroy()方法开始被调用,容器不用再发送其他请求到该servlet实例处理.即使容器需要重新激活该servlet,它也必须重新创建一个实例。
当destroy()方法调用完成后,容器必须释放该实例,以便它可以被垃圾收集.
第三章 Servlet Context
3.1 介绍ServletContext接口
ServletContext接口定义了web应用的servlet视图.容器提供者有责任提供对ServletContext接口的实现.通过ServletContext对象,servlet可以记录日志,获得资源的URL引用,存储其他servlet也可以访问的属性.
ServletContext以Web服务器中一条已知的路径为根,例如,servlet
context可以位于http://www.mycorp.com/catalog,所有以/catalog开始的请求被发送到与此ServletContext相关联的web应用.
3.2 ServletContext接口的范围
每个应用都会有一个ServletContext对象与之关联,当容器分布在在多个虚拟机上时,web应用在所分布的每个虚拟机上都拥有一个ServletContext实例.缺省情况下,ServletContext不是分布式的,并且只存在于一个虚拟机上。
3.3 初始化参数
以下ServletContext接口中的方法允许servlet访问开发人员在配置描述中定义的web应用初始化参数:
. getInitParameter
. getInitParameterNames
应用开发人员使用初始化参数传递构建信息,例如管理员的邮件地址等.
3.4 context属性
可以在context中用名称邦定一个对象属性.context中的任何属性对于同一web应用下的其他servlet都是可用的.下列方法完成了这样的功能:
. setAttribute
. getAttribute
. getAttributeNames
. removeAttribute
3.4.1 在分布式容器中的context属性
对于创建它们的虚拟机来说context属性是本地的.
这使得ServletContext属性不能以共享内存的方式存储在分布式的容器中.如果有些信息需要在不同的机器上运行的servlet之间共享的话,它们应该被存储在session中,数据库中,或EJB组件中.
3.5 资源
使用ServletContext接口可以直接访问web应用中的静态内容文档结构.包括HTML,GIF和JPEG文件.如以下方法:
.getResource
.getResourceAsStream
这两个方法的参数都是以"/"开头的字符串,表示资源相对于context根的相对路径.文档结构可以存在于服务器文件系统,或是war包中,或是在远程服务器上,抑或其他位置.
以上方法不可以用来获得动态资源,比如,getResource("/index.jsp"),这个方法将返回该jsp文件的源码,而不是动态页面.可以用"DispatchingRequests"获得动态内容.
列出web应用中可被访问的资源,可以使用getResourcePaths(Stringpath)方法。
3.6多主机和servlet context
web服务器支持在一台机器上共享一个IP的多个逻辑主机,这种能力被称为"虚拟主机",每个逻辑主机都拥有它自己的servletcontext。servlet context不能跨虚拟主机共享.
3.7 重新装载
虽然容器提供重新装载的实现对于开发有益,但不是必须的。这些实现必须确保所有的servlet、和它们所使用的类在单独的类装载器范围内被装载。此机制保证了应用正常运行。
Previous generations ofcontainers created new class loaders to
load a servlet,distinct from classloaders used to load other servlets or
classes used in the servlet context.This could cause object references
within a servlet context to point atunexpected
classes or objects, and cause unexpected behavior. The requirementis
needed to prevent problems caused by demand generation of new class
loaders.
(不确定的参考译文:
容器用来装载servlet的类装载器和ServletContext用来装载servlet和其他类的装载器不同,这导致在servlet上下文之内的对象引用指向不可预期的类或对象, 和不可预期的行为。需要防止由新的类装载器产生的问题.)
3.8 临时工作目录
每个servletcontext都有它自己的临时存储目录,容器必须为每个servlet
conetxt提供一个私有的临时目录,存储于context属性javax.servlet.context.tempdir.该对象必须是java.io.File类型.容器启动时,不需要维护该临时目录的内容,但必须确保一个servlet
context临时目录的内容对其他web应用(servletcontext)是不可见的.
第四章 Request
4.1 HTTP协议参数
servlet的请求参数是客户机发送给servlet容器的字符串,它是请求的一部分.当请求是一个HttpServletRequest对象时,且条件设置合适时,容器从URI请求字符串和POST数据中组装参数.
参数的存储为名称-值成对形式,对于同一个名称可能存在多参数值.以下ServletRequest接口方法可以访问参数:
.getParameter
.getParameterNames
.getParameterValues
getParameterValues方法返回一个字符串对象数组,包含有和指定名称关联的所有参数值.getParameter方法返回的是该数组的第一个参数值.
查询字符串和post body 包含的数据被组装到request参数集合中,query string中的数据顺序要优先于post body中的数据.比如:
如果一个请求使用查询字符串 a=hello 和postbody a=goodbye&a=world,最后的参数集顺序是a=(hello,goodbye,world).
路径参数需要使用getRequestURI和getPathInfo方法获得.
4.1.1 什么时候参数可用
POST form 中的数据被放进参数集之前,必须符合以下条件:
1.必须是HTTP或HTTPS请求
2.HTTP方法是POST
3.内容类型是application/x-www-form-urlencoded
4.servlet 调用了request对象上的getParameter之类与参数相关的方法.
如上述条件不满足的话,被POST的form中的数据不会包含在参数集中,但数据仍可以通过request对象的输入流得到。否则数据不能通过request对象的输入流得到。
4.2 属性
属性是和request关联的对象。它一般被容器用作存储不能通过API获得的信息,或者是用作与其他servlet的通讯信息(通过RequestDispatcher)。属性可以用以下方法访问:
.getAttribute
.getAttributeNames
.setAttribute
一个属性名只能和一个属性值关联。
以“java.”,"javax."开始的属性名是本规范的保留定义,同样,以"sun.","com.sun."开头的属性名是Sun公司的保留定义。建议所有的属性名以包命名规范。
4.3 头(Headers)
servlet通过以下方法访问HTTP request的头信息。
.getHeader
.getHeaders
.getHeaderNames
.getHeader方法返回与头名称对应的头信息。同一个名称可能有多个头信息对应。比如:Cache-Control头。如果这样,该方法应该 返回第一个。要取得所有信息,则使用getHeaders方法。
头信息也许是某些整型或日期型,可以直接使用下面方法读取:
.getIntHeader
.getDateHeader
如果getIntHeader方法不能将值转换为整数,将抛出NumberFormatException异常。如果.getDateHeader方法不能将值转换为日期对象,将抛出IllegalArgumentException异常。
4.4 请求路径
请求路径由许多重要的部分组成。下列元素从请求URI获得,并通过request对象暴露。
.Context
Path:和此servlet所在的ServletContext关联的路径前缀,如果context是"default"context,那么该路径为"",另外,如果该context不是root,那么路径以"/"开头,但不能以"/"结束。
.servlet path:此部分和请求的映射相关,以"/"开始,但如果该映射是"/*",那么servlet路径此时为""
.PathInfo:此部分是context path 和servletpath 的一部分。如没有额外路径它为null,否则是以"/"开始的字符串。
HttpServletRequest接口中的以下方法可以访问这些信息:
.getContextPath
.getServletPath
.getPathInfo
值得注意的是,URI和路径之间除了URL编码区别外,以下等式是成立的:
requestURI = contextPath + servletPath+ pathInfo
例子:
Context Path /catalog
Servlet Mapping Pattern: /lawn/*
Servlet:LawnServlet
Servlet Mapping Pattern: /garden/*
Servlet:GardenServlet
Servlet Mapping Pattern: *.jsp
Servlet:JSPServlet
我们发现:
Request Path PathElements
/catalog/lawn/index.html ContextPath:/catalog
ServletPath:/lawn
PathInfo:/index.html
/catalog/garden/implements/ ContextPath:/catalog
ServletPath:/garden
PathInfo:/implements/
/catalog/help/feedback.jsp ContextPath:/catalog
ServletPath:/help/feedback.jsp
PathInfo:null
4.5 路径转换方法
API中有两个方便的方法可以让开发人员获得和某个路径相关的文件系统路径:
.ServletContext.getRealPath
.HttpServletRequest.getPathTranslated
getRealPath接受一个字符串参数,并返回和该路径相关联的文件系统路径。getPathTranslated方法则可以得到该request的PathInfo对应的真实路径。下列情况下,servlet容器不能判别合法的文件路径,如,web应用以war包形式运行,或者不是本地文件路径,或者在数据库中,这些方法将返回NULL.
4.6 cookie
HttpServletRequest接口提供getCookies方法获得该request中的cookies数组。这些cookies是客户机每次发送给服务器请求中的数据,其形式为cookie名和cookie值。当cookie被送回给客户机时也可设置其他属性,比如注释,但一般是不会送回服务器的。
4.7 SSL属性
如果请求是通过安全协议传输的,如:HTTPS,那么ServletRequest接口的isScure方法可以得到。
4.8 国际化
客户机可以向web服务器提示它以何种语言接受服务器的响应.HTTP1.1规范中指出在头信息Accept-Language表示。以下方法可以得到发送者的地域信息:
.getLocale
.getLocales
如果客户机没有指定地域信息,servlet容器将会使用默认的地域信息设定。
4.9 请求数据编码
目前,许多浏览器在Content-Type头中不会指定字符编码方式,那么容器就会使用"ISO-8859-1"方式解析POST数据,而此时,为了向开发人员提示字符编码方式未指定,容器将会在getCharacterEncoding返回null.
如果客户机没有设置字符编码信息,但是request数据又以和缺省编码方式不同的方式编码,就会发生数据破坏。setCharacterEncoding(String
enc)方法可以防止这种状况发生,但是必须在解析数据或从request中读取数据之前调用。否则调用该方法不会有任何效果。
4.10 request对象的生命周期
request对象只在servlet的service方法范围内有效。或者是在filter的doFilter方法内,开发人员必须确信在范围之外引用request对象行为不会导致不可预计的后果。
第五章 Response
response对象包装了所有从服务器发送到客户机的的信息,在HTTP协议中,这些信息被分为HTTP头和消息体的两部分。
5.1 缓冲
servlet容器被允许但不被要求,缓冲发送到客户机的输出.一般情况下容器都会做缓冲,并且允许servlet定制缓冲参数。下列方法和缓冲相关。
.getBufferSize
.setBufferSize
.isCommitted
.reset
.resetBuffer
.flushBuffer
这些方法对ServletOutputStream和Writer有效。
getBufferSize返回正在使用的缓冲大小,如果没有使用缓冲,返回整数0。可以通过setBufferSize设定缓冲大小,缓冲分配的ad校可以与servlet请求的不同,但至少应不比所请求的小。此方法应在调用ServletOutputStream或Writer向客户机发送数据之前调用。如果有任何数据已经被发送,此方法将抛出IllegalStateException异常.
isCommitted方法返回一个boolean值显示是否有任何response字节被发送回客户机。flushBuffer强制将buffer中的内容发送给客户机。reset方法在response还没有被commit以前将buffer的内容清空,头信息和状态码也被清除。resetBuffer则只清除buffer内容。
如果response已经被commit,调用reset和resetBuffer将抛出IllegalStateException异常,同时response对象不会发生改变。
一旦数据被开始发送给客户机,response对象就被认为是committed状态。
5.2 头
servlet用下面的方法设置HTTP头信息。
.setHeader
.addHeader
setHeader方法会替换已经被设置的值,而addHeader则会添加一个值到同名头的值集合中。
为保证成功地将头信息传送到客户机,必须在response对象被提交以前,设置HTTP头信息。
servlet开发人员有责任确保Content-type头被正确设置,HTTP1.1规范并不要求HTTPresponse中设置该头。当开发人员没有设置该值, 容器也不要设置缺省值。
5.3 方便的方法
.sendRedirect
.sendError
sendRedirect方法会自动设置合适的头与内容,引导客户机跳转到另一个URL去。
使用相对路径作为参数也是合法的,但容器必须将相对路径转换为绝对路径送回客户机。如果由于某些原因不能转换,必须抛出IllegalArgumentException异常.
sendError方法自动设置返回客户机的头信息和错误信息,一个可选的字符串类型的变量,可用来显示错误信息的内容部分。
这两个方法对于response的commit都有影响,如response还没被提交,将终止提交。这些方法后调用的输出都将被忽略。
若数据被写进缓冲,但还没有发送到客户机,这些数据将被这两个方法发送的数据代替,如果response已经被提交,将抛出 IllegalStateException异常.
5.4 国际化
setLocale方法可以设置response对象的语言属性,此方法必须在开发人员调用getWriter之前使用。
注意。setContentType方法将覆盖setLocale设定的值。
5.5 response对象的关闭
当response被关闭时,容器必须立即发送所有缓冲中的数据到客户机,下列情形显示response对象被关闭了。
.servlet的service方法结束。
.当发送完由setContentLength设定的长度的内容后。
.调用了sendError方法。
.调用了sendRedirect方法。
5.6 response对象的生命周期
每个response对象只在service方法范围内可用,或者是filter的doFilter方法内。容器通常会回收response对象以避免频繁创建response对象造成的性能过载。开发人员在可用范围外引用response对象时,必须确保不会引起不可预知的行为。
第六章 filter
Filter是规范2.3新提供的特性,它使得请求与响应过程的信息转换很敏捷。
6.1 什么是Filter
Filter是一段可重用的,转换HTTP请求,响应内容和头信息的代码。Filter通常不象servlet那样会创建response或对请求作出响应。它们只是修改对资源的请求或对资源的响应。
Filter对静态和动态资源都可以起作用。filter的一般作用:
.在request到达它调用的资源之前访问该资源。
.在request到达它调用的资源之前处理该资源。
.修改request头和数据,通过重新包装request对象。
.修改response头和数据,通过重新包装response对象。
.拦截监听资源的调用。
.以一定顺序对一个或一组servlet或者静态资源作用。
6.1.1 Filter的例子
.认证filter
.日志与审核filter
.图像转换filter
.数据压缩filter
.加密Filter
.令牌filter
.出发资源访问事件的filter
.转换XML文档的XSLT filter
.MIME-type 链filter
.缓存filter
6.2 主要概念
开发人员实现javax.servlet.Filter接口来创建filter,同时提供一个公共无参数的构造函数。该类应该被包含在web应用中。在发布文件中,以“filter”,"filter-mapping"元素声明配置。
6.2.1 filter 生命周期
web应用发布完成之后,在request访问web资源之前,容器必须定位需要应用到该资源上的filter列表。容器需要确保实例化列表中的每个filter并调用它的init方法。如不能正确执行,filter可以抛出异常。
配置描述文件中每个filter的声明在每个虚拟机中仅仅只有一个实例。容器提供filter配置秘书中声明的config对象, 它包含了该web应用的ServletContext引用,和filter初始参数。
当容器接收到request,将取得列表中的第一个filter实例,并调用它的doFilter方法。将传入ServletRequest,ServletResponse和filterchain的引用。
doFilter方法遵循以下模式:
1.该方法检测request的头信息
2.可能会用一个定制的ServletRequest或HttpServletRequest包装request对象。
3.可能会用一个定制的ServletResponse或HttpServletResponse包装response对象。
4.filter可以调用filter链中的下一个入口,可能是一个filter,也可能是目的web资源。
5.调用链中的下一个filter之后,filter可以检测响应头信息。
6.处理过程中,filter可能抛出异常。如果在doFilter中抛出UnavailableException,容器不要继续filter链的处理,如飞永久性异 常,它可以选择在稍晚的时候重新执行整个filter链。
6.2.2 包装request和response
filter的核心概念就是包装request和response对象,以便于执行filter任务时,覆盖其行为。在此模型中,开发人员不仅能够覆盖request和response对象上已有的方法,还可以为某个特定的过滤任务在整个链上提供新的API。例如,开发人员可能想在response对象上使用比out流和writer更高级的输出对象,例如,允许DOM对象写回客户机的API。
为了支持这种类型的filter,容器必须满足下列需求。当一个filter调用filter
chain的doFilter方法时,容器必须确保传到下一Filter或web资源入口的request和response对象,和调用filter的doFilter方法传入的是同一个对象。
同样的,被包装的request、response对象和传入的也必须是一致的。
6.2.3 filter环境
配置描述中可以使用一系列参数初始化filter,描述符为"init-param",filter在运行时可以使用FilterConfig对象上的getInitParameter和getInitParameterNames方法得到参数名和值。FilterConfig也提供对ServletContext的访问。
6.2.4 在web应用中配置filter
在配置描述中,filter以filter元素定义。
filter-name:用来映射该filter到一个servlet或者URL
filter-class:容器用来识别filter类型
init-params:初始化参数
图标,文本描述,显示名称都是可选项描述,主要给县骨干工具软件使用。对于每个filter声明,容器必须正确地实例化一个filter对象,如果对于一个filter类定义了多个声明,容器将会生成多个实例。
例子:
filter声明:
<filter>
<filter-name>ImageFilter</filter-name>
<filter-class>com.acme.ImageServlet</filter-class>
</filter>
filter声明后,应使用filter-mapping元素定义该filter要应用的servlet或是静态资源。
<filter-mapping>
<filter-name>ImageFilter</filter-name>
<servlet-name>ImageServlet</servlet-name>
</filter-mapping>
filter也可以使用URL匹配模式和一组servlet或静态资源关联起来。
<filter-mapping>
<filter-name>LoggingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
此处,Loggingfilter被应用到applicationlide所有servlet和静态资源上了,因为所有请求URI都匹配"/*"。
当用url-pattern处理filter-mapping时,容器必须用路径映射规则判断所请求的URI是否匹url-pattern。
容器针对某个被请求的URI构建的filter链的顺序为:
1.首先是url-pattern匹配的filter,如有多个,其顺序是配置描述的顺序。
2.然后是servlet-name匹配的filter,如有多个,其顺序是配置描述的顺序。
以上两点意味着容器:
.根据匹配规则11.2确定被访问的目标Web资源
.如果通过servlet名称匹配到了filter,且web资源拥有servlet名称,容器按照配置描述的顺序构建filter链。链的底部就是最后一个通过servlet名称匹配的filter,并且由它调用目的web资源。
.如果有url-pattern匹配的request
URI的filter,容器以配置描述中的顺序构建url-pattern匹配的filter链.链的底部是最后一个url-pattern匹配到的filter,并且该filter将调用第一个servlet-name匹配的filter,如没有则直接调用目的web资源.
若希望提高容器性能,最好缓存filter链,以免每次请求都要重新计算.
第七章 会话 sessions
超文本传输协议(HTTP)被设计成无状态的协议.为了构建有效率的web应用,将从某个特定客户机上发送来的request互相关联起来是很必要的,会话跟踪的技术已经发展了很长时间,但对于开发人员直接使用来说都是很困难的.
本规范定义了HttpSession接口,允许servlet容器使用好几种方式跟踪用户会话,而开发人员不会陷入不同方式的细节.
7.1 会话跟踪机制
通过HTTPcookie跟踪会话时比较常用的办法,所有servlet容器都要求实现这一技术.
容器发送cookie到客户机,客户机在接下来的请求中都将该cookie返回给容器.这样就很清楚地将请求和会话关联起来,用于会话跟踪的cookie名称一定是JSESSIONID.
7.1.2 SSL Session
7.1.3 URL重写
URL重写式会话跟踪的最低公分母?,当客户机不接受cookie时,server就使用URL重写作为会话跟踪的基本方式.URL重写,添加了附加数据(会话ID)到请求的URL路径上.
会话ID必须被编码作为该URL字符串中的路径参数。该参数的名称为jsessionid,例如:
http://www.myserver.com/catalog/index.html;jsessionid=1234
7.1.4会话完整性
Web容器必须能支持HTTP会话,当处理不支持cookie使用的客户机发送来的HTTP请求时,web容器通常使用URL重写机制。
7.2 创建会话
一个会话被认为是新的,只有当它是唯一一个预期会话并还没没有被建立。因为,HTTP是基于request-response的协议,HTTP会话被认为是新的,直到有客户机加入它.当会话跟踪信息返回服务器时,意味着一个会话已经被建立了,此时,客户机加入了会话.直到客户机加入会话为止,它都不能假定下一个来自该客户机的请求隶属于某个会话的一部分.只要下列条件成立,会话被认为是新的:
.客户机还不知道该会话
.客户机选择不加入会话
这些情况定义了servlet容器没有机制将一个请求和上一个请求关联起来.
servlet开发人员必须将它的应用设计为可以处理客户机没有,不能,不会加入会话的情形.
7.3 会话范围
HttpSession对象在应用级别(servlet context)范畴.
下列机制,例如cookie,对于不同context可以是相同的,但该对象引用,包扩属性,容器必须按context区别开来.
用一个例子来说明:如果一个servlet使用RequestDispatcher调用属于另一个web应用的servlet,任何对于执行调用的servlet创建的,可 见的会话信息,必须不同于被调用的servlet.
7.4 在会话中绑定属性
servlet可以通过名称绑定对象属性到HttpSession的实现中.任何绑定对象对于同一servlet context下在同一会话中处理请求的其他servlet都是可用的.
会话中的一些对象被放入或是移出时,可能需要获得通知,这可以通过实现HttpSessionBindingListener接口的对象获得.该接口定义了下列方法,当对象被绑定或解除绑定时,发出信号.
.valueBound
.valueUnbound
valueBound方法必须在该对象通过执行HttpSession的setAttribute方法而可用之前被调用valueUnbound方法必须在该对象通过执行HttpSession的setAttribute方法而不再可用之前被调用.
7.5 会话超时
在HTTP协议中,客户机不再活跃时没有明确的终止信号.这意味着只有超时才是标识客户机不再活跃的唯一机制.
缺省的会话超时由servlet容器定义,在HttpSession接口上调用getMaxInactiveIntervalfangaf得到该值.开发人员可以使用HttpSession接口上的setMaxInactiveInterval方法改变该值.时间单位是秒.若设置为-1,表示该会话永不过期.
7.6最后访问时间
HttpSession接口上的getLastAccessedTime方法允许servlet判定该会话在本次请求前被访问的最后时间.当容器第一次处理隶属于该会话的请求时,认为该会话被访问.
7.7 重要的会话语义
7.7.1 线程
多个servlet执行请求线程也许同时使用一个会话对象,开发人员有责任同步对会话资源的访问.
7.7.2 分布式环境
在分布式应用中,所有隶属于某个会话的请求在某一时间必须同一个虚拟机处理,容器必须能适当地处理使用setAttribute或putValue方法放进HttpSession实例的所有对象.下列约束是强制的:
.接受的对象应实现Serializable接口
.容器可以选择支持在HttpSession中存储其他指定的对象,例如:EJB和事务
.移动会话将由容器具体设施处理
若放进会话的对象未实现Serializable接口,或对该类对象的支持还不可用,servlet容器可以抛出IllegalArgumentException异常,只有在容器不能支持移动会话时抛出该异常.
这些约束表明开发人员被保证没有发生在非分布式容器之外的并发问题.
通过拥有移动会话对象的能力,从分布式系统中的任一个活跃节点移动到系统中的另一个节点,容器提供者能保证服务的可伸缩性和品质,负载均衡和故障转移等特性.
如果分布式容器坚持或移动会话来提供服务特性的品质,就不强求在序列化HttpSession和它们的属性时使用本地虚拟机的序列化机制.容器不会保证调用会话属性上的readObject和writeObject方法.但是保证它们的属性可序列化关闭将被保存.
在移动会话期间,容器必须通知实现了HttpSessionActivationListener接口的会话属性.通知钝化监听器必须优先于序列化监听器,活化则要迟于会话反序列化.
应用开发人员编写分布式应用,应该明白因为允许容器运行在多个java虚拟机上,他就不能依赖静态变量来存储应用程序状态.应使用EJB或数据库.
7.7.3 客户机语义
实际上cookie和SSL认证都是由web浏览器控制的,而不是和浏览器的某个特定窗口相关联,也许从所有窗口发送到servlet容器的请求是属于同一个会话.为了尽量方便,开发人员总是假定客户机的所有窗口参加的是同一个会话.
八 转发请求
使用RequestDispatcher接口,向另一个servlet转发请求,或者在response中包含另一个servlet的输出.
8.1 获得RequestDispatcher
从ServletContext上可以获得RequestDispatcher的实现对象:
.getRequestDispatcher
.getNamedDispatcher
getRequestDispatcher方法的参数为一个字符串变量,描述在ServletContext范围内的路径.该路径必须相对于ServletContext的根.并且以"/"开始.该方法使用此路径寻找到servlet后,用一个RequestDispatcher对象包装返回.如果在该路径上没有找到对应的servelt,一个包装了该路径内容的RequestDispatcher被返回.
getNamedDispatcher方法参数为一个字符串变量,它表示ServletContext内已知servlet的名称,如找到该servelt,用RequestDispatcher对象包装后返回,如未找到,返回null。
使用相对于当前请求的相对路径也可以获得RequestDispatcher对象,以下方法在ServletRequest接口提供:
.getRequestDispatcher
该方法的行为类似于ServletContext中同名函数,servlet容器使用request对象中的信息转换给定的相对路径为完整的路径。例如:
在context“/”和一个到“/garden/tools.html”的请求,此时,调用request上的getRequestDispatcher("header.html")方法效果等同于ServletContext.getRequestDispatcher("/garden/header.html")。
8.1.1 转发请求路径里的查询字符串
ServletContext和ServletRequest里使用路径参数获得RequestDispatcher的方法,允许添加查询字符串到该路径上。例如,开发人员用以下代码:
String path =“/raisons.jsp?orderno=5”;
RequestDispatcher rd =context.getRequestDispatcher(path);
rd.include(request, response);
查询字符串里指定的参数,顺序要优先于同名的其他被传进的参数。该参数与RequestDispatcher相关联,只在include或forward调用中有效。
8.2 使用request Dispatcher
使用requestDispatcher,调用该接口上的include或forward方法。参数可以是通过service方法传入的request,response对象,也可以是它们被包装后的对象。
容器提供者应确保将请求分发到目的servlet必须发生在原始请求所在的虚拟机上。
8.3 include方法
该方法必须可以在任意时刻调用,目标servlet的include方法可以五约束地访问request对象,但在访问response对象上有一些限制。它只能向response对象的ServletOutputStream或Writer写入信息,在response的缓冲写完后提交response,或直接调用flushBuffer方法提交response,但不能设置头信息,或者调用任何可能改变头信息的方法。任何此类操作将被忽略。
8.3.1 Included参数
除了使用getNamedDispatcher获得的servlet外,a servletbeing used from
within an include has access to the path by which it wasinvoked.
以下request属性被设置:
javax.servlet.include.request_uri
javax.servlet.include.context_path
javax.servlet.include.servlet_path
javax.servlet.include.path_info
javax.servlet.include.query_string
被包含的servlet可以调用getAttribute访问这些属性。
以getNamedDispatcher方式获得的被包含servlet不能得到这些属性。
8.4 Forward方法
在负责调用的servlet还没有向客户机提交输出的时候,才可以调用RequestDispatcher上的Forward方法。在目标servlet的service方法调用前,如果response缓冲中存在还没提交的数据,这些缓冲内容必须被清空,如果response已经提交,将抛出IllegalStateException异常。request对象暴露给目的servlet的路径元素必须反射用于获得RequestDispatcher路径。唯一的例外是,如果RequestDispatcher是通过
getNamedDispatcher方法获得的。
在forward方法返回前,response内容必须被发送和提交,并被容器关闭。
8.4.1query string
在forward或include请求的时候,请求转发机制负责组合查询字符串。
8.5 错误处理
如果requestdispatcher的目标servlet扔出运行时错误或一个被检查类型的意外ServletException或IOException,它应当被传播到调用它的servlet。所有其他异常则应被包装为ServletException,被传播以前,异常引起的最初原因应被设置到原始异常。
第九章 web 应用
web应用是web server中某个特定的路径。例如,一个catalog应用可能在http://www.mycorp.com/catalog.所有以此路径开始的请求都被发送到catalog应用处理。
servlet容器能建立自动产生web应用的规则,例如,~user/ 可以映射到基于/home/user/public_html/的一个web应用。
默认情形下,在任意时间内,一个web应用的实例必须运行在一个虚拟机上。但如果是分布式应用,这是可以改变的,分布式应用需要遵循一套更严格的规则,这些规则不在本规范内。
9.2 和ServletContext的关系
servlet 容器在web应用和Servletcontext之间必须强制一对一的对应关系。ServletContext对象相servelt提供了整个应用视图。
9.3 web应用的元素
web应用可以由以下元素构成:
.servlet
.JSP页面
.工具类(Utilityclass)
.静态文档(html,image,sound等)
.客户端javaapplet,beans,和类
.组合所有以上元素的描述元信息
9.4发布层次
本规范定义了一个层次结构,用于发布和打包,可以文件系统形式存在,也可以以压缩包文件形式。建议servlet容器支持此结构为运行时表示。
9.5 目录结构
web应用以结构化层次目录形式存在。该层次结构的根为文档的根。例如,context路径为/catalog的web应用,应用根路径下的index.html文件可以用/catalog/index.html请求来访问。映射的规则在第11章有叙述。因为应用的context路径决定了内容的URL名字空间,web容器必须拒绝可能引起URL名字空间潜在冲突的context定义。这有可能发生,例如试图以相同的context路径发布另一个web应用,或者其中一个context路径是另一个context的一部份。匹配是大小写敏感的,所以,冲突检测也是一样的。
应用层次中有个特殊的目录叫“WEB-INF”,此目录包含了不在应用根文档的所有东西。WEB-INF节点不是应用文档树的公开部分,不能被客户机直接访问。但该目录的内容对于servlet代码是可见的,可使用ServletContext接口上的getResource或getResourceAsStream方法访问。因此,开发人员如果需要在servlet代码访问,又不希望暴露给客户机的信息,他可以放置在此目录。WEB-INF目录的内容有:
./WEB-INF/web.xml
./WEB-INF/classes/目录存储servlet和应用类,这些类必须可以对applicationclass loader是可用的。
./WEB-INF/lib/*.jar存储java存档文件,这些文件包含servlet,bean,和其他可用类。applicationclass loader必须也能装载这些存档文件中的类。
装载次序是先WEB-INF/classes目录,再WEB-INF/lib目录。
9.6 web应用存档文件
web应用可以使用标准java存档工具打包为war文件.
9.7 web 应用配置描述
应该包含以下内容:
.ServletContext 初始化参数
.会话配置
.Servlet / JSP定义
.Servlet / JSP映射
.MIME类型映射
.Welcome文件列表
.Error页面
.Security
9.7.1 扩展依赖
当大量的应用使用相同的代码和资源,它们在容器中一般被安装为库文件。这些文件通常是通用或标准的API.
应用开发人员需要知道web容器上安装了什么扩展部件,容器则需要知道servlet依赖什么样的库。web容器应有一种机制,让web应用可以知道JAR文件包含的资源与代码是可用的,并使它们为应用所用。容器应当提供一种简便的过程可以编辑配置库文件或是扩展部分。
推荐开发人员在war文件中使用META-INF/MANIFEST.MF列出扩展部分。若web容器不能满足这种方式声明的依赖,它必须拒绝应用程序,并给出错误信息。
9.7.2 web应用classloader
容器用来装载WAR中的servlet的Classloader必须允许开发人员以J2SE语义使用getResource装载任何包含在war中JAR库内的资源。必须防止war覆盖J2SE或javaservlet
API类。该classloader应该不允许servlet访问web容器的实现类。推荐让应用类装载器优先装载WAR中的类和资源,再装载JAR中的类和资源。
9.8 替换web应用
服务器应该能够在不重新启动容器的情况下替换一个web应用。当应用被替换时,容器应当保持会话数据。
9.9 错误处理
9.9.1 request属性
web应用必须能够指定错误发生时应用使用的其他资源,这些资源的规范在配置描述中叙述。
如果错误定位为servlet或JSP页面,下列request属性必须设定:
Request Attributes Type
javax.servlet.error.status_code java.lang.Integer
javax.servlet.error.exception_type java.lang.Class
javax.servlet.error.message java.lang.String
javax.servlet.error.exception java.lang.Throwable
javax.servlet.error.request_uri java.lang.String
javax.servlet.error.servlet_name java.lang.String
这些属性允许servlet产生一个描述详细错误信息的页面。
9.9.2 错误页面
当sertvlet产生错误时,为了允许开发人员定制返回客户机的页面,配置描述文件中定义了一系列错误页面的描述。该语法允许容器返回配置的资源,或者当servlet设置状态码来表明response的错误,或者servlet产生传播到容器的异常和错误.
若在response上设置了表明错误的状态码,容器参考错误页面声明的列表,试图取得一个匹配的结果.
servlet处理请求时可以抛出下列异常:
.runtime exceptions or errors
.ServletExceptions orsubclasses thereof
.IOExceptions or subclassesthereof
web应用也可以使用exception-type元素声明错误页面.容器通过比较所抛出的异常和错误页面中exception-type元素的定义,返回匹配的资源.在类层次最接近的将获得匹配.如果得不到匹配,将抛出ServletException或subclass
thereof,容器将展开被包装的异常.A second pass is made over the errorpage
declarations,again attempting the match against the error
page declarations,but using thewrapped exception instead.
exception-type元素必须是exception-type的class名称唯一.类似的,status-code元素的状套码也必须是唯一的.
错误页面机制不回干扰使用RequestDispatcher调用的servlet发生错误.
如果servlet产生的错误没有错误页处理,容器将会产生状态码为500的response.
9.10 WelCome file
开发人员可以在配置描述文件中定义一个welcaome文件的顺序列表.本机制的目的是允许开发人员制定目录入口的缺省资源,如目录缺省资源未定义,那么到目录入口的请求将返回一个404的response.
9.11 web应用环境
J2EE定义了命名环境,允许应用很容易地访问资源和外部信息,webu需要显式了解外部信息具体是如何命名和组织的.
正如servlet是J2EE技术的一个组件,也制定允许servlet获得资源和EJB的引用的规范,这些元素是:
.env-entry
.ejb-ref
.ejb-local-ref
.resource-ref
.resource-env-ref
( 本节略)
第十章 应用生命周期事件
10.1 介绍
本规范支持应用级别的事件.这些应用事件给了开发人员更大的控制能力,比使用ServletContext和HttpSession对象.
10.2 事件监听器
应用事件监听器是实现了一个或多个和servlet 事件监听接口的类,在web应用被发布的时候被实例化并在web容器中注册。它们应由开发人员提供。
Servlet事件监听器支持当ServletContext和HttpSession对象的状态改变时,进行事件通知。servlet
context监听器用来管理应用程序的虚拟机级的资源和状态。HTTP会话监听器用来管理应用程序中与某个客户机或用户关联的一些列请求的资源与状态。
对于每种事件类型,可能有多个监听器监听它们。开发人员也可以指定容器调用监听器的顺序。
10.3 事件类型与监听器接口
事件类型 描述 监听器接口
__________________________________________________________________________________________
Servlet Context
事件
生命周期 servletcontext 刚刚被创建 javax.servlet.ServletContextListener
它准备好处理它的第一个请求
或者context即将被关闭
属性改变 servlet context上的属性已经 javax.servlet.ServletContextAttributesListener
被添加,删除,替换
____________________________________________________________________________________
HTTP会话事件
生命周期 HttpSession对象已经被创建, javax.servlet.http.HttpSessionListener
实效,或过期
属性改变 HttpSession对象上的属性已经 javax.servlet.HttpSessionAttributesListener
被添加、删除或替换
10.2.2 使用监听器的例子
为举例说明事件的使用,我们假定一个简单的web应用,包含很多用到数据库的servlet。开发人员已提供了一个servlet context监听器管理数据库联接。
1.当应用启动,监听器类被通知,应用登陆数据库,并将联接存储在servlet context内。
2.web应用中的servlet可以在需要的时候访问该联接。
3.当web服务器关闭,或者web应用被删除,该监听器被通知,接着数据库联接被关闭。
10.3 监听器类配置
10.3.1 提供监听器类
应用开发人员听实现以上四个接口的监听器类,每个类必须有不带参数的公共构造函数。该类被打包进WAR中,也可以放在WEB-INF/classes目录下或者是WEB-INF/lib下的JAR中。
10.3.2 发布声明
在web应用配置描述文件中,使用listener元素声明监听器类。以类名按照调用的顺序被列出。
10.3.3 注册监听器
web容器在应用处理第一个请求前,创建每个监听器的实例并注册它。容器根据监听器实现的接口以及在配置描述文件中的顺序注册实例。容器以监听器注册的顺序执行它们。
10.3.4 关闭时通知
当应用被关闭时,监听器以声明时相反的顺序得到通知,,先处理会话监听,然后是context监听.
10.4 Example
下面例子是关于如何注册两个servlet context 监听器和一个 会话监听器的语法.
假定com.acme.MyConnectionManager和com.acme.MyLoggingModule都实现了javax.servlet.ServletContextListener,并且com.acme.MyLoggingModule
还实现了javax.servlet.HttpSessionListener,以下是配置描述:
<web-app>
<display-name>MyListeningApplication</display-name>
<listener>
<listener-class>com.acme.MyConnectionManager</listenerclass>
</listener>
<listener>
<listener-class>com.acme.MyLoggingModule</listener-class>
</listener>
<servlet>
<display-name>RegistrationServlet</display-name>
</servlet>
</web-app>
10.5 监听器实例与线程
容器需要在应用程序处理第一个请求之前,完成实例化监听器类.在完成最后一个服务请求之前,容器必须维护每个监听器实例的引用.
ServletContext和HttpSession属性的改变是可以并发的.对属性监听器通知,容器不需要同步.维持状态的监听器类有责任保持数据的完整性,并应当明确地处理这种情况.
10.6 分布式容器
在分布式web容器中,HttpSession实例存在于处理该会话的请求的特定虚拟机范围,ServletContext对象的范围则在web容器所在的虚拟机内.分布式容器不需要将servlet
context和session上的 事件传播到其他虚拟机.监听器实例范围则在每个虚拟机上的每个部署声明对应一个.
10.7 会话事件
开发人员使用监听器类跟踪应用会话.它的作用很大,想知道哪个会话因容器使会话超时,或是因为被调用了invalidate方法而失效了。区别在于是直接使用HTTPSession的API还是间接使用监听器。
第十一章 映射请求到Servlet
收到客户机的请求,web容器首先判断由哪个web应用处理它。被选定的应用拥有最长的匹配该请求URL的context路径,和URL的匹配部分就是contextpath.
web容器接下来必须使用路径映射过程定位处理请求的servlet:
用来映射到servlet的路径是请求的URL减去contextpath,下列的URL映射规则其顺序是有用的,一个成功的匹配后就不需要进行下一步的匹配了:
1.容器尝试找到请求路径到servlet路径的精确匹配,成功就选定该servlet。
2.容器将回退尝试匹配最长的路径前缀:使用"/"字符作为路径符,每次沿着路径向下一步查找目录。按最长的那个匹配选择servlet.
3.如果URL最后的那段包含扩展名(即.jsp),servlet容器将试图匹配一个处理该扩展名的servlet。扩展名是最后一个"."后面的这部分。
4.如果上述都没有找到匹配的servlet,容器尝试servecontent appropriate for the resource requested.如“default"servlet定义了,就使用default servlet。
11.2 映射规范
以下语法用于在应用配置描述中定义映射:
.以'/'开头,'/*'结尾的后缀被用来路径映射
.以'*.'开始的前缀被用来映射扩展名
.只包含'/'的字符串表示应用的缺省servlet,此时,servlet path是请求的URI减去context path,path info 为null.
.所有其他字符串只是被用来做精确匹配.
11.2.1 隐含的映射
若容器拥有内部的JSP容器,所有"*.jsp" 扩展被映射到它,允许执行JSP页面。这就是隐含映射。如Web 应用定义了*.jsp映射,将优先于隐含映射。
只要显式映射优先,servlet容器也可以做其他隐含映射。例如:*.shtml可以映射用来包含。
11.2.2映射例子
path pattern servlet
/foo/bar/* servlet1
/baz/* servlet2
/catalog servlet3
*.bop servlet4
incoming path servlethandling request
/foo/bar/index.html servlet1
/foo/bar/index.bop servlet1
/baz servlet2
/baz/index.html servlet2
/catalog servlet3
/catalog/index.html “default”servlet
/catalog/racecar.bop servlet4
/index.bop servlet4
第十二章 安全
开发人员创建出web应用以后,将其赠与、销售或者传送到发布者,并安装到运行环境中。开发人员需要和发布者沟通如何给安装的应用程序
设置安全。利用配置描述机制完成此项工作。
本章描述了安全需求的发布表示。和web应用目录结构与配置描述类似,本章并不描述运行表现需求。推荐容器实现本章作为它们的运行表示。
12.1 介绍
web应用包含的资源可以被很多用户访问。在开放网络特别是internet,这些资源通常可以被无保护的访问。在这样的环境下,大量的web应用都有安全需求。
虽然质量管理和实施细节可能不同, servlet容器的机制和基础设施可以达到分享一些以下要求:
.认证(Authentication:):通信的个体互相证明他们就代表着被批准为访问的那个指定的身分。
.资源访问控制:出于增强完整性、机密性、有效性的约束,与资源的交互被限制在某些用户或程序的集合内。
.数据完整性:证明数据在传输过程中未被第三方篡改。
.机密性和数据保密:信息只对被批准访问它的用户可用。
12.2 声明安全
声明安全涉及表示应用安全结构的方式,包括角色,访问控制,外部对应用认证需求形式。配置描述是声明web应用安全的主要工具。
发布者映射应用的逻辑安全需求到针对运行环境特定的安全策略。运行时,servlet容器使用安全策略增强认证和授权。
安全模型适用于web应用的静态内容部分和servlet。当servlet使用RequestDispatcher对象调用静态资源或servlet使用forward或include,安全模型是不应用的。
12.3 安全编程
当只使用声明性的安全措施不能满足应用安全模型的需要时,可进行安全方面的编程。由下列HttpServletRequest接口的方法组成:
.getRemoteUser
.isUserInRole
.getUserPrincipal
getRemoteUser方法返回客户机用来认证的用户名称。isUserInRole方法判断是否远程用户隶属于某个安全角色。getUserPrincipal方法
判断当前用户的Principal,并返回一个java.security.Principal对象。这些方法允许servlet根据获得的信息作出商业逻辑层的判断。
如果用户没有通过认证,getRemoteUser方法返回NULL.isUserInRole则总是返回false,getUserPrincipal方法返回null。
isUserInRole方法有一个字符串类型的参数代表用户角色名称,配置描述中应声明security-role-ref元素,同时声明role-name子元素,此元素的内容将被传递进该方法。
security-role元素应包含一个role-link子元素,其值就是此用户被映射的安全角色名称。决定调用的返回值时,容器使用security-role-ref到security-role的映射。
例如:映射安全引用"FOO"到角色名为"manager"的安全角色的语法如下:
<security-role-ref>
<role-name>FOO</role-name>
<role-link>manager</manager>
</security-role-ref>
此例中,如属于“manager”角色的用户调用了此servlet,isUserInRole("FOO")方法将返回true。
如果security-role-ref匹配security-role元素未被声明,容器必须默认逆着security-role元素列表检查role-name元素参数。
isUserInRole方法则相关于调用者是否映射到了安全角色。开发人员必须知道使用默认机制也许限制了其灵活性,在改变角色名称而不必要重新编译发起调用的servlet。
12.4 角色
安全角色是由应用开发人员或集成者定义的逻辑用户组。当发布应用时,角色釉发布者映射到运行环境的principal或组。
Servlet容器根据principal的安全属性,为与请求相关的principal执行声明的或编程的安全。
1.发布者已经映射了一个安全角色到操作系统内的一个用户组。正在调用的用户组隶属于哪个principal,可在其安全属性中检索到。
当principal的用户组匹配映射了安全角色的用户组时,principal隶属于安全角色。
2.发布者在安全策略域中已经为principal名称映射了安全角色。在这种情况下,可从其安全属性中检索到正在调用的principal的名称.
只有当principal的名字和安全角色映射的principal名字一样时,principal隶属于安全角色。
12.5 认证
web 客户机使用下列机制向服务器认证用户:
.HTTP基本认证
.HTTP摘要认证
.HTTPS客户端认证
.基于Form认证
12.5.1 HTTP基本认证
HTTP基本认证,基于用户名和密码,是HTTP/1.1规范定义的认证机制。Web服务器要求web客户端认证用户。作为请求的一部分,web服务器传递"领域"(字符串),包含被认证的用户信息。用于基本认证的领域字符串并不附加任何特殊安全策措施。Web客户机将用户名和用户密码传给服务器。然后,web服务器在指定的领域内认证用户。
基本认证并非安全的认证协议,用户密码以简单的base64编码发送。并且目标服务器也不需要被认证。额外的保护可以减轻一些安全忧虑:比如安全传输机制(HTTPS) ,或者网络层安全(IPSEC协议或VPN策略)都是经常被使用的。
12.5.2 HTTP摘要认证
象HTTP基本认证一样,HTTP摘要认证也用用户名和密码的形式给用户授权。但认证的密码被加密,当然比HTTP基本认证使用的base64编码安全得多。HTTPS客户端认证,象摘要认证一样,目前不被广泛使用,servlet容器支持它不是必须的,但值得鼓励。
12.5.3 基于Form的认证
使用“登陆屏幕”这种内建在web浏览器内的认证机制看上去感觉缺乏变化。本规范介绍介绍基于form认证机制,它允许开发人员控制登陆屏幕的外观。
web应用配置描述包含登陆form和错误页面的入口,登陆form必须包含用户名和密码的输入域。这些输入域必须分别命名为j_username和j_password。
当用户试图当问受保护的web资源,容器检验用户的认证。如果用户通过认证,拥有权限访问该资源,该资源被激活,返回它的引用。如果用户未通过认证,则:
1.与安全约束关联的登陆form被发送到客户端,而引发认证的URL被容器存储。
2.用户填写form。
3.客户机将form发送回服务器。
4.容器试图使用form里的信息认证用户
5.若认证失败,适用formward或redirect导航到错误页面,将response的状态码设定为401。
6.如果认证成功,被认证的用户的principal被检查看是否它在一个已授权的角色中.
7.如果认证成功,客户机被重新导航到到前面所存储的URL路径。
发送给未通过认证的用户的错误页包含关于失败的信息。
基于form认证和基本认证一样缺乏安全,因为用户密码是明码传输,而且目的服务器也未经认证。通过附加的保护可以减轻这些安全忧虑:例如安全传输机制(HTTPS),网络层安全(IPSEC或VPN)。
12.5.3.1 登陆form
基于form登陆和基于URL的会话跟踪一起使用是有问题的,实现基于form登陆应当只在通过cookie方式的会话或SSL会话时使用。
为了使认证能过程正确执行,登陆form的action必须是j_security_check,此约束是为了使登陆form 面对所请求的任何资源都可以工作得很好,并避免要求服务器指定action域。
<form method=”POST”action=”j_security_check”>
<inputtype=”text” name=”j_username”>
<inputtype=”password” name=”j_password”>
</form>
如果该登陆form由一个带参数的HTTP请求引起,容器必须保存原始的请求参数,当认证成功后,再将其转发到被请求的资源。
如果被认证的用户使用登陆form,并且已创建了HTTP 会话,该会话的超时或失效导致该用户登出,其接下来的请求会引起用户再次认证.
登出的范围和认证是一样的. 例如:如果容器支持single-signon,J2EE技术兼容的web容器,用户只需要认证web容器中的所有应用任何一个就可以了.
12.5.4 HTTPS客户端认证
使用HTTPS的终端用户认证是强认证机制.此机制要求用户拥有公共密钥证书(PKC),PKC对于电子商务应用是很有用的,不兼容J2EE技术的servlet容器不需要支持HTTPS协议.
12.6 服务器跟踪认证信息
下列安全身份在运行环境中映射的角色由运行环境指定而非应用程序所指定的.
1.使登陆机制和策略成为要发布web应用的环境的属性.
2.对于同一容器内的所有应用可以使用相同的认证信息.
3.只有安全策略域的边界交叉的时候,才需要用户重新认证.
因此,servlet容器需要在容器层次(非web应用层)跟踪认证信息,允许在一个web应用中认证后的用户可以访问容器用同样安全身份管理的其他资源.
12.7 EJB调用里的安全身份传播
调用企业bean必须提供安全身份或principal.从web应用中调用企业bean的默认模式是将web用户的安全身份传播到EJB容器.
在另一些场景中,web容器需要允许对于它或者EJB容器来说不认识的用户调用:
1.web容器需要支持还没有认证的客户机访问web资源.
2.应用代码也许是基于访问者身份的数据和signon的唯一处理者.
在这些情况下,web应用配置描述,可以指定一个run-as元素,容器必须根据定义在run-as元素内的安全角色的名字,将调用者的安全身份传播到EJB层.
该安全角色的名字必须是web应用定义的安全角色中的一个.
因为web容器作为J2EE平台的一部份运行,调用在相同或不同J2EE应用中的EJB组件,run-as元素都必须被支持.
12.8 指定安全约束
安全约束是给与网络内容以预期保护的一种声明性方式。包含以下元素:
.web资源集合
.授权约束
.用户数据约束
web资源集合是一系列的URL范式和描述需要保护的一系列资源的HTTP方法。所有路径匹配URL范式的请求遵循此约束。
容器匹配预定义与安全约束内的URL范式的短发与11.1是一致的。
授权限制是一套安全角色,访问网资源集合描述的资源的用户必须属于其中之一。如果用户不是被允许角色的一部分,用户被拒绝访问该资源.
如果授权约束为定义角色,那么没有用户可以访问web应用中被此安全约束定义的这一部分.
用户数据描述了对客户机服务器传输层的需求.此需求的目的是内容一致性(防止通信过程中的破坏)或是机密性(防止传输中被窃听).容器必须至少使用SSL响应标志为
integral或confidential.的请求.如果原始请求的协议是HTTP,容器必须重新导向客户机到HTTPS端口.
12.9 默认策略
默认情况下,访问资源无需认证,只有在配置描述中指定了的才需要认证.
(完)