HttpServletRequest请求对象的详细介绍以及使用方式

1,033 阅读15分钟

这是我参与8月更文挑战的第21天,活动详情查看:8月更文挑战

Servlet中的HttpServletRequest的常见用法和API,比如获取请求头、请求参数、设置请求转发和包含等知识点。

javax.servlet.http.HttpServletRequest是一个接口,它扩展了扩展 ServletRequest 接口,专门为基于HTTP协议的HTTPServlet 封装、提供请求信息,是请求的抽象。

在客户端发出每个HTTP请求时,服务器都会创建一个对应的HttpServletRequest对象,并把请求数据封装到该对象中,然后在调用Servlet.service()方法时传递给service()方法,随后又会传递给对应类型的服务方法(doGet、doPost 等)中,开发中是可以直接可以通过该对象来获取请求数据。

HttpServletRequest主要有如下作用:

  1. 封装了请求头数据;
  2. 封装了请求正文数据,如果是GET请求,那么就没有正文;
  3. request是一个域对象,可以把它当成Map来添加、获取数据;
  4. 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:

  1. 浏览器地址栏直接输入:一定是GET请求;
  2. 超链接:一定是GET请求;
  3. 表单:可以是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路径也都是服务端路径。请求转发和请求包含特点:

  1. 一次请求一次响应。
  2. 浏览器地址栏不会发生变化。
  3. 服务器内部行为。
  4. 只能转发到本系统内部的资源,不能转发到站外资源。
  5. 请求转发和包含对不同的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的介绍以及使用。下面简单说说它们的区别

  1. 请求转发通过HttpServletRequest#forward方法实现,重定向通过HttpServletResponse#sendRedirect方法实现。
  2. 请求转发是一个请求,而重定向是两个请求;
  3. 请求转发后浏览器地址栏不会有变化,而重定向会有变化,因为重定向是两个请求;
  4. 请求转发的目标只能是本应用中的资源,重定向的目标可以是其他应用中的资源;
  5. 请求转发对不同的Servlet的请求方法是相同的,即要么都是GET,要么都是POST,因为请求转发是一个请求;重定向的第二个请求一定是GET(客户端发出的);
  6. 对于转发和重定向操作之后的语句。重定向会在当前页面代码执行完毕后,跳转到指定的页面执行其他代码。转发:在当前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 请求转发与请求包含

在这里插入图片描述

  1. 如果在AServlet中请求转发到BServlet,那么在AServlet中就不允许再输出响应体,这一工作应该由BServlet来完成;如果是使用请求包含,那么没有这个限制;虽然请求转发中前面的Servlet不能输出响应体,但还是可以设置响应头的,而对于请求包含的多个Servlet都可以设置响应头和响应体。即请求转发:留头不留体;请求包含:留头又留体。
  2. 我们在写网页的时候,一般网页的头部和尾部是不需要改变的,请求包含大多是应用在JSP页面中,完成多页面的合并,用于实现网页头部和尾部的复用;请求转发大多是应用在Servlet+JSP组合的逻辑中,转发目标大多是JSP页面,通常访问 Servlet 处理业务逻辑,然后 forward 到 jsp页面显示处理结果,浏览器的URL 不变。而重定向一般是用于实现外部应用的资源的访问,或者用于提交表单,处理成功后 redirect 到另一个jsp,防止表单重复提交,因为浏览器里面的URL 变了。

4.5 请求转发和包含的URL路径

请求转发和请求包含都是服务端行为,客户端是不知情的,设置的参数URL路径也都是服务端路径。服务器端路径必须是相对路径,不能是绝对路径。

相对路径有两种形式:以“/”开头和不以“/”开头,这和客户端路径就存在同样的情形了,但是它们还是有所区别:

  1. 客户端路径以“/”开头,表示相对当前主机,比如localhost:8080,不以“/”开头,表示相对当前资源路径;浏览器解析。
  2. 服务器端路径以“/”开头,表示相对当前应用根目录,比如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学习博客!