HttpServlet 简介

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

Servlet的体系结构:

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

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

使用HttpServlet的格式如下:

package com.linner.web;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.IOException;

@WebServlet("/demo")
public class ServletDemo extends HttpServlet {

    // 需要复写以下两个方法:

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Get...");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Post...");
    }
}
  • 启动Tomcat,使用浏览器访问http://localhost:8080/web-demo/demo可以在控制台看到doGet()方法被执行。

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

    <!DOCTYPE html>
    <html lang="zh">
    <head>
        <meta charset="UTF-8">
        <title>TestPost</title>
    </head>
    <body>
        <form action="/web-demo/demo" method="post">
            <input name="username"/><input type="submit"/>
        </form>
    </body>
    </html>
    

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

使用Servlet实现HttpServlet

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

package com.linner.web;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

public class MyHttpServlet implements Servlet {

    @Override
    public void init(ServletConfig servletConfig) throws ServletException {

    }

    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        // 根据请求方式的不同,分别进行处理

        HttpServletRequest request = (HttpServletRequest) servletRequest;
        // 1. 获取请求方式
        String method = request.getMethod();

        // 2. 判断
        if ("GET".equals(method)) {
            // GET 方式的处理逻辑
            doGet(servletRequest, servletResponse);
        } else if ("POST".equals(method)) {
            // POST 方式的处理逻辑
            doPost(servletRequest, servletResponse);
        }
    }

    protected void doPost(ServletRequest servletRequest, ServletResponse servletResponse) {
    }

    protected void doGet(ServletRequest servletRequest, ServletResponse servletResponse) {
    }

    @Override
    public String getServletInfo() {
        return null;
    }

    @Override
    public void destroy() {

    }
}

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


HttpServletRequest 和 HttpServletResponse

Request 和 Response 概述

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

public class ServletDemo implements Servlet {

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        // ...
    }

    // ......

}

Request作用:获取请求数据

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

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

    如,HTTP协议请求:

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

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

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

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

Response作用:设置响应数据

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

而HttpServlet使用的 RequestResponse 对象与Servlet有所不同。HttpServlet使用的是 HttpServletRequestHttpServletResponse

Example:

@WebServlet("/demo")
public class HttpServletDemo extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 使用request对象 获取请求数据
        String name = request.getParameter("name"); 
        // Parameter在地址中以 ? 开始:url?name=zhangsan

        // 使用response对象 设置响应数据
        response.setHeader("content-type","text/html;charset=utf-8");
        response.getWriter().write("<h1>"+name+",欢迎您!</h1>");
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}

Request 和 Response 继承体系

HttpServletRequestServletRequest之间是继承关系,HttpServletResponseServletResponse是继承关系。

Request之间的继承关系如下:

Response之间的继承关系如下:


HttpServletRequest

HTTP常用的请求方式为:

  • GET
  • POST

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

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

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

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

获取请求行数据

如打开以下链接:

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

其请求行大致内容如下:

GET /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方法来获取:

  • 获取请求方式:

    String getMethod()
    

    返回:GET

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

    String getContextPath()
    

    返回:/HttpServlet

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

    StringBuffer getRequestURL()
    

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

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

    String getRequestURI()
    

    返回:/HttpServlet/httpservlet.html

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

    String getQueryString()
    

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

Example:

package com.linner.web;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;

@WebServlet("/test")
public class TestHttpServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("---------------------------------------");
        System.out.println("请求行:");
        System.out.println("请求方式:" + request.getMethod());
        System.out.println("虚拟目录:" + request.getContextPath());
        System.out.println("URL:" + request.getRequestURL());
        System.out.println("URI:" + request.getRequestURI());
        System.out.println("请求参数:" + request.getQueryString());
        System.out.println("---------------------------------------");
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}

获取请求头数据

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

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

HttpServletRequest 获取请求头的方法为:

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

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

package com.linner.web.request;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/test")
public class RequestDemo7 extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String agent = request.getHeader("user-agent"); // name 不区分大小写
        System.out.println(agent);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}

输出:

Mozila/5.0 Chrome/105.0.0.0 Edg/105.0.1343.42

获取请求体数据

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

请求体中的数据格式如:

username=linner&password=123456

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

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

  • 获取字节输入流:

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

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

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

    BufferedReader getReader()
    

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

Example:

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

    <!DOCTYPE html>
    <html lang="zh">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <!-- 
            action: 表单提交的请求地址
            method: 请求方式,指定为post
        -->
        <form action="/request-demo/test" method="post">
            <input type="text" name="username">
            <input type="password" name="password">
            <input type="submit">
        </form>
    </body>
    </html>
    
  2. doPost方法中获取数据:

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

    package com.linner.web.request;
    
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.BufferedReader;
    import java.io.IOException;
    
    @WebServlet("/test")
    public class RequestDemo8 extends HttpServlet {
    
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
        }
    
        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            // 1. 获取字符输入流
            BufferedReader br = request.getReader();
            // 2. 读取数据
            String line = br.readLine();
            System.out.println(line);
        }
    }
    

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

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

    username=linner&password=123456
    

获取请求参数

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

package com.linner.web;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/test")
public class RequestDemo extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String result = req.getQueryString();
        System.out.println(result);

    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        BufferedReader br = req.getReader();
        String result = br.readLine();
        System.out.println(result);
    }
}

上述代码存在的问题:

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

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

  • doGet()doPost都必须存在。

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

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

    package com.linner.web;
    
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    @WebServlet("/test")
    public class RequestDemo extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            // 1. 获取请求方式
            String method = req.getMethod();
            // 2. 获取请求参数
            String params = "";
            if("GET".equals(method)){
                params = req.getQueryString();
            }else if("POST".equals(method)){
                BufferedReader reader = req.getReader();
                params = reader.readLine();
            }
            // 3. 处理请求
            System.out.println(params);
    
        }
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            this.doGet(req,resp);
        }
    }
    
  2. HttpServletRequest已经对获取请求参数的方式进行了封装:

    • 获取所有参数Map集合

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

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

      String getParameter(String name)
      

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

    Example:

    1. webapp/index.html

      <!DOCTYPE html>
      <html lang="zh">
      <head>
          <meta charset="UTF-8">
          <title>Title</title>
      </head>
      <body>
          test:get
          <form action="/request-demo/test" method="get">
              <input type="text" name="username"><br>
              <input type="password" name="password"><br>
              <input type="checkbox" name="hobby" value="1"> get-1
              <input type="checkbox" name="hobby" value="2"> get-2 <br>
              <input type="submit">
          </form>
          test:post
          <form action="/request-demo/test" method="post">
              <input type="text" name="username"><br>
              <input type="password" name="password"><br>
              <input type="checkbox" name="hobby" value="1"> post-1
              <input type="checkbox" name="hobby" value="2"> post-2 <br>
              <input type="submit">
          </form>
      </body>
      </html>
      
    2. 使用getParameterValues()getParameter()获取请求参数:

      package com.linner.web;
      
      import javax.servlet.ServletException;
      import javax.servlet.annotation.WebServlet;
      import javax.servlet.http.HttpServlet;
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletResponse;
      import java.io.IOException;
      
      @WebServlet("/test1")
      public class RequestDemo1 extends HttpServlet {
          @Override
          protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
              System.out.println("---------------");
              // 验证请求的方式:
              String method = req.getMethod();
              System.out.println(method);
      
              // 获取多个 value:
              System.out.print("hobby: ");
              String[] hobbies = request.getParameterValues("hobby");
              for (String hobby : hobbies) {
                  System.out.print(hobby + ", ");
              }
              System.out.println("\b\b  ");
      
              // 获取单个 value:
              String username = request.getParameter("username");
              String password = request.getParameter("password");
              System.out.println("username: " + username);
              System.out.println("password: " + password);
          }
      
          @Override
          protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
              this.doGet(request, response);
          }
      }
      
    3. 使用getParameterMap()一次性获取所有参数:

      package com.linner.web;
      
      import javax.servlet.ServletException;
      import javax.servlet.annotation.WebServlet;
      import javax.servlet.http.HttpServlet;
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletResponse;
      import java.io.IOException;
      import java.util.Map;
      
      @WebServlet("/test")
      public class RequestDemo extends HttpServlet {
          @Override
          protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
              System.out.println("-------------------------------------------");
              // 验证请求的方式:
              String method = request.getMethod();
              System.out.println(method);
      
              // 获取所有参数的Map集合
              Map<String, String[]> map = request.getParameterMap();
              for (String key : map.keySet()) {
                  System.out.print(key + ":");
      
                  // 获取key对应的所有values
                  String[] values = map.get(key);
                  for (String value : values) {
                      System.out.print(value + ", ");
                  }
                  System.out.println("\b\b  ");
              }
          }
      
          @Override
          protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
              this.doGet(request, response);
          }
      }
      

请求转发

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

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

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

请求转发的特点:

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

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

package com.linner.web;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/demo1")
public class RequestDemo1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("demo1...");

        // 存储数据
        request.setAttribute("msg", "Hello");

        // 请求转发(资源转发到demo2)
        request.getRequestDispatcher("/demo2").forward(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}
package com.linner.web;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/demo2")
public class RequestDemo2 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("demo2...");

        // 获取数据
        Object msg = request.getAttribute("msg");
        System.out.println(msg);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}

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

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

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

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

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

    void setAttribute(String name, Object object)
    

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

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

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

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

    RequestDispatcher getRequestDispatcher(String path)
    

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

    void 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。

package com.linner.web;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;

@WebServlet("/test")
public class RequestDemo4Copy extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 设置字符输入流的编码
        request.setCharacterEncoding("UTF-8");

        // 2. 获取请求参数
        BufferedReader br = request.getReader();
        String line = br.readLine();
        // getReader()获取的是编码后的URL,如果要显示中文,需要对URL按照UTF-8进行解码
        String decode = URLDecoder.decode(line, StandardCharsets.UTF_8);
        System.out.println("解决乱码后: " + decode);
    }
}

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. 编码:

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

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

解决方案:

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

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

package com.linner.web;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;

@WebServlet("/test")
public class RequestDemo4Copy extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String qs = request.getQueryString();
        // 把字符数据(URL编码)按照ISO-8859-1编码转换成字节
        byte[] bytes = qs.getBytes(StandardCharsets.ISO_8859_1);
        // 按照UTF-8编码转换成对应的字符
        String s = new String(bytes, StandardCharsets.UTF_8);
        // 转换后的字符是URL编码后的字符,需要再次解码
        qs = URLDecoder.decode(s, StandardCharsets.UTF_8);
        System.out.println("解决乱码后: " + qs);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    }
}

通用方式解决乱码问题:

package com.linner.web;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

@WebServlet("/test")
public class RequestDemo4Copy extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 获取username
        String username = request.getParameter("username");
        // 2. 解决乱码
        username = new String(username.getBytes(StandardCharsets.ISO_8859_1),
                            StandardCharsets.UTF_8);
        System.out.println("解决乱码后: " + username);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}

HttpServletResponse

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

  • 响应行

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

    • HTTP协议及版本

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

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

  • 响应头

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

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

    • 获取字符输出流:

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

      ServletOutputStream getOutputStream()
      

Respones重定向

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

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

重定向的特点:

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

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

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

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

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

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

重定向需要两个步骤:

  1. 设置302状态码:

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

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

Example:

package com.linner.web;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/demo1")
public class ResponseDemo1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("demo1...");

        // 重定向
        // 1. 设置相应状态码
        resp.setStatus(302);
        // 2. 设置相应头 Location (不区分大小写)
        resp.setHeader("Location", "/request-demo/demo2");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req, resp);
    }
}
package com.linner.web;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/demo2")
public class ResponseDemo2 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("demo2...");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req, resp);
    }
}

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

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

package com.linner.web.response;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/resp1")
public class ResponseDemo1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("resp1...");

        // 简化方式完成重定向
        // 动态获取虚拟目录
        String contextPath = req.getContextPath();
        resp.sendRedirect(contextPath + "/resp2");

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req, resp);
    }
}

响应字符数据

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

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

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

    writer.write("你好")
    

Example:

package com.linner.web;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet("/demo")
public class ResponseDemo extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 设置content-type(响应的数据格式)和字符集(编码)
        resp.setContentType("text/html;charset=utf-8");
        // content-type也可以使用setHeader()手动设置
        // resp.setHeader("content-type", "text/html");

        // 获取字符输出流
        PrintWriter writer = resp.getWriter();
        writer.write("你好");
        writer.write("<h1>Hello World!</h1>");
        // 
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req, resp);
    }
}

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

响应字节数据

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

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

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

    os.write(buff)
    

Example:

package com.linner.web;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet("/demo")
public class ResponseDemo extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 1. 读取文件
        FileInputStream fis = new FileInputStream("src/main/webapp/imgs/bg.jpg");

        // 2. 获取response字节输出流
        ServletOutputStream os = resp.getOutputStream();

        // 3. 完成流的copy
        byte[] buff = new byte[1024];
        int len = 0;
        while ((len = fis.read(buff)) != -1) {
            os.write(buff, 0, len);
        }

        fis.close();
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req, resp);
    }
}

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

  • 导入配置:

    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.6</version>
    </dependency>
    
  • 修改ResponseDemo

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

总结

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重定向