一、请求转发(forward)和请求重定向(sendRedirect)
二、Response设置ContentType头字段编码和Response.getWriter 方法返回的PrintWriter采用的编码
一、请求转发(forward)和请求重定向(sendRedirect)
在Servlet程序中,有时需要调用另外一个资源来对浏览器的请求进行响应,这可以通过两种方式来实现:其中一种就是调用RequestDispatcher.forward 方法实现的请求转发;另外一种则是调用HttpServletResponse.
sendRedirect 方法实现的请求重定向。
1、请求转发(forward)
请求转发中的RequestDispatcher对象的获取的途径有三种,ServeltContext接口中定义了两个用户获取RequestDispatcher对象的方法,在ServletRequest接口中也定义了一个getRequestDispatcher方法来获得RequestDispatcher对象。
ServletContext.getRequestDispatcher方法 | 返回包装了某个路径所指定的资源的RequestDispatcher 对象,传递给该方法的路径必须以 “/ ”开头,这个“/”代表的是当前Web应用程序的根目录。Web-INF目录中的内容对RequestDispatcher对象是可见的,所以,传递给getRequestDispatcher方法中的资源可以是Web-INF目录中不能被外界访问的文件。 |
ServletContext.getNamedDispatcher方法 | 返回包装了某个Servlet或JSP文件的RequestDispatcher对象,传递给该方法的参数是在Web应用程序部署描述中为Servlet或JSP文件指定的友好名称。 |
ServletRequest.getRequestDispatcher方法 | 它与ServletContext.getRequestDispatcher方法的区别在于:传递给这个 方法的参数除了可以采用以“/”开头的路径字符串,还可以采用非“/”开头的相对路径。 |
RequestDispatcher对象只能包装当前Web应用程序中的资源,所以forward方法和include方法只能在同一个Web应用程序内的资源之间转发请求和实现资源包含。
为什么ServletContext.getRequestDispatcher方法不可以采用非“/”开头的参数,而ServletRequest.
getRequestDispatcher可以采用?
(1)通俗的理解是:ServletContext中封装的是所有Servlet所处的容器环境信息,“/”代表的是当前Web应用程序的根目录信息,在获取RequestDispatcher的时候如果以“/”开头那么转发路径就会从Web应用程序的根目录按照路径向下查找,可以如果不以“/”开头的话,由于ServletContext中没有任何具体路径,所以无法获得具体的转发路径。而Request在获取RequestDispatcher的时候,由与Request中封装了对某一个Servlet的请求信息,当然也包括被请求的Servlet的路径,如果以“/”开头的话仍然作为当前Web应用程序的根目录信息处理,如果不是以“/”开头的话就会作为相对路径来处理,相对路径的参考基准路径就是Request中封装的当前请求的Servlet路径。ServletContext在获取RequestDispatcher的时候正是没有这个可以参考的基准路径所以不可以采用非“/”开头的参数。
(2)从代码的角度分析,原理正如上面描述的类似
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
ServletContext servletContext = getServletConfig().getServletContext();
System.out.println("servletContextRequestDispatcher = " + servletContext);
//servletContextRequestDispatcher = org.apache.catalina.core.ApplicationContextFacade@cbd8dc
System.out.println("request = " + request);
//request = org.apache.catalina.connector.RequestFacade@10a5c21
}
该ServletContext接口和Request接口的具体实现类的getRequestDispatcher方法的部分代码为:
org.apache.catalina.core.ApplicationContextFacade的getRequestDispatcher的部分代码为:
// Validate the path argument
if (path == null)
return (null);
if (!path.startsWith("/"))
throw new IllegalArgumentException
(sm.getString
("applicationContext.requestDispatcher.iae", path));
首先会验证路径参数,如果路径不是以“/”开头的路径就会抛出异常。
org.apache.catalina.connector.RequestFacade的getRequestDispatcher的部分代码为:
// If the path is already context-relative, just pass it through
if (path == null)
return (null);
else if (path.startsWith("/"))
return (context.getServletContext().getRequestDispatcher(path));
/..............................
其他部分代码
...............................
/
int pos = requestPath.lastIndexOf('/');
String relative = null;
if (pos >= 0) {
relative = requestPath.substring(0, pos + 1) + path;
} else {
relative = requestPath + path;
}
return (context.getServletContext().getRequestDispatcher(relative));
而Request在获取RequestDispatcher的时候,先判断路径是否以“/”开头,如果是以“/”开头的话则复用ServletContext接口的getRequestDispatcher方法返回RequestDispatcher接口对象。如果不是以“/”开头的话则通过当前Request请求的Servlet路径作为基准路径,与参数中的相对路径组拼成以“/”开头的路径,最后还是复用ServletContext接口的getRequestDispatcher方法返回RequestDispatcher接口对象。
2、请求重定向(sendRedirect)
HttpServletResponse.sendRedirect方法实现请求重定向,会要求浏览器按照response指定的路径重新进行一次资源的请求。sendRedirect 方法不仅可以重定向到当前应用程序中的其他资源,它还可以重定向到同一个站点上的其他应用程序中的资源,甚至是使用绝对URL重定向到其他站点的资源,这与RequestDispatcher.forward方法只能在同一个Web应用程序内的资源之间转发请求有很大的不同。
(1)对于sendRedirect方法参数的形式有如下总结:
如果传递给sendRedirect 方法的参数是以“/”开头的 | 则“/”代表整个Web站点的根目录(并非Web应用程序的根目录这根转发不同) |
如果传递给sendRedirect方法的参数不是以“/”开头的,如果该参数符合如下规则的话,response对象会将该参数作为location的资源地址原样返回,当然这也可以理解成绝对路径 | 当参数形式为:一个或多个URL合法符号紧跟着:作为开头的参数路径会原样返回 1、合法的URL字母包括字母和数字以及“+”“-”“.” 2、http://www.baidu.com 和 https://mybank.icbc.com.cn/ 和 abc://test 都会原样返回的,只是当浏览器发现abc开头的协议 并不是自己能处理的协议类型所以就忽略该请求重定向 |
除了以上两种情况外的参数形式 | 都作为以相对路径来处理,并且将当前请求资源的路径作为基准路径 |
(2)从代码的角度分析来看
首先通过打印获取Response接口的具体实现类全路径
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println("response = " + response);
//response = org.apache.catalina.connector.ResponseFacade@12462b3
}
然后查看org.apache.catalina.connector.ResponseFacade 的sendRedirect方法
public void sendRedirect(String location)
throws IOException {
/
..........部分代码
/
// Generate a temporary redirect to the specified location
try {
String absolute = toAbsolute(location);
setStatus(SC_FOUND);
setHeader("Location", absolute);
} catch (IllegalArgumentException e) {
setStatus(SC_NOT_FOUND);
}
/
..........部分代码
/
}
由于response.setHeader方法只能设置一个绝对的URL路径,所以需要通过toAbsolute方法将参数路径转换为URL绝对路径。
private String toAbsolute(String location) {
if (location == null)
return (location);
boolean leadingSlash = location.startsWith("/");
if (leadingSlash || !hasScheme(location)) {
/
..............部分代码
/
return redirectURLCC.toString();
} else {
return (location);
}
}
当参数以“/”开头或者不是以协议头形式的字符串开头的都需要进行组拼出一个以协议头形式开头的绝对URL地址,如果是以协议头形式开头的参数就将它作为绝对URL地址直接返回。
其中hasScheme方法代码如下:
private boolean hasScheme(String uri) {
int len = uri.length();
for(int i=0; i < len ; i++) {
char c = uri.charAt(i);
if(c == ':') {
//能循环这里说明前面字符都合法,如果i>0说明符合形式,否者这也算是非法字符了
return i > 0;
} else if(!URL.isSchemeChar(c)) {
//如果包含非法字符,就无从谈起是否包含协议头了
return false;
}
}
return false;
}
二、Response设置ContentType头字段编码和Response.getWriter 方法返回的PrintWriter采用的编码
1、首先探讨一下Response.getWriter 方法是用的字符编码的设置问题
对于Response.getWriter 方法使用的字符编码可以通过下面三个方法来设置setContentType、setCharacterEncoding、setLocale,单独针对PrintWriter使用编码来说只要它们在第一次获取PrintWriter前进行设置都是有效的,只是这三个方法是有优先级的,优先级从高到底为:setCharacterEncoding、setContentType、setLocale 在设置编码的时候优先级高的可以覆盖掉优先级低的方法设置的编码。
2、对于HTTP协议来说,Content-Type头字段的字符编码的设置问题
一般我们会通过response.setContentType("text/html;charset=GB2312"); 语句来设置HTTP响应头的Content-Type 头字段的字符编码。对于Content-Type 头字段字符编码的设置可以通过下面三个方法来设置,但是设置Content-Type头字段编码时,不仅仅跟方法的优先级有关系,还跟有没有设置Content-Type头字段有关系。
当然设置Content-Type头字段编码的优先级跟上面相同,优先级从高到低为:setCharacterEncoding、setContentType、setLocale 不过这是在确保类似response.setContentType('text/html;charset=GB2312');的语句被执行的情况下,因为只有执行了该条语句后,在HTTP返回协议中添加一个头名称为Content-Type的响应头,内容为'text/html;charset=GB2312' 当然内容也可以设置成为'text/html' ,这个操作不仅仅添加了名称为Content-Type的头名称,还相当于为MIME类型和字符编码设置了两个占位符,这样HTTP响应头中就有地方放置MIME类型和字符编码。
所以只有当执行了response.setContentType(); 方法后HTTP协议中就有了MIME和字符编码的占位符,如果参数为'text/html'说明只是设置了MIME内容,字符编码目前为空,如果参为'text/html;charset=GB2312' 说明即设置了MIME类型也设置了编码类型为GB2312。那么优先级高的设置编码方法就可以覆盖掉之前优先级低的方法设置的编码。
对于HTTP协议中Content-Type头字段中的编码信息规律如下表:
没有调用response.setContentType() 方法 | 由于没有向HTTP响应头中添加Content-Type头字段和设置了内容占位符,所以无法设置响应编码 |
调用了response.setContentType()方法,参数为 'text/html' | 添加了Content-Type头字段并且设置了内容占位符,参数只是为MIME类型设置了值,编码值为空。 所以响应编码就会按照优先级从高到底的顺序决定这三个方法的某一个设置的编码:setCharacterEncoding、setContentType、setLocale |
调用了response.setContentType()方法,参数为 'text/html;charset=GB2312' | 添加了Content-Type头字段并且设置了内容占位符,参数不仅设置了MIME类型的值,而且为响应编码也设置了GB2312的值。由于setContentType的优先级低于setCharacterEncoding高于setLocale,所以,如果再修改响应编码的话,只能通过本身方法setContentType或setCharacterEncoding方法来实现。 |
说明:对于设置编码不管是设置HTTP协议中的Content-Type头字段中的编码还是设置PrintWriter输出字符时的编码都需要在第一次获取PrintWriter之前设置的才是有效的。并且被Included的Servlet内设置的响应头和响应码信息是无效的,只会使用include调用者的信息。