天天看点

深入Jetty源码之Servlet框架及实现(ServletRequest、ServletResponse)

servlet是server applet的缩写,即在服务器端运行的小程序,而servlet框架则是对http服务器(servlet container)和用户小程序中间层的标准化和抽象。这一层抽象隔离了http服务器的实现细节,而servlet规范定义了各个类的行为,从而保证了这些“服务器端运行的小程序”对服务器实现的无关性(即提升了其可移植性)。

在servlet规范有以下几个核心类(接口):

servletcontext:定义了一些可以和servlet container交互的方法。

registration:实现filter和servlet的动态注册。

servletrequest(httpservletrequest):对http请求消息的封装。

servletresponse(httpservletresponse):对http响应消息的封装。

requestdispatcher:将当前请求分发给另一个url,甚至servletcontext以实现进一步的处理。

servlet(httpservlet):所有“服务器小程序”要实现了接口,这些“服务器小程序”重写doget、dopost、doput、dohead、dodelete、dooption、dotrace等方法(httpservlet)以实现响应请求的相关逻辑。

filter(filterchain):在进入servlet前以及出servlet以后添加一些用户自定义的逻辑,以实现一些横切面相关的功能,如用户验证、日志打印等功能。

asynccontext:实现异步请求处理。

servletrequest是对servlet请求消息的封装,其子接口httpservletrequest则是对http请求消息的封装,在servlet框架中默认实现了servletrequestwrapper和httpservletrequestwrapper以便利用户对请求的wrap。

深入Jetty源码之Servlet框架及实现(ServletRequest、ServletResponse)

在jetty中,使用request类实现httpservletrequest接口,request包含了httpconnection引用,因为httpconnection包含了httpparser解析http请求后的所有信息,如请求行、请求头以及请求内容。其中servletrequest接口定义与实现如下:

public interface servletrequest {

    // request级别的attribute操作,它属于request的私有数据,因而request使用attributes私有字段实现。然而jetty添加了一些自定义的attribute:

    // 在getattribute时,org.eclipse.jetty.io.endpoint.maxidletime用于获取endpoint中maxidletime属性,org.eclipse.jetty.continuation用于获取continuation实例(如果该属性没被占用)

    // 在setattribute时,org.eclipse.jetty.server.request.queryencoding属性同时用于设置request的queryencoding属性;

    // org.eclipse.jetty.server.sendcontent同时用于直接向客户端发送object指定的相应内容,该值必须是httpcontent、resource、buffer、inputstream类型;

    // org.eclipse.jetty.server.responsebuffer同时用于设置对客户端相应的bytebuffer数据;org.eclipse.jetty.io.endpoint.maxidletime同时用于设置endpoint中的maxidletime属性。

    // 如果注册了servletrequestattributelistener,则相应的attributeadded、attributereplaced、attributeremoved方法会被调用。

    public object getattribute(string name);

    public enumeration<string> getattributenames();

    public void setattribute(string name, object o);

    public void removeattribute(string name);

    // characterencoding属性,用于指定读取请求内容时使用的编码方式,如果设置的编码方式不支持,抛出unsupportedencodingexception。characterencoding的设置必须在读取parameter或getreader方法的调用之前,不然该方法不会有效果。该属性默认为null,此时jetty默认使用utf-8编码parameter,使用iso-8859-1编码方式创建reader实例。

    public string getcharacterencoding();

    public void setcharacterencoding(string env) throws unsupportedencodingexception;

    // 请求内容的长度,以字节为单位,-1表示长度未知。该值从httpconnection的requestfields字段中获取,该字段在httpparser解析http请求消息时填充。

    public int getcontentlength();

    // 返回请求内容的mime type,即在请求头中设置的contenttype头,该值同样从httpconnection的requestfields字段中获取,该字段在httpparser解析http请求消息时填充。

    public string getcontenttype();

    // 使用servletinputstream包装请求内容的读取,该方法和getreader方法,只能使用一种方式来读取请求内容,否则抛出illegalstateexception。

    // servletinputstream继承自inputstream,它只是实现了一个readline函数。在该方法实现中返回httpconnection的getinputstream,在该方法返回前,如果当前请求有expect: 100-continue头,则先向客户端发送100 continue相应,然后返回httpinput实例,它继承自servletinputstream,从httpparser中读取数据。

    public servletinputstream getinputstream() throws ioexception; 

    // 请求参数相关的操作,请求参数可以在url使用?paramname=paramvalue&...的方式指定,也可是使用post/put方法在请求消息体中指定(contenttype: application/x-www-form-urlencoded),或同时存在。url的parameter信息读取使用queryencoding字段指定的编码方式(默认编码为utf-8),请求消息体中信息读取使用characterencoding字段指定的编码方式(默认编码为iso-8859-1)。在读取请求消息体中的parameter数据时,可以使用contexthandler中的maxformcontentsize属性或server中的org.eclipse.jetty.server.request.maxformcontentsize的attribute配置最大支持的parameter数据大小,如果contentlength超过该值,则抛出illegalstateexception。

    // 相同的paramname可以多次出现,因而一个paramname可能对应多个值,此时使用getparametervalues()方法获取所有的值。

    // 如果使用post指定请求参数,使用getinputstream或getreader读取数据会对parameter的读取有影响。

    public string getparameter(string name);

    public enumeration<string> getparameternames();

    public string[] getparametervalues(string name);

    public map<string, string[]> getparametermap();

    // 返回当前请求使用的协议以及其版本号,如http/1.1,对http请求,它在解析请求行时设置。

    public string getprotocol();

    // 返回请求的schema,默认为http,在ssl相关的connector的customize方法中会被设置为https。

    public string getscheme();

    // 返回当前请求发送的服务器名称,如果请求uri中包含host name信息,则servername使用该信息;否则使用host头(值为host:port)中的server name信息;

    // 如果不存在host头,尝试使用endpont中的localhost(localaddr,如果_dns值设置为false,即connector的resolvenames属性为false,其默认值为false);

    // 否则使用当前server的localhost的address:inetaddress.getlocalhost().gethostaddress();

    // 对代理服务器,如果jetty在connector中开启了forwarded检查,如果connector中设置了_hostheader值,则使用强制设置host头为该值,清除已有的servername和port,并重新计算;

    // 否则如果存在x-forwarded-host头,强制设置host头为该头值的最左边的值,即第一个代理服务器的主机名,清除已有的servername和port,并重新计算;

    // 如果存在x-forwarded-server头,则强制设置request的servername值为该头值的最左边的值,即第一个代理服务器的主机名。

    // 如果存在x-forwarded-for头,则更新request的remoteaddr和remotehost值,即最原始客户端的地址和主机名。

    // 如果存在x-forwarded-proto头,则更新request的schema为该头值的最左边的值。(这是原始请求的schema还是神马呢?)

    // 具体参考:http://benni82.iteye.com/blog/849139

    public string getservername();

    // serverport有一下查找路径:请求uri中的port;host头中值的port;endpoint的localport;如果都没有找到,则对https默认值为443,对http默认值为80

    public int getserverport();

    // 将servletinputstream包裹成bufferedreader类型,使用characterencoding编码方式,如果没有设置该编码,默认使用iso-8559-1,因而characterencoding的设置必须在该方法调用之前。

    public bufferedreader getreader() throws ioexception;

    // 返回客户端的ip地址,如果开启了forwarded检查,则该值会被更新为原始的客户端请求ip,即使该请求穿越了好几个代理服务器。

    public string getremoteaddr();

    // 如果connector设置了resolvenames属性为true,即request中_dns字段为true,则返回客户端主机名,否则返回客户端主机ip;如果开启了forwarded检查,则该值被更新为最原始的客户端的主机名或ip,即使该请求穿越了好几个代理服务器。

    public string getremotehost();

    // 返回accept-language请求头中的指定的locale值,支持多个值,以",", " ", "\t"等字符分隔,每个值都可以是如下格式:language-<country>;q<value>或language-<country>; q=<value>的格式,在选择一个locale时使用qvalue最大的那个。如果没有设置,默认使用当前服务器的locale值。

    public locale getlocale();

    // 返回accept-language头中指定的所有locale枚举值,以qvalue的值降序排列,如果没有指定该头,使用服务器默认的locale值。

    public enumeration<locale> getlocales();

    // 检查当前请求是否在安全传输通道,如https。

    public boolean issecure();

    // 返回一个requestdispatcher,内部使用servletcontext获取requestdispatcher实例,根据传入的path计算uriincontext值:如果它以"/"开头,uriincontext的值即为该值,

    // 如果它不以"/"开头,即表示它是相对与当前请求的path,则uriincontext的值为相对于当前request uri,如pathinfo为/foo/goo,path为poo,则uriincontext为:<servletpath>/foo/poo

   public requestdispatcher getrequestdispatcher(string path);

    // 返回客户端的端口,调用endpoint的getremoteport方法。

    public int getremoteport();

    // 返回当前服务器的主机名,如果connector的resolvenames为true,否则为当前服务器的ip地址。

    public string getlocalname();

    // 返回当前服务器的ip地址,调用endpoint的getlocaladdr方法。

    public string getlocaladdr();

    // 返回当前服务器的端口号,即当前连接使用的端口号,不是服务器本身监听的端口号。

    public int getlocalport();

    // 返回当前request正在执行所在servletcontext。

    public servletcontext getservletcontext();

    // 启动当前请求的异步模式,该方法调用后,即可以推出当前请求的处理方法,在退出之前需要将返回的asynccontext放到一个等待queue或类似的数据结构中,从而在之后的处理中还能得到这个

    // asynccontext实例进一步处理这个request,asynccontext包含了对当前request和response实例的引用,一般唤起这个异步的请求,使用asynccontext的dispatch方法,

    // 从而保证在下一次的处理过程中依然存在filter链通道。这个方法和带servletrequest、servletresponse参数的方法区别在于:

    // 如果servlet a对应为/url/a,在servlet a中调用request.getrequestdispatcher("/url/b"),此时在servlet b中,如果调用request.startasync().dispatch(),此时会dispatch到/url/a,

    // 但是如果在servlet b中调用request.startasync(request, response).dispatch(),此时会dispatch到/url/b中。

    // 另外该方法会在调用之前注册的asynclistener的onstartasync()方法之后,清除这些已注册的asynclistener。在onstartasync方法中可以将自己重新注册到asynccontext中,只是这个设计好奇怪。。。

    public asynccontext startasync() throws illegalstateexception;

    public asynccontext startasync(servletrequest servletrequest, servletresponse servletresponse)

            throws illegalstateexception;

    // 查看是否当前request异步模式已经启动,即已经调用startasync方法,但是还没有调用asynccontext中的dispatch或oncomplete方法。

    public boolean isasyncstarted();

    // 查看当前request是否支持异步模式,默认为true,如果filter或servlet不支持异步模式,这在调用对应的dofilter、service之前会把该值设置为false。

    public boolean isasyncsupported();

    // 获取最近一次调用startasync方法时创建的asynccontext实例,如果当前request没有异步模式还没有启动,则抛出illegalstateexception。

    public asynccontext getasynccontext();

    // 获取当前请求的dispatcher类型。dispatcher type是container用于选区对应的filter链。请求最初为dispatcher.request;如果调用requestdispatcher.forward()方法,

    // 则变为dispatchertype.forward;如果调用requestdispatcher.include()方法,则变为dispatchertype.include;如果调用asynccontext.dispatch()方法,则变为

    // dispatchertype.async;最后如果请求被dispatch到error page中,则为dispatchertype.error。

    // dispatchertype在httpconnection中的handlerequest方法中,在调用server.handle()/server.handleasync()方法之前,设置为request、async,根据当前request的asynccontext是否处于初始化状态,如果是,则为request,否则为async状态,其他值则在dispatcher中forward或include方法中设置。

    public dispatchertype getdispatchertype();

}

httpservletrequest接口定义如下:

public interface httpservletrequest extends servletrequest {

    // servlet的四种验证方式(他们在web.xml文件中的long-config/auth-method中定义):

    // basic使用authentication头传输认证信息,该信息以base64的编码方式编码,当需要用户验证时会弹出登陆窗口。

    // form使用表单的方式认证,用户名和密码在j_username和j_password字段中,登陆url为/j_security_check,第一次使用表单方式明文传输,之后将认证信息存放在session中,在session实效之前可以不用在手动登陆。

    // digest也时使用authentication头传输认证信息,但是它使用加密算法将密码加密。

    // client_cert则使用客户端证书的方式传输认证信息。

    // 更详细的内容参考:http://docs.oracle.com/cd/e19798-01/821-1841/bncas/index.html 

    public static final string basic_auth = "basic";

    public static final string form_auth = "form";

    public static final string client_cert_auth = "client_cert";

    public static final string digest_auth = "digest";

    // 返回当前请求的认证方法,可以是basic、form、client_cert、digest。如果没有定义对应servlet的认证配置,则返回null。在jetty实现中,在servlethandler根据配置信息以及认证的结果设置请求的authentication实例,如果authentication实例是authentication.deffered实例,则先验证,并设置验证后的authentication实例,如果authentication实例是authentication.user实例,则设置返回其authmethod属性,否则返回null。

    public string getauthtype();

    // 返回当前请求包含的cookie数组,从请求的cookie头中获取,如果没有cookie信息,则返回null。在jetty实现中,使用cookiecutter类解析cookie头。

    // cookie值的格式为:<name>=value; [$path=..; $domain=...; $port=...; $version=..]; <name>=<value>.....

    // 在servlet中,cookie类包含comment(描述信息)、domain(cookie只是对指定的domain域可见,默认情况下cookie只会传输给设置这个cookie的server)、maxage(cookie的最长可存活秒数,负数表示永远不会失效,0表示cookie会被删除)、path(指定那个path下的请求这个cookie才会被发送,包括它的子目录)、secure(这个cookie是否只在https请求中发送)、version(0表示netscape最初定义的cookie标准,1表示兼容rfc 2109,一般都是0以保持最大兼容性)、ishttponly(httponly标识的cookie一般不应该暴露给客户端的脚本,以部分解决跨站点脚本攻击问题)字段。对浏览器,一般他们应该能为每个web server存储至少20个cookie,总共至少能处理300个cookie,以及至少4k大小。

    public cookie[] getcookies();

    // 解析指定请求头的date值,并转换成long值,如if-modified-since头。jetty支持的date格式有:eee, dd mmm yyyy hh:mm:ss zzz; eee, dd-mmm-yy hh:mm:ss等。

    public long getdateheader(string name);

    // 返回指定的请求头的值,没有该头,返回null,如果有多个相同名字的头,则返回第一个该头的值。name是大小写无关。

    public string getheader(string name); 

    // 返回指定请求头的所有值

    public enumeration<string> getheaders(string name); 

    // 返回请求头的所有名称,有些container会禁用该方法,此时返回null。

    public enumeration<string> getheadernames();

    // 返回指定请求头的int值。

    public int getintheader(string name);

    // 返回请求方法,如get、post、put等。该值在解析请求行结束后设置。

    public string getmethod();

    // 设置请求的额外path信息,该值在不同的地方会被设置为不同的值,如果请求的uri为: http://host:80/context/servlet/path/info如在httpconnection中,其值为/context/servlet/path/info

    // 在contexthandler的doscope中,其值被设置为/servlet/path/info;在servlethandler的doscope方法中,其值被设置为/path/info,并设置servletpath为/servlet。

    // 这里基于的假设为contextpath为/context,servletpath为/servlet,即在web.xml配置中servlet相关的url-pattern为/servlet/*。

    // 如果没有/path/info后缀,则pathinfo为null,如果servlet的path-pattern设置为/*,则servletpath为"",对url-pattern为*.do类似的配置,pathinfo永远为null,servletpath则为/path/info.do的值;有些时候servletpath的值也可能是servlet name的值。

   public string getpathinfo();

   public string getservletpath();

    // 返回pathinfo对应的在servletcontext下的真是server路径,如果pathinfo为null,则返回null。

    public string getpathtranslated();

    // 返回当前请求所属的contextpath。它在contexthandler的doscope方法中或dispatcher的forward方法中设置。

    public string getcontextpath();

    // 返回当前请求的query字符串,如果设置了queryencoding,使用该编码方式编码。

    public string getquerystring();

    // 返回请求的登陆用户,如果用户没有认证,则返回null。该信息从设置的authentication实例中获取,如果其实例为authentication.defered,则需要先验证,然后返回获取useridentity,并从中获取principal,而从principal中可以获取用户名。

    public string getremoteuser();

    // 验证当前请求的user是否在传入的role中,即使用设置的authentication实例验证,当前登陆的user所在的roles中是否存在传入的rolename。其中useridentity.scope实例用于查找rolename对应在container使用的rolename,如果没有这个映射则使用传入的rolename本身,这个scope在servlethandler的doscope方法中设置。这个映射在web.xml中的security-rol-ref(role-name->role-link)中设置。

    public boolean isuserinrole(string role);

    // 返回当前登陆user的principal实例,从设置的authentication实例中获取,或者为null。

    public java.security.principal getuserprincipal();

    // 返回客户端指定的session id,它可以和server的当前session id不同。客户端可以使用两种方式设置该值:cookie和url。默认先从cookie中找,找不到再从url中找。

    // 对cookie方式,在web.xml的cookie-config/name中配置session的cookie name(默认值为jssessionid),找到该cookie name对应的cookie值作为requested session id

    // 对url方式,在;<sessionidpathparamname>=....[;#?/]之间的值作为requested session id的值。其中sessionidpathparamname可以通过web.xml的context-param,使用org.eclipse.jetty.servlet.sessionidpathparametername属性值配置,默认为jsessionid。

    public string getrequestedsessionid();

    public boolean isrequestedsessionidfromcookie();

    public boolean isrequestedsessionidfromurl();

    // 检查当前requested session id是否valid,在jetty中valid是指requstedsessionid存在,并且和当前请求的server session的clusterid相同,即他们的sessionid相同。

    public boolean isrequestedsessionidvalid();    

    // 返回/contextpath/servletpath/pathinfo的值。对forward后请求,该值为forward后的uri。

    public string getrequesturi();

    // 返回getschema()://getservername():getport()/getrequesturi(),对forward后请求,该值为forward后的url。

    public stringbuffer getrequesturl();

    // 返回和当前请求相关联的httpsession,如果没有关联的httpsession,且create为true,则创建新的httpsession实例。没有参数即create为true。

    public httpsession getsession(boolean create);

    public httpsession getsession();

    //使用container定义的认证机制验证请求用户的合法性。在jetty实现中,只是对authentication.deferred的authentication类型进行验证,否则返回401 unauthorized错误相应,并返回false。

    public boolean authenticate(httpservletresponse response)  throws ioexception,servletexception;

    // 提供用户名和密码并交由container对其进行认证。在jetty实现中,只对authentication.deferred类型提供用户名和密码认证,否则抛出servletexception。

    public void login(string username, string password)  throws servletexception;

    // 注销当前用户,并清理_authentication字段。

    public void logout() throws servletexception;

    // 对multipart/form-data请求类型,表示请求内容由多个部分组成,此时使用rfc1867来解析该内容到多个part中。在servlet中一个part有自己的请求头和请求消息提,包含contenttype、name、headers、size(已写入的大小)、inputstream等信息,它还提供了一个方法将请求内容写入指定的文件中,以及删除该内部文件。并提供multipartconfigelement类来做相关的配置,如写文件时的位置location;最大可上传的文件大小maxfilesize;最大可包含的所有part的请求大小maxrequestsize;如果写入的数据超过配置的大小,则开始将数据写入文件中maxfilesizethreshold。该配置信息可以使用org.eclipse.multipartconfig属性设置,而location信息可以使用javax.servlet.context.tempdir属性在servletcontext中设置,或这使用java.io.tmpdir中指定的值。在jetty中使用multipartinputstream来表达并解析请求内容到多个part(multipart)中。具体格式可以参考:http://blog.zhaojie.me/2011/03/html-form-file-uploading-programming.html

    public collection<part> getparts() throws ioexception, servletexception;

    public part getpart(string name) throws ioexception, servletexception;

除了以上实现,request还包含了一些额外的字段,如_timestamp在请求开始时设置,即解析请求头完成时;_dispatchtime在requestloghandler的handle方法,当request是非initial的状态下设置,即当前request已经为dispatched的状态;_handled表示该请求是否已经处理完成;

servletresponse是对servlet响应消息的封装,其子接口httpservletresponse则是对http响应消息的封装, 

深入Jetty源码之Servlet框架及实现(ServletRequest、ServletResponse)

servletresponse接口定义如下:

public interface servletresponse {

    // 设置与返回消息体中的字符编码方式。如果没有设置,默认使用iso-8559-1。其中setcontenttype和setlocale会隐式的设置该值,但是显示的设置会覆盖之前隐式的设置,而该值的设置也会影响contenttype的值。该值的设置必须在调用getwriter()方法之前,否则不会起作用。在set方法中,如果传入的charset为null,则清楚原来设置的值,并将原来的_mimetype值设置为content-type头;否则,更新characterencoding的值,并更新contenttype的属性以及content-type头(更新contenttype中"charset="之后部分的值,或使用"mimetype; charset=<charactertype>")。

    public void setcharacterencoding(string charset);

    public string getcharacterencoding();

    // 设置响应消息的content-type头以及contenttype字段的值,它会同时影响mimetype字段以及characterencoding字段。如果type为null,则清除contenttype、mimetype的值,移除content-type响应头,并且如果locale也为null,同时清除charactertype值;否则,设置mimetype为";"之前的值,而characterencoding的值为"charset="之后的值,如果没有"charset="之后的值,则是使用"charset=<charactertype>"值拼接以设置content-type响应头。

    public void setcontenttype(string type);

    public string getcontenttype();

    // 设置locale的值,同时设置响应的content-language头。同时该set也会影响characterencoding、mimetype和content-type响应头。其中characterencoding从contexthandler中的locale到charset的映射中获取,即web.xml中的locale-encoding-mapping-list中定义,而对content-type的值只影响"charset="之后的值。

    public void setlocale(locale loc);

    // 设置contentlength字段以及content-length响应消息头。如果当前写入的数据已经大于或等于设置的值,则该方法还会关闭当前的servletoutputstream或writer。

    public void setcontentlength(int len);  

    // 返回向客户端写数据的servletoutputstream或printwriter。它们只能使用其中一个方法。在jetty中使用httpoutput内部封装httpgenerator实例用于向socket中写数据。

    public servletoutputstream getoutputstream() throws ioexception;

    public printwriter getwriter() throws ioexception;    

    // 响应消息处理相关的buffer操作,在jetty中即为httpgenerator中响应消息体的buffer大小与刷新操作。设置buffersize必须在写响应消息体之前。另外reset只是清除消息体的缓存,并不会清除响应状态码、响应头等信息,如果该方法调用时消息已经被commit,则抛出illegalstateexception。

    public void setbuffersize(int size);

    public int getbuffersize();

    public void flushbuffer() throws ioexception;

    public void resetbuffer();

   // 是否响应消息已经commit,即响应消息状态行和消息头已经发送给客户端。

    public boolean iscommitted();

    // 清除所有的响应消息状态,所有已经设置的响应头(但是不包括connection头),如果响应已经commit,则抛出illegalstateexception。

    public void reset();

httpservletresponse接口定义如下,它添加了一些cookie、header相关的操作:

public interface httpservletresponse extends servletresponse {

    // 向响应消息中添加一个cookie实例。即添加"set-cookie"头。

    public void addcookie(cookie cookie);

    // 消息头操作:增删改查。一个相同名字的头可能会有多个条纪录,因而可以对应多个值。在jetty实现中代理给httpconnection中的responsefields字段。其中date头的格式为:eee, dd mmm yyyy hh:mm:ss 'gmt',对于string为值的设置来说,处于include dispatch状态下,只能设置org.eclipse.jetty.server.include.<headername>的头。

    public boolean containsheader(string name);

    public void setdateheader(string name, long date);

    public void adddateheader(string name, long date);

    public void setheader(string name, string value);

    public void addheader(string name, string value);

    public void setintheader(string name, int value);

    public void addintheader(string name, int value);

    public collection<string> getheaders(string name); 

    public collection<string> getheadernames();

    // 编码传入的url,决定并添加是否需要在url中加入session id信息以作为session追踪。处于健壮性的考虑,所有servlet产生的url必须使用改方法编码,不然对不支持cookie的浏览器将会失去session信息。在实现中,如果request使用cookie作为session追踪,则去除url中的sessionid信息;否则如果session存在并可用,则向url中添加session追踪信息,在"#"或"?"之前。

    public string encodeurl(string url);

    // 编码传入的url,用于sendredirect方法中。在jetty中改方法直接调用encodeurl()方法。

    public string encoderedirecturl(string url);

    // 向客户端发送响应状态码和原因(如果有的话)。服务器会保留已经设置的cookie,但是会在必要情况下修改响应头。如果在web.xml中定义了响应的状态码到error page的映射,则该响应会被转发到那个错误页面中。在jetty实现中,它清除buffer信息,characterencoding值,expires、last-modified、cache-control、content-type、content-length头,设置响应状态和消息,对非204(no content)、304(not modified)、206(partial content)、200(ok)的状态码(即允许有消息体),首先查找有没有注册的errorhandler ,如果有,向request的属性中设置javax.servlet.error.status_code, javax.servlet.error.message, javax.servlet.error.request_uri, javax.servlet.error.servlet_name为相应的值(requestdispatcher中定义的属性key),并调用errorhandler的handle方法;否则设置cache-control头为must-revalidate,no-cache,no-store,设置content-type头为text/html;charset=iso-8859-1,并返回一个简单的错误页面包含状态码和消息原因。最后调用httpconnection的completeresponse()方法以完成响应。

    public void senderror(int sc, string msg) throws ioexception;

    public void senderror(int sc) throws ioexception;

    // 发送客户端一个重定向的消息和url,即设者响应状态码为302 moved temporary,在location中包含要重定向目的地的url,此时客户端会使用新的url重新发送请求。如果location以"/"开头,表示它是绝对地址,否则为相应请求的相对地址,即最终解析成request.getrequesturi()/location,location可以包含query信息。最后调用httpconnection的completeresponse()方法以完成当前响应。

    public void sendredirect(string location) throws ioexception;

    // 设置响应状态码和状态描述信息。

    public void setstatus(int sc);

    public void setstatus(int sc, string sm);

    public int getstatus();

继续阅读