HttpServlet 简介

HttpServlet是对HTTP协议封装的Servlet实现类。

Servlet的体系结构:

在开发中,关注更多的是Servlet.service()方法。而每实现一个Servlet就必须实现Servlet接口,重写接口中的5个方法。

但其实可以通过继承HttpServlet来编写Servlet,简化Servlet的开发流程。并且,如果是开发B/S架构的Web项目,针对的都是HTTP协议。

使用HttpServlet的格式如下:

 1package com.linner.web;
 2
 3import javax.servlet.ServletException;
 4import javax.servlet.annotation.WebServlet;
 5import javax.servlet.http.*;
 6import java.io.IOException;
 7
 8@WebServlet("/demo")
 9public class ServletDemo extends HttpServlet {
10
11    // 需要复写以下两个方法:
12
13    @Override
14    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
15        System.out.println("Get...");
16    }
17
18    @Override
19    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
20        System.out.println("Post...");
21    }
22}
  • 启动Tomcat,使用浏览器访问http://localhost:8080/web-demo/demo可以在控制台看到doGet()方法被执行。

  • 在项目的webapp目录下新建index.html,写入:

     1<!DOCTYPE html>
     2<html lang="zh">
     3<head>
     4    <meta charset="UTF-8">
     5    <title>TestPost</title>
     6</head>
     7<body>
     8    <form action="/web-demo/demo" method="post">
     9        <input name="username"/><input type="submit"/>
    10    </form>
    11</body>
    12</html>
    

    启动Tomcat,访问http://localhost:8080/web-demo/,在表单输入内容后提交。即可在控制台看到doPost()被执行。

使用Servlet实现HttpServlet

既然HttpServlet继承自Servlet,那么也可以通过编写Servlet类来实现HttpServlet:

 1package com.linner.web;
 2
 3import javax.servlet.*;
 4import javax.servlet.http.HttpServletRequest;
 5import java.io.IOException;
 6
 7public class MyHttpServlet implements Servlet {
 8
 9    @Override
10    public void init(ServletConfig servletConfig) throws ServletException {
11
12    }
13
14    @Override
15    public ServletConfig getServletConfig() {
16        return null;
17    }
18
19    @Override
20    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
21        // 根据请求方式的不同,分别进行处理
22
23        HttpServletRequest request = (HttpServletRequest) servletRequest;
24        // 1. 获取请求方式
25        String method = request.getMethod();
26
27        // 2. 判断
28        if ("GET".equals(method)) {
29            // GET 方式的处理逻辑
30            doGet(servletRequest, servletResponse);
31        } else if ("POST".equals(method)) {
32            // POST 方式的处理逻辑
33            doPost(servletRequest, servletResponse);
34        }
35    }
36
37    protected void doPost(ServletRequest servletRequest, ServletResponse servletResponse) {
38    }
39
40    protected void doGet(ServletRequest servletRequest, ServletResponse servletResponse) {
41    }
42
43    @Override
44    public String getServletInfo() {
45        return null;
46    }
47
48    @Override
49    public void destroy() {
50
51    }
52}

翻阅HttpServlet.service()方法源码,可以发现HttpServlet不仅仅可以对GET和POST进行处理,还能处理其它五种请求:doHead()、doPut()、doDelete()、doOptions()和doTrace()。


HttpServletRequest 和 HttpServletResponse

Request 和 Response 概述

Request是请求对象,Response是响应对象。在Servlet中也存在这样的两个对象:

 1public class ServletDemo implements Servlet {
 2
 3    @Override
 4    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
 5        // ...
 6    }
 7
 8    // ......
 9
10}

Request作用:获取请求数据

  • 浏览器会发送HTTP请求到后台服务器(如,Tomcat)

  • HTTP的请求中会包含很多请求数据

    如,HTTP协议请求:

    • 请求行
    • 请求头
    • 请求体
  • 后台服务器会对HTTP请求中的数据进行解析并把解析结果存入到一个对象中

    所存入的对象即为Request对象,所以我们可以从Request对象中获取请求的相关参数

  • 获取到数据后就可以继续后续的业务

    如,获取用户名和密码就可以实现登录操作的相关业务

Response作用:设置响应数据

  • 业务处理完后,后台就需要给前端返回业务处理的结果(即,响应数据)
  • 把响应数据封装到Response对象中
  • 后台服务器会解析Response对象,按照格式(响应行+响应头+响应体)拼接结果
  • 浏览器最终解析结果,把内容展示在浏览器给用户浏览

而HttpServlet使用的 Request 和 Response 对象与Servlet有所不同。HttpServlet使用的是 HttpServletRequest 和 HttpServletResponse。

Example:

 1@WebServlet("/demo")
 2public class HttpServletDemo extends HttpServlet {
 3    @Override
 4    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
 5        // 使用request对象 获取请求数据
 6        String name = request.getParameter("name"); 
 7        // Parameter在地址中以 ? 开始:url?name=zhangsan
 8
 9        // 使用response对象 设置响应数据
10        response.setHeader("content-type","text/html;charset=utf-8");
11        response.getWriter().write("<h1>"+name+",欢迎您!</h1>");
12    }
13
14    @Override
15    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
16        this.doGet(request, response);
17    }
18}

Request 和 Response 继承体系

HttpServletRequest和ServletRequest之间是继承关系,HttpServletResponse和ServletResponse是继承关系。

Request之间的继承关系如下:

Response之间的继承关系如下:


HttpServletRequest

HTTP常用的请求方式为:

  • GET
  • POST

HTTP请求数据总共分为三部分内容:

  • 请求行
  • 请求头
  • 请求体

在请求数据中,还包含着请求参数:

  • 对于GET:请求参数包含在请求头中。
  • 对于POST:请求参数一般包含在请求体中。

获取请求行数据

如打开以下链接:

1http://localhost:8080/HttpServlet/httpservlet.html?username=linner

其请求行大致内容如下:

1GET /HttpServlet/httpservlet.html?username=linner HTTP/1.1

包含以下三部分内容:

  • 请求方式:GET

  • 请求资源路径:/HttpServlet/httpservlet.html?username=linner

    请求资源路径包含:

    • 虚拟目录(项目访问路径):/HttpServlet
    • URI(统一资源标识符):/HttpServlet/httpservlet.html
    • 请求参数:username=linner
  • HTTP协议及版本:HTTP/1.1

这三部分内容,HttpServletRequest对象都提供了对应的API方法来获取:

  • 获取请求方式:

    1String getMethod()
    

    返回:GET

  • 获取虚拟目录(项目访问路径):

    1String getContextPath()
    

    返回:/HttpServlet

  • 获取URL(统一资源定位符):

    1StringBuffer getRequestURL()
    

    返回:http://localhost:8080/HttpServlet/httpservlet.htm

  • 获取URI(统一资源标识符):

    1String getRequestURI()
    

    返回:/HttpServlet/httpservlet.html

  • 获取请求参数(GET方式):

    1String getQueryString()
    

    返回:username=linner(多个参数也一并返回)

Example:

 1package com.linner.web;
 2
 3import javax.servlet.*;
 4import javax.servlet.http.*;
 5import javax.servlet.annotation.*;
 6import java.io.IOException;
 7
 8@WebServlet("/test")
 9public class TestHttpServlet extends HttpServlet {
10    @Override
11    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
12        System.out.println("---------------------------------------");
13        System.out.println("请求行:");
14        System.out.println("请求方式:" + request.getMethod());
15        System.out.println("虚拟目录:" + request.getContextPath());
16        System.out.println("URL:" + request.getRequestURL());
17        System.out.println("URI:" + request.getRequestURI());
18        System.out.println("请求参数:" + request.getQueryString());
19        System.out.println("---------------------------------------");
20    }
21
22    @Override
23    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
24        this.doGet(request, response);
25    }
26}

获取请求头数据

请求头数据由多个 key: value 组成,如客户端浏览器的版本信息:

1User-Agent: Mozila/5.0 Chrome/105.0.0.0 Edg/105.0.1343.42

HttpServletRequest 获取请求头的方法为:

1String getHeader(String name)
  • name:是请求头中的key。
  • 返回值:返回name对应key的value。

使用getHeader()获取客户端浏览器的版本信息:

 1package com.linner.web.request;
 2
 3import javax.servlet.ServletException;
 4import javax.servlet.annotation.WebServlet;
 5import javax.servlet.http.HttpServlet;
 6import javax.servlet.http.HttpServletRequest;
 7import javax.servlet.http.HttpServletResponse;
 8import java.io.IOException;
 9
10@WebServlet("/test")
11public class RequestDemo7 extends HttpServlet {
12
13    @Override
14    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
15        String agent = request.getHeader("user-agent"); // name 不区分大小写
16        System.out.println(agent);
17    }
18
19    @Override
20    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
21        this.doGet(request, response);
22    }
23}

输出:

1Mozila/5.0 Chrome/105.0.0.0 Edg/105.0.1343.42

获取请求体数据

浏览器发送的GET请求,是没有请求体的。只有在发送POST请求时才带有请求体。

请求体中的数据格式如:

1username=linner&password=123456

与资源路径中,请求参数的格式一样。

HttpServletRequest提供了两种方式来获取请求体中的数据:

  • 获取字节输入流:

    当前端发送的是字节数据,如传递的是文件数据时使用。

    1ServletInputStream getInputStream()
    
  • 获取字符输入流:

    当前端发送的是纯文本数据时使用。

    1BufferedReader getReader()
    

如果要在客户端浏览器发送POST请求,需要编写一个<form>表单。

Example:

  1. 在项目的webapp目录下添加index.html:

     1<!DOCTYPE html>
     2<html lang="zh">
     3<head>
     4    <meta charset="UTF-8">
     5    <title>Title</title>
     6</head>
     7<body>
     8    <!-- 
     9        action: 表单提交的请求地址
    10        method: 请求方式,指定为post
    11    -->
    12    <form action="/request-demo/test" method="post">
    13        <input type="text" name="username">
    14        <input type="password" name="password">
    15        <input type="submit">
    16    </form>
    17</body>
    18</html>
    
  2. 在doPost方法中获取数据:

    由于index.html提交的是纯文本数据,所以要使用getReader()方法获取。

     1package com.linner.web.request;
     2
     3import javax.servlet.ServletException;
     4import javax.servlet.annotation.WebServlet;
     5import javax.servlet.http.HttpServlet;
     6import javax.servlet.http.HttpServletRequest;
     7import javax.servlet.http.HttpServletResponse;
     8import java.io.BufferedReader;
     9import java.io.IOException;
    10
    11@WebServlet("/test")
    12public class RequestDemo8 extends HttpServlet {
    13
    14    @Override
    15    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    16
    17    }
    18
    19    @Override
    20    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    21        // 1. 获取字符输入流
    22        BufferedReader br = request.getReader();
    23        // 2. 读取数据
    24        String line = br.readLine();
    25        System.out.println(line);
    26    }
    27}
    

    BufferedReader流是通过HttpServletRequest对象来获取的,当请求完成后HttpServletRequest对象就会被销毁,HttpServletRequest对象被销毁后,BufferedReader流就会自动关闭,所以就不需要手动关闭流了。 getReader()获取请求参数后,还需要使用readLine()读取参数数据。

  3. 通过浏览器访问:http://localhost:8080/request-demo/。在表单中输入内容,然后提交,就可以在控制台看到前端所发送的请求数据:

    1username=linner&password=123456
    

获取请求参数

使用getQueryString()方法和getReader()分别获取GET和POST的请求参数:

 1package com.linner.web;
 2
 3import javax.servlet.ServletException;
 4import javax.servlet.annotation.WebServlet;
 5import javax.servlet.http.HttpServlet;
 6import javax.servlet.http.HttpServletRequest;
 7import javax.servlet.http.HttpServletResponse;
 8import java.io.IOException;
 9
10@WebServlet("/test")
11public class RequestDemo extends HttpServlet {
12    @Override
13    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
14        String result = req.getQueryString();
15        System.out.println(result);
16
17    }
18    @Override
19    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
20        BufferedReader br = req.getReader();
21        String result = br.readLine();
22        System.out.println(result);
23    }
24}

上述代码存在的问题:

  • doGet()和doPost中出现了重复代码。

    在实际业务中,可能会出现很多相同的业务代码。

  • doGet()和doPost都必须存在。

  • GET请求和POST请求获取请求参数的方式不一样。

  1. 在doPost()中调用doGet(),然后在doGet()判断请求的方式,并分别做处理:

     1package com.linner.web;
     2
     3import javax.servlet.ServletException;
     4import javax.servlet.annotation.WebServlet;
     5import javax.servlet.http.HttpServlet;
     6import javax.servlet.http.HttpServletRequest;
     7import javax.servlet.http.HttpServletResponse;
     8import java.io.IOException;
     9
    10@WebServlet("/test")
    11public class RequestDemo extends HttpServlet {
    12    @Override
    13    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    14        // 1. 获取请求方式
    15        String method = req.getMethod();
    16        // 2. 获取请求参数
    17        String params = "";
    18        if("GET".equals(method)){
    19            params = req.getQueryString();
    20        }else if("POST".equals(method)){
    21            BufferedReader reader = req.getReader();
    22            params = reader.readLine();
    23        }
    24        // 3. 处理请求
    25        System.out.println(params);
    26
    27    }
    28    @Override
    29    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    30        this.doGet(req,resp);
    31    }
    32}
    
  2. HttpServletRequest已经对获取请求参数的方式进行了封装:

    • 获取所有参数Map集合

      1Map<String, String[]> getParameterMap()
      
    • 根据名称获取参数值(返回值为数组,返回多个参数)

      1String[] getParameterValues(String name)
      
    • 根据名称获取参数值(单个值)

      1String getParameter(String name)
      

    同样是在doPost()中调用doGet(),然后在doGet()处理参数,但是在获取参数时不用对请求方式进行判断。

    Example:

    1. webapp/index.html:

       1<!DOCTYPE html>
       2<html lang="zh">
       3<head>
       4    <meta charset="UTF-8">
       5    <title>Title</title>
       6</head>
       7<body>
       8    test:get
       9    <form action="/request-demo/test" method="get">
      10        <input type="text" name="username"><br>
      11        <input type="password" name="password"><br>
      12        <input type="checkbox" name="hobby" value="1"> get-1
      13        <input type="checkbox" name="hobby" value="2"> get-2 <br>
      14        <input type="submit">
      15    </form>
      16    test:post
      17    <form action="/request-demo/test" method="post">
      18        <input type="text" name="username"><br>
      19        <input type="password" name="password"><br>
      20        <input type="checkbox" name="hobby" value="1"> post-1
      21        <input type="checkbox" name="hobby" value="2"> post-2 <br>
      22        <input type="submit">
      23    </form>
      24</body>
      25</html>
      
    2. 使用getParameterValues()和getParameter()获取请求参数:

       1package com.linner.web;
       2
       3import javax.servlet.ServletException;
       4import javax.servlet.annotation.WebServlet;
       5import javax.servlet.http.HttpServlet;
       6import javax.servlet.http.HttpServletRequest;
       7import javax.servlet.http.HttpServletResponse;
       8import java.io.IOException;
       9
      10@WebServlet("/test1")
      11public class RequestDemo1 extends HttpServlet {
      12    @Override
      13    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
      14        System.out.println("---------------");
      15        // 验证请求的方式:
      16        String method = req.getMethod();
      17        System.out.println(method);
      18
      19        // 获取多个 value:
      20        System.out.print("hobby: ");
      21        String[] hobbies = request.getParameterValues("hobby");
      22        for (String hobby : hobbies) {
      23            System.out.print(hobby + ", ");
      24        }
      25        System.out.println("\b\b  ");
      26
      27        // 获取单个 value:
      28        String username = request.getParameter("username");
      29        String password = request.getParameter("password");
      30        System.out.println("username: " + username);
      31        System.out.println("password: " + password);
      32    }
      33
      34    @Override
      35    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
      36        this.doGet(request, response);
      37    }
      38}
      
    3. 使用getParameterMap()一次性获取所有参数:

       1package com.linner.web;
       2
       3import javax.servlet.ServletException;
       4import javax.servlet.annotation.WebServlet;
       5import javax.servlet.http.HttpServlet;
       6import javax.servlet.http.HttpServletRequest;
       7import javax.servlet.http.HttpServletResponse;
       8import java.io.IOException;
       9import java.util.Map;
      10
      11@WebServlet("/test")
      12public class RequestDemo extends HttpServlet {
      13    @Override
      14    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
      15        System.out.println("-------------------------------------------");
      16        // 验证请求的方式:
      17        String method = request.getMethod();
      18        System.out.println(method);
      19
      20        // 获取所有参数的Map集合
      21        Map<String, String[]> map = request.getParameterMap();
      22        for (String key : map.keySet()) {
      23            System.out.print(key + ":");
      24
      25            // 获取key对应的所有values
      26            String[] values = map.get(key);
      27            for (String value : values) {
      28                System.out.print(value + ", ");
      29            }
      30            System.out.println("\b\b  ");
      31        }
      32    }
      33
      34    @Override
      35    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
      36        this.doGet(request, response);
      37    }
      38}
      

请求转发

请求转发(forward)是一种在服务器内部的资源跳转方式。如:

  1. 服务器内资源A获取请求。
  2. 资源A(处理一部分数据后)将请求转发给资源B去处理。
  3. 资源B处理完成后将将结果响应给浏览器。

请求从资源A到资源B的过程即为请求转发。

请求转发的特点:

  • 浏览器地址栏路径不变。
  • 只能转发到当前服务器的内部资源。
  • 一次请求,可以在转发的资源间使用request共享数据。

使用request.getRequestDispatcher("/path").forward(request, response)进行请求转发:

 1package com.linner.web;
 2
 3import javax.servlet.ServletException;
 4import javax.servlet.annotation.WebServlet;
 5import javax.servlet.http.HttpServlet;
 6import javax.servlet.http.HttpServletRequest;
 7import javax.servlet.http.HttpServletResponse;
 8import java.io.IOException;
 9
10@WebServlet("/demo1")
11public class RequestDemo1 extends HttpServlet {
12    @Override
13    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
14        System.out.println("demo1...");
15
16        // 存储数据
17        request.setAttribute("msg", "Hello");
18
19        // 请求转发(资源转发到demo2)
20        request.getRequestDispatcher("/demo2").forward(request, response);
21    }
22
23    @Override
24    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
25        this.doGet(request, response);
26    }
27}
 1package com.linner.web;
 2
 3import javax.servlet.ServletException;
 4import javax.servlet.annotation.WebServlet;
 5import javax.servlet.http.HttpServlet;
 6import javax.servlet.http.HttpServletRequest;
 7import javax.servlet.http.HttpServletResponse;
 8import java.io.IOException;
 9
10@WebServlet("/demo2")
11public class RequestDemo2 extends HttpServlet {
12    @Override
13    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
14        System.out.println("demo2...");
15
16        // 获取数据
17        Object msg = request.getAttribute("msg");
18        System.out.println(msg);
19    }
20
21    @Override
22    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
23        this.doGet(request, response);
24    }
25}

通过http://localhost:8080/request-demo/demo1访问。

由于请求转发是在服务器内部的资源转发,所以getRequestDispatcher()中的路径不需要包含虚拟目录。

请求转发使用HttpServletRequest(Request)对象进行资源的传递。这个用来存储资源的空间被称为Request域。

HttpServletRequest对象提供了对于Request中的域属性操作的方法有:

  1. 在 Request 域属性空间中放入数据:

    1void setAttribute(String name, Object object)
    

    其生命周期与 Request 的生命周期相同。

  2. 从 Request 的域属性空间中获取指定名称的数据:

    1Object getAttribute(String name)
    
  3. 从 Request 的域属性空间中删除指定名称的数据:

    1void removeAttribute(String name)
    
  4. 创建请求转发器:

    1RequestDispatcher getRequestDispatcher(String path)
    

    请求转发器中有一个方法,用于完成将请求对象转发给下一个资源:

    1void forward(HttpServletRequest request, HttpServletResponse response)
    

Tomcat7 请求参数中文乱码问题

Tomcat8.0 之后,已经将默认编码设置为UTF-8。

POST请求参数是通过流的方式获取数据:

  • Tomcat在获取流的时候采用的编码是ISO-8859-1。
  • 页面设置的编码格式一般为UTF-8。
  • ISO-8859-1编码是不支持中文的,所以会出现乱码。

解决方案:通过HttpServletRequest提供的setCharacterEncoding(),在Tomcat在获取流数据之前的编码设置为UTF-8。

 1package com.linner.web;
 2
 3import javax.servlet.ServletException;
 4import javax.servlet.annotation.WebServlet;
 5import javax.servlet.http.HttpServlet;
 6import javax.servlet.http.HttpServletRequest;
 7import javax.servlet.http.HttpServletResponse;
 8import java.io.BufferedReader;
 9import java.io.IOException;
10import java.net.URLDecoder;
11import java.nio.charset.StandardCharsets;
12
13@WebServlet("/test")
14public class RequestDemo4Copy extends HttpServlet {
15    @Override
16    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
17    }
18
19    @Override
20    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
21        // 1. 设置字符输入流的编码
22        request.setCharacterEncoding("UTF-8");
23
24        // 2. 获取请求参数
25        BufferedReader br = request.getReader();
26        String line = br.readLine();
27        // getReader()获取的是编码后的URL,如果要显示中文,需要对URL按照UTF-8进行解码
28        String decode = URLDecoder.decode(line, StandardCharsets.UTF_8);
29        System.out.println("解决乱码后: " + decode);
30    }
31}

GET请求参数包含在URL中:

getQueryString()获取的并不是字符输入流,所以setCharacterEncoding()并不适用。

  • 浏览器在发送HTTP的过程中会根据页面<meta>标签指定的charset的方式(一般为UTF-8)对URL进行编码。 URL编码:

    1. 将字符串按照编码方式转为二进制。
    2. 每个字节(8位)转为2个16进制数(一个16进制数代表4位)并在前边加上%。
  • Tomcat在接收编码后的URL后,会默认按照ISO-8859-1进行URL解码。

    可以使用以下两个函数可以模拟URL编码、解码的过程:

    1. 编码:

      1java.net.URLEncoder.encode(string, charset)
      
    2. 解码:

      1java.net.URLDecoder.decode(string, charset)
      

解决方案:

  1. 把字符数据(URL编码)按照ISO-8859-1编码转换成字节。
  2. 字节按照浏览器对应的URL编码(UTF-8)转换成对应的字符。

这样在转换的过程中保持编码一致,就可以解决中文乱码问题:

 1package com.linner.web;
 2
 3import javax.servlet.ServletException;
 4import javax.servlet.annotation.WebServlet;
 5import javax.servlet.http.HttpServlet;
 6import javax.servlet.http.HttpServletRequest;
 7import javax.servlet.http.HttpServletResponse;
 8import java.io.IOException;
 9import java.net.URLDecoder;
10import java.nio.charset.StandardCharsets;
11
12@WebServlet("/test")
13public class RequestDemo4Copy extends HttpServlet {
14    @Override
15    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
16        String qs = request.getQueryString();
17        // 把字符数据(URL编码)按照ISO-8859-1编码转换成字节
18        byte[] bytes = qs.getBytes(StandardCharsets.ISO_8859_1);
19        // 按照UTF-8编码转换成对应的字符
20        String s = new String(bytes, StandardCharsets.UTF_8);
21        // 转换后的字符是URL编码后的字符,需要再次解码
22        qs = URLDecoder.decode(s, StandardCharsets.UTF_8);
23        System.out.println("解决乱码后: " + qs);
24    }
25
26    @Override
27    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
28    }
29}

通用方式解决乱码问题:

 1package com.linner.web;
 2
 3import javax.servlet.ServletException;
 4import javax.servlet.annotation.WebServlet;
 5import javax.servlet.http.HttpServlet;
 6import javax.servlet.http.HttpServletRequest;
 7import javax.servlet.http.HttpServletResponse;
 8import java.io.IOException;
 9import java.nio.charset.StandardCharsets;
10
11@WebServlet("/test")
12public class RequestDemo4Copy extends HttpServlet {
13    @Override
14    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
15        // 1. 获取username
16        String username = request.getParameter("username");
17        // 2. 解决乱码
18        username = new String(username.getBytes(StandardCharsets.ISO_8859_1),
19                            StandardCharsets.UTF_8);
20        System.out.println("解决乱码后: " + username);
21    }
22
23    @Override
24    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
25        this.doGet(request, response);
26    }
27}

HttpServletResponse

HTTP响应数据总共分为三部分内容,分别是:

  • 响应行

    如,HTTP/1.1 200 OK,从左到右分别为:

    • HTTP协议及版本

    • 响应状态码 设置响应状态码,HttpServletResponse对象提供了以下方法设置:

      1void setStatus(int sc)
      
    • 状态码描述

  • 响应头

    由多个 key: value 组成。HttpServletResponse对象提供了以下方法设置键值对:

    1void setHeader(String name, String value)
    
  • 响应体

    • 获取字符输出流:

      1PrintWriter getWriter()
      
    • 获取字节输出流:

      1ServletOutputStream getOutputStream()
      

Respones重定向

Response重定向(redirect)是一种资源跳转方式。如:

  1. 浏览器发送请求给服务器,服务器中对应的资源A接收到请求。
  2. 资源A现在无法处理该请求,就会给浏览器响应一个302的状态码和location(一个访问资源B的路径)。
  3. 浏览器接收到响应状态码为302就会重新发送请求到location对应的访问地址去访问资源B。

重定向的特点:

  • 浏览器地址栏路径发送变化(由资源A的路径变化为资源B的路径)。

    进行重定向访问时,由浏览器发送两次请求,所以地址发生了变化。

  • 可以重定向到任意位置的资源(服务器内部、外部均可)。

    资源由浏览器来访问,所以可以重定向到任意位置资源。

  • 不能在多个资源使用Request重定向共享数据。

    重定向是由浏览器来发送新的请求,每次请求中的Request对象都是不同的。

重定向需要两个步骤:

  1. 设置302状态码:

    1response.setStatus(302)
    
  2. 设置响应头中,location的值:

    1response.setHeader("location", "/path_b")
    

Example:

 1package com.linner.web;
 2
 3import javax.servlet.ServletException;
 4import javax.servlet.annotation.WebServlet;
 5import javax.servlet.http.HttpServlet;
 6import javax.servlet.http.HttpServletRequest;
 7import javax.servlet.http.HttpServletResponse;
 8import java.io.IOException;
 9
10@WebServlet("/demo1")
11public class ResponseDemo1 extends HttpServlet {
12    @Override
13    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
14        System.out.println("demo1...");
15
16        // 重定向
17        // 1. 设置相应状态码
18        resp.setStatus(302);
19        // 2. 设置相应头 Location (不区分大小写)
20        resp.setHeader("Location", "/request-demo/demo2");
21    }
22
23    @Override
24    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
25        this.doGet(req, resp);
26    }
27}
 1package com.linner.web;
 2
 3import javax.servlet.ServletException;
 4import javax.servlet.annotation.WebServlet;
 5import javax.servlet.http.HttpServlet;
 6import javax.servlet.http.HttpServletRequest;
 7import javax.servlet.http.HttpServletResponse;
 8import java.io.IOException;
 9
10@WebServlet("/demo2")
11public class ResponseDemo2 extends HttpServlet {
12    @Override
13    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
14        System.out.println("demo2...");
15    }
16
17    @Override
18    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
19        this.doGet(req, resp);
20    }
21}

由于重定向是将重定向后的资源路径告知浏览器,所以location需要添加虚拟目录(如/response-demo/demo2)。 如果是重定向到服务器外部资源,location的值为外部资源的URL。

HttpServletResponse提供了sendRedirect()方法来简化重定向流程。修改ResponseDemo1:

 1package com.linner.web.response;
 2
 3import javax.servlet.ServletException;
 4import javax.servlet.annotation.WebServlet;
 5import javax.servlet.http.HttpServlet;
 6import javax.servlet.http.HttpServletRequest;
 7import javax.servlet.http.HttpServletResponse;
 8import java.io.IOException;
 9
10@WebServlet("/resp1")
11public class ResponseDemo1 extends HttpServlet {
12    @Override
13    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
14        System.out.println("resp1...");
15
16        // 简化方式完成重定向
17        // 动态获取虚拟目录
18        String contextPath = req.getContextPath();
19        resp.sendRedirect(contextPath + "/resp2");
20
21    @Override
22    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
23        this.doGet(req, resp);
24    }
25}

响应字符数据

将字符数据写回到浏览器,需要:

  1. 通过HttpServletResponse对象获取字符输出流:

    1PrintWriter writer = response.getWriter()
    
  2. 通过字符输出流写数据:

    1writer.write("你好")
    

Example:

 1package com.linner.web;
 2
 3import javax.servlet.ServletException;
 4import javax.servlet.annotation.WebServlet;
 5import javax.servlet.http.HttpServlet;
 6import javax.servlet.http.HttpServletRequest;
 7import javax.servlet.http.HttpServletResponse;
 8import java.io.IOException;
 9import java.io.PrintWriter;
10
11@WebServlet("/demo")
12public class ResponseDemo extends HttpServlet {
13    @Override
14    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
15        // 设置content-type(响应的数据格式)和字符集(编码)
16        resp.setContentType("text/html;charset=utf-8");
17        // content-type也可以使用setHeader()手动设置
18        // resp.setHeader("content-type", "text/html");
19
20        // 获取字符输出流
21        PrintWriter writer = resp.getWriter();
22        writer.write("你好");
23        writer.write("<h1>Hello World!</h1>");
24        // 
25    }
26
27    @Override
28    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
29        this.doGet(req, resp);
30    }
31}

write()不仅能写入文本数据,还能写入HTML数据。 PrintWriter 对象会在 HttpServletRequest 被销毁时一并销毁,无需手动关闭。

响应字节数据

将字节数据写回到浏览器,需要:

  1. 通过HttpServletResponse对象获取字节输出流:

    1ServletOutputStream os = response.getOutputStream()
    
  2. 通过字节输出流写数据:

    1os.write(buff)
    

Example:

 1package com.linner.web;
 2
 3import javax.servlet.ServletException;
 4import javax.servlet.ServletOutputStream;
 5import javax.servlet.annotation.WebServlet;
 6import javax.servlet.http.HttpServlet;
 7import javax.servlet.http.HttpServletRequest;
 8import javax.servlet.http.HttpServletResponse;
 9import java.io.FileInputStream;
10import java.io.IOException;
11import java.io.PrintWriter;
12
13@WebServlet("/demo")
14public class ResponseDemo extends HttpServlet {
15    @Override
16    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
17        // 1. 读取文件
18        FileInputStream fis = new FileInputStream("src/main/webapp/imgs/bg.jpg");
19
20        // 2. 获取response字节输出流
21        ServletOutputStream os = resp.getOutputStream();
22
23        // 3. 完成流的copy
24        byte[] buff = new byte[1024];
25        int len = 0;
26        while ((len = fis.read(buff)) != -1) {
27            os.write(buff, 0, len);
28        }
29
30        fis.close();
31    }
32
33    @Override
34    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
35        this.doGet(req, resp);
36    }
37}

其中,流的copy可以使用IOUtils工具类的copy()来简化操作:

  • 导入配置:

    1<dependency>
    2    <groupId>commons-io</groupId>
    3    <artifactId>commons-io</artifactId>
    4    <version>2.6</version>
    5</dependency>
    
  • 修改ResponseDemo:

     1@Override
     2protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
     3    // 1. 读取文件
     4    FileInputStream fis = new FileInputStream("src/main/webapp/imgs/reg_bg_min.jpg");0
     5
     6    // 2. 获取response字节输出流
     7    ServletOutputStream os = resp.getOutputStream();
     8
     9    // 3. 完成流的copy
    10    IOUtils.copy(fis, os);
    11
    12    fis.close();
    13}
    

总结

HttpServletRequest 常用方法

方法名作用
String getMethod()获取请求方式
String getContextPath()获取虚拟目录(项目访问路径)
StringBuffer getRequestURL()获取URL(统一资源定位符)
String getRequestURI()获取URI(统一资源标识符)
String getQueryString()获取请求参数(GET方式)
String getHeader(String name)获取name指定key对应的请求头的value
ServletInputStream getInputStream()获取请求体字节输入流(POST方式获取请求参数)
BufferedReader getReader()获取请求体字符输入流
getReader()获取请求参数后,还需要使用readLine()读取参数数据
即,
BufferedReader br = request.getReader();
String line = br.readLine();
Map<String, String[]> getParameterMap()获取所有请求参数Map集合
String[] getParameterValues(String name)根据名称获取请求参数值
返回值为数组
返回多个参数
String getParameter(String name)根据名称获取请求参数值
返回单个参数值
void setAttribute(String name, Object object)在 Request 域属性空间中放入数据
Object getAttribute(String name)从 Request 的域属性空间中获取指定名称的数据
void removeAttribute(String name)从 Request 的域属性空间中删除指定名称的数据
RequestDispatcher getRequestDispatcher(String path)创建请求转发器
请求转发器中有一个方法,用于完成将请求对象转发给下一个资源:
void forward(HttpServletRequest request, HttpServletResponse response)
void setCharacterEncoding(String charset)设置请求体字符输入流的编码

HttpServletResponse 常用方法

方法名作用
void setStatus(int sc)设置响应状态码
void setHeader(String name, String value)设置响应头键值对
PrintWriter getWriter()获取响应体字符输出流获取字符输出流后还需要使用write()方法来写入字符数据:
PrintWriter writer = response.getWriter();
writer.write("Hello World!");
ServletOutputStream getOutputStream()获取响应体字节输出流
获取字节输出流前需要先使用FileInputStream对象来读取文件
然后使用IOUtils工具类的copy()来copy流
获取字节输出流后还需要使用write()方法来写入字符数据
响应字节数据
void sendRedirect(String path)Respones重定向