这是我参与8月更文挑战的第21天,活动详情查看:8月更文挑战
Servlet中的HttpServletRequest的常见用法和API,比如获取请求头、请求参数、设置请求转发和包含等知识点。
javax.servlet.http.HttpServletRequest是一个接口,它扩展了扩展 ServletRequest 接口,专门为基于HTTP协议的HTTPServlet 封装、提供请求信息,是请求的抽象。
在客户端发出每个HTTP请求时,服务器都会创建一个对应的HttpServletRequest对象,并把请求数据封装到该对象中,然后在调用Servlet.service()方法时传递给service()方法,随后又会传递给对应类型的服务方法(doGet、doPost 等)中,开发中是可以直接可以通过该对象来获取请求数据。
HttpServletRequest主要有如下作用:
- 封装了请求头数据;
- 封装了请求正文数据,如果是GET请求,那么就没有正文;
- request是一个域对象,可以把它当成Map来添加、获取数据;
- request提供了请求转发和请求包含功能。
1 获得请求头信息
方法 | 描述 |
---|---|
String getHeader(String name) Name-指定请求头名称字符串 | 返回一个字符串, 包含请求头的值, 如果要获得没有该请求名称的头, 则返回null。 |
Enumeration< String>getHeaders(String name) | 返回包含请求头的值的枚举,某些请求头例如:Accept-Language,可以具有多个值。如果传入没有该名称的请求头,则返回空枚举。如果容器不允许访问头信息,则返回 null。 |
Enumeration< String> getHeaderNames() | 返回与此请求一起发送的所有请求头名称的枚举;如果没有请求头,则返回空枚举。如果 servlet容器不允许servlet使用此方法,则返回null。 |
int getIntHeader(String name) | 返回表示请求头的值的整数,如果请求没有此名称的头,则返回-1,如果值无法转换为整数,则此方法将抛出 NumberFormatException。 |
2 获得请求参数信息
这些方法是我们最常用的,用于实现前后端交互,最为常见的客户端传递参数方式有两种get和post:
- 浏览器地址栏直接输入:一定是GET请求;
- 超链接:一定是GET请求;
- 表单:可以是GET,也可以是POST,这取决与< form>的method属性值;
方法 | 描述 |
---|---|
String getParameter(String name) | 通过指定名称获取参数值;name属性的必须和前端页面标签的name属性对应的值相同。 |
String[] getParameterValues(String name) | 当多个参数名称相同时,可以使用方法来获取全部的值的数组,例如多选框。无参数,返回null。 |
Enumeration getParameterNames() | 返回包含此请求中包含的参数的名称的字符串对象的枚举。如果请求没有参数, 则该方法返回空的枚举。 |
Map getParameterMap() | 获取所有参数封装到Map中,其中key为参数名,value为参数值,因为一个参数名称可能有多个值,所以参数值是String[]类型,而不是String类型。 |
3 作为域对象使用
request是一个域对象!request可以在一个当前请求中共享数据,作用域是当前请求链。它同样具有域对象的通用方法setAttribute、getAttribute、removeAttribute、getAttributeNames,这几个方法我们在ServletContext部分就讲解了。
一个请求会创建一个request对象,如果在一次请求中经历了多个Servlet(比如请求转发和请求包含),那么该请求中的多个Servlet就可以使用request来共享数据。
凡是域对象,都有如下4个操作数据的方法,Servlet中可以使用的域对象有HttpServletRequest、HttpSession、ServletContext,而JSP中可以多使用一个域对象pageContext。
方法 | 描述 |
---|---|
void setAttribute(String name, Object object) | 将对象绑定到此域对象中的给定属性名称。如果指定的名称已用于属性,则此方法将用新属性的值替换旧的属性。 |
Object getAttribute(String name) | 返回具有给定名称key的包含属性值的value对象,如果不存在与给定名称匹配的属性,则返回 null。 |
void removeAttribute(String name) | 从此域对象中删除具有给定名称的属性。删除后,对getattrit (java.lang.String) 以检索属性值的后续调用将返回 null。如果参数name指定的域属性不存在,那么本方法什么都不做。 |
Enumeration getAttributeNames() | 返回包含此域对象中可用的属性名称的枚举。如果没有则返回空枚举。 |
4 设置请求转发和请求包含
请求转发和请求包含都是服务端行为,客户端是不知情的,设置的URL路径也都是服务端路径。请求转发和请求包含特点:
- 一次请求一次响应。
- 浏览器地址栏不会发生变化。
- 服务器内部行为。
- 只能转发到本系统内部的资源,不能转发到站外资源。
- 请求转发和包含对不同的Servlet的请求方法是相同的:当第一次请求是使用get方法,服务器内部的转发请求也是使用get方法。
4.1 请求转发
request.getRequestDispatcher会获取到一个RequestDispatcher对象,通过该对象的forward(request,response)方法可以实现请求转发!
负责转发的Servlet:
@WebServlet("/requestDispatcher-servlet")
public class RequestDispatcherServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
/*
* 1 设置响应头,会被保留并应用,一定要在传递对象之前设置才会保留
*/
resp.setHeader("expires", "0");
resp.setContentType("text/html;charset=utf-8");
/*
* 2 请求转发和包含可以将本servlet的对象传递给下一个servlet对象,因此request存的值在另一个Servlet中可以取出来
*/
req.setAttribute("username", "aa");
/* 3 获得请求分派器,并把请求分派到指定站内资源 /target-servlet*/
RequestDispatcher rd = req.getRequestDispatcher("/target-servlet");
/* 4 将请求和响应参数传递给下一个servlet*/
rd.forward(req, resp);
/*
* 5 设置响应体(正文),不会被实现,即负责转发的servlet,留头不留体
*/
PrintWriter out = resp.getWriter();
out.print("RequestDispatcherServlet");
out.flush();
out.close();
}
}
目的Servlet:
@WebServlet("/target-servlet")
public class TargetServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
//请求转发到该servlet
PrintWriter out = resp.getWriter();
out.print("目的Servlet: TargetServlet");
out.println("<br/>");
//获得在RequestDispatcherServlet中存入的值
String attribute = (String) req.getAttribute("username");
out.print("username: " + attribute);
out.flush();
out.close();
}
}
我们请求/requestDispatcher-servlet,页面上最终会输出/target-servlet中响应的内容,并且浏览器地址栏并没有发生变化,这就是请求转发!
在执行forward方法的时候,如果如果响应已提交,比如写了数据并且执行了PrintWriter或者OutputStream的flush或者close方法,比如调用了sendError、sendRedirect方法,那么将会抛出一个IllegalStateException,并且转发不会完成。
4.2 请求转发与重定向
关于重定向,我们在上一篇文章就讲过了:Java Web(6)—HttpServletResponse的介绍以及使用。下面简单说说它们的区别
- 请求转发通过HttpServletRequest#forward方法实现,重定向通过HttpServletResponse#sendRedirect方法实现。
- 请求转发是一个请求,而重定向是两个请求;
- 请求转发后浏览器地址栏不会有变化,而重定向会有变化,因为重定向是两个请求;
- 请求转发的目标只能是本应用中的资源,重定向的目标可以是其他应用中的资源;
- 请求转发对不同的Servlet的请求方法是相同的,即要么都是GET,要么都是POST,因为请求转发是一个请求;重定向的第二个请求一定是GET(客户端发出的);
- 对于转发和重定向操作之后的语句。重定向会在当前页面代码执行完毕后,跳转到指定的页面执行其他代码。转发:在当前Servlet中代码执行到转发语句后,即跳转到指定的Servlet执行其他代码,执行完毕后返回本Servlet接着执行转发语句后的代码。
4.3 请求包含
RequestDispatcher的include方法可以实现请求包含。包含和被包含的多个Servlet配合工作,它们设置的响应头和响应体都会被保留。
包含Servlet:
@WebServlet("/requestInclude-servlet")
public class RequestIncludeServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
/*
* 1 设置响应头,会被保留并应用,一定要在传递对象之前设置才会保留
*/
resp.setHeader("expires", "0");
resp.setContentType("text/html;charset=utf-8");
/*
* 2 请求转发和包含可以将本servlet的对象传递给下一个servlet对象,因此request存的值在另一个Servlet中可以取出来
*/
req.setAttribute("username", "aa");
/* 3 获得请求分派器,并把请求分派到指定站内资源 /include-servlet*/
RequestDispatcher rd = req.getRequestDispatcher("/include-servlet");
PrintWriter out = resp.getWriter();
/*
* 设置响应体(正文),对于请求包含来说会保留并输出,会输出在开头
*/
out.println("-----我是开头-----");
/* 4 将请求和响应参数传递给下一个servlet,请求包含*/
rd.include(req, resp);
/*
* 设置响应体(正文),对于请求包含来说会保留并输出,会输出在最后
*/
out.println("-----我是结尾-----");
out.flush();
out.close();
}
}
被包含的Servlet:
@WebServlet("/include-servlet")
public class IncludeServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
//请求包含该servlet
/*
* 1 设置响应头,会被保留并应用
*/
resp.setHeader("header", "header");
/*
* 2 设置响应体,会被保留并应用
*/
PrintWriter out = resp.getWriter();
out.println("<br/>");
out.println("包含的Servlet: IncludeServlet");
out.println("<br/>");
//获得在RequestDispatcherServlet中存入的值
String attribute = (String) req.getAttribute("username");
out.print("username: " + attribute);
out.println("<br/>");
}
}
我们访问/requestInclude-servlet,将会得到下面的结果:
4.4 请求转发与请求包含
- 如果在AServlet中请求转发到BServlet,那么在AServlet中就不允许再输出响应体,这一工作应该由BServlet来完成;如果是使用请求包含,那么没有这个限制;虽然请求转发中前面的Servlet不能输出响应体,但还是可以设置响应头的,而对于请求包含的多个Servlet都可以设置响应头和响应体。即请求转发:留头不留体;请求包含:留头又留体。
- 我们在写网页的时候,一般网页的头部和尾部是不需要改变的,请求包含大多是应用在JSP页面中,完成多页面的合并,用于实现网页头部和尾部的复用;请求转发大多是应用在Servlet+JSP组合的逻辑中,转发目标大多是JSP页面,通常访问 Servlet 处理业务逻辑,然后 forward 到 jsp页面显示处理结果,浏览器的URL 不变。而重定向一般是用于实现外部应用的资源的访问,或者用于提交表单,处理成功后 redirect 到另一个jsp,防止表单重复提交,因为浏览器里面的URL 变了。
4.5 请求转发和包含的URL路径
请求转发和请求包含都是服务端行为,客户端是不知情的,设置的参数URL路径也都是服务端路径。服务器端路径必须是相对路径,不能是绝对路径。
相对路径有两种形式:以“/”开头和不以“/”开头,这和客户端路径就存在同样的情形了,但是它们还是有所区别:
- 客户端路径以“/”开头,表示相对当前主机,比如localhost:8080,不以“/”开头,表示相对当前资源路径;浏览器解析。
- 服务器端路径以“/”开头,表示相对当前应用根目录,比如localhost:8080/hello,不以“/”开头,表示相对当前资源路径;服务器解析。
如下案例,假如转发Aservlet访问路径为http://localhost:8081/servlet-02/aa/aservlet
:
@WebServlet("/aa/aservlet")
public class Aservlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
RequestDispatcher bservlet = req.getRequestDispatcher("/bservlet");
//RequestDispatcher bservlet = req.getRequestDispatcher("../bservlet");
bservlet.forward(req,resp);
}
}
假如目的Bservlet路径为http://localhost:8081/servlet-02/bservlet
:
@WebServlet("/bservlet")
public class Bservlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setContentType("text/html;charset=utf-8");
PrintWriter out = resp.getWriter();
out.print("Bservlet");
out.flush();
out.close();
}
}
想要从Aservlet转发到Bservlet,那么getRequestDispatcher的参数路径可以是“../bservlet”(不以“/”开头),或者是“/bservlet”(以“/”开头)。使用“../ ”可以定位到上级路径。
5 获得其他的信息(URL相关)
request中还提供了与请求相关的其他方法,有些方法是为了我们更加便捷的方法请求头数据而设计,有些是与请求URL相关的方法,如下表:
方法 | 描述 |
---|---|
int getContentLength() | 获取请求体的字节数,GET请求没有请求体,没有请求体返回-1; |
String getContentType() | 获取请求类型,如果请求是GET,那么这个方法返回null;如果是POST请求,那么默认为application/x-www-form-urlencoded,表示请求体内容使用了URL编码 |
String getMethod() | 返回请求方法,例如:GET |
Locale getLocale() | 返回当前客户端浏览器的Locale。java.util.Locale表示国家和言语,这个东西在国际化中很有用; |
String getCharacterEncoding() | 获取对客户端请求和数据库取值时的编码,如果没有setCharacterEncoding(),那么返回null,表示使用ISO-8859-1编码; |
void setCharacterEncoding(String code) | 设置对客户端请求的编码,只对请求体有效!注意,对于GET而言,没有请求体!!!所以此方法只能对POST请求中的参数有效! |
String getContextPath() | 返回上下文路径,获取的是项目名称。例如:/hello |
String getQueryString() | 返回请求URL中的参数,例如:name=zhangSan |
String getRequestURI() | 返回请求URI路径,例如:/hello/oneServlet |
StringBuffer getRequestURL() | 返回请求URL路径,例如:http://localhost/hello/oneServlet,即返回除了参数以外的路径信息 |
String getServletPath() | 返回Servlet路径,例如:/oneServlet |
String getRemoteAddr() | 返回当前客户端的IP地址 |
String getRemoteHost() | 返回当前客户端的主机名,但这个方法的默认实现还是获取IP地址; |
String getScheme() | 返回请求协议,例如:http; |
String getServerName() | 例如:localhost |
int getServerPort() | 返回主机名,返回服务器端口号,例如:8080 |
图片描述:
其他方法:
@WebServlet("/uriInfo-servlet")
public class UriInfoServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
System.out.println("request.getContentLength(): " + request.getContentLength());
System.out.println("request.getContentType(): " + request.getContentType());
System.out.println("request.getContextPath(): " + request.getContextPath());
System.out.println("request.getMethod(): " + request.getMethod());
System.out.println("request.getLocale(): " + request.getLocale());
System.out.println("request.getQueryString(): " + request.getQueryString());
System.out.println("request.getRequestURI(): " + request.getRequestURI());
System.out.println("request.getRequestURL(): " + request.getRequestURL());
System.out.println("request.getServletPath(): " + request.getServletPath());
System.out.println("request.getRemoteAddr(): " + request.getRemoteAddr());
System.out.println("request.getRemoteHost(): " + request.getRemoteHost());
System.out.println("request.getRemotePort(): " + request.getRemotePort());
System.out.println("request.getScheme(): " + request.getScheme());
System.out.println("request.getServerName(): " + request.getServerName());
System.out.println("request.getServerPort(): " + request.getServerPort());
}
}
6 get请求参数编码
request.setCharacterEncoding方法只对请求体有效!注意,对于GET而言,没有请求体,参数附加在URL中!所以此方法只能对POST请求中的参数有效,因此在收到参数时还需要额外的编码!
tomcat 8以下的版本用于解码URL的字符编码,如果未指定,则默认使用iso-8859-1,所以如果这个过来的get参数是用的utf-8编码的,到了服务器端tomcat用iso8859-1解码就会出现乱码。那么我们在接受数据时需要使用如下转码方式:
String name = request.getParameter("name"); //默认iso8859-1解码
name = new String(name.getBytes("iso-8859-1"), "utf-8"); //先iso8859-1编码,然后utf-8解码
或者修改tomcat的server.xml配置文件的连接器参数
,加上URIEncoding="utf-8"属性,这样就不用修改代码了:
<Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443" URIEncoding="utf-8"/>
如果是tomcat 7插件
,那就更简单了,直接在maven插件中设置< uriEncoding>UTF-8< /uriEncoding>
即可。
在tomcat 8及其之后,用于解码URL的字符编码,如果未指定,则默认使用UTF-8,因此不再需要转码,或者修改配置文件可以直接获取:
String name = request.getParameter("name"); //默认utf-8解码
6.1 扩展-URL编码
在因特网上传送URL,只能采用ASCII字符集。一旦请求路径中一旦空格、特殊符号、中文等,这时就需要先把要发送的数据转换成URL编码格式,再发送给服务器。其实需要我们自己动手给数据转换成URL编码的只有GET超链接,因为表单发送数据会默认使用URL编码,也就是说,不用我们自己来编码。
例如:“王五”这两个字通过URL编码后得到的是:“[%E7, %8E, %8B, %E4, %BA, %94 ]”。URL编码是先需要把“王五”转换成字节,例如我们现在使用UTF-8把“王五”转换成字符,得到的结果是:“[-25, -114, -117, -28, -70, -108]”,然后再把所有负数加上256,得到[231, 142 , 139 , 228, 186, 148 ],再把每个int值转换成16进制,得到[E7, 8E, 8B, E4, BA, 94 ],最后再每个16进制的整数前面加上“%”。
通过URL编码,把“王五”转换成了“[%E7, %8E, %8B, %E4, %BA, %94 ]”,然后发送给服务器!服务器会自动识别出数据是使用URL编码过的,然后会自动把数据转换回来。
当然,在页面中我们不需要自己去通过上面的过程把“王五”转换成“%E4%BC%A0%E6%99%BA”,而是使用js来完成即可,比如encodeURIComponent函数 实际上,我们也不需要手动进行URL编码,现在浏览器会对URL自动进行编码,浏览器自动URL编码采用的字符集,可以在html页面的head标签中设置charset="UTF-8"来指定。一般来说都是指定为utf-8,这样如果使用tomcat8,也就不需要再次转码了。
服务器会自动识别数据是否使用了URL编码,如果使用了,服务器会自动把数据解码,无需我们自己动手解码。
如有需要交流,或者文章有误,请直接留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!