Http响应和ServletResponse对象

Http响应和ServletResponse对象

Http 响应

与 Http 请求消息相对应,响应消息是指服务器发送给客户端浏览器的数据,可以分为响应行,响应头,响应空行,响应体。

响应行

  • 响应协议/版本:例如 HTTP/1.1
  • 响应状态码:服务器告知客户端浏览器本次请求和响应的状态
    1. 1xx:服务器收到客户端消息,但是需要客户端继续发送请求才能完成后续工作
    2. 2xx:成功,例如 200
    3. 3xx:重定向,例如 302 重定向,304 访问缓存
    4. 4xx:客户端错误,例如 404 请求路径没有对应的资源,405 请求方式没有对应的 doXxx 方法
    5. 5xx:服务器端错误,例如 500 服务器内部出现异常
  • 状态码描述:例如,响应行中 HTTP/1.1 200 OK,OK 代表成功

响应头

响应头中封装着服务器传递给客户端浏览器的一些信息,使用键值对存储,格式为 响应头名称:响应头值,常见的响应头有

  • Content-Type:服务器告知客户端浏览器本次响应体数据格式以及编码格式,响应头值例如 text/html; charset=utf-8
  • Content-disposition:服务器告知客户端浏览器以什么格式打开响应体数据,响应值例如
    • in-line 默认值,在当前页面内打开
    • attachment;filename=xxx 以附件形式打开响应体,可以用于文件下载

响应空行

就是一个空行,用于分隔响应头和响应体

响应体

服务器要传输给客户端的数据

ServletResponse

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

    }

service() 方法中需要传入的第二个参数,即 ServletResponse 对象,ServletResponse 的主要功能就是设置相应消息

  1. 设置响应状态码
    void setStatus(int var1)
    
  2. 设置响应头:入参分别为要设置的响应头的名称和值
    void setHeader(String name, String value)
    
  3. 设置响应体
    1. 获取输出流
      PrintWriter getWriter(); // 获取打印流,用于输出字符流
      ServletOutputStream getOutputStream(); // 获取字节输出流,用于输出字节流
      
    2. 使用输出流,将数据输出到客户端浏览器

使用 ServletResponse 完成重定向

重定向是区别于请求转发的另外一种资源跳转方式,当服务器接收到浏览器发来的请求时,通过响应告知浏览器要跳转的资源的路径,此时响应状态码为 302,然后浏览器使用新的资源路径重新发起请求

重定向

方法一 设置状态码和 location 响应头

response.setStatus(302);
response.setHeader("location","/ServletResponse/demo2"); // 参数二为要跳转的资源路径

方法二 直接使用封装好的方法

response.sendRedirect("/ServletResponse/demo2"); // 方法一中,只有资源路径是可变参数

重定向和请求转发作为两种资源跳转方式,有以下不同点

请求转发重定向
完成跳转的对象RequestDispatcher 对象的 forward() 方法ServletResponse 对象的 senndRedirect 方法
跳转的资源路径只能跳转至当前服务器的资源可以跳转至任意资源
浏览器地址栏路径不会改变路径会改变
请求是否是同一次是,可以使用 ServletRequest 域对象共享数据否,不可以使用 ServletRequest 域对象共享数据

输出字符流到浏览器

@WebServlet(urlPatterns = {"/demo3"})
public class ResponseDemo3 extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        PrintWriter pw = response.getWriter();
        String str = "<h1>输出字符流</h1>";
        pw.write(str);
    }

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

如果通过以上代码,向页面输出一个 html 标签 <h1>输出字符流</h1>,会发生乱码问题。这是因为输出流的默认编码与浏览器解析响应体的编码不同,解决乱码问题,需要在获取流之前,设置响应头 Content-Type,这句代码可以将输出流编码方式设置为 utf-8,同时告知浏览器响应体的数据格式以及编码格式。

response.setContentType("text/html;charset=utf-8");

关于项目中路径的写法

  • 相对路径,表示资源与资源之间的相对位置关系,不以 / 开头,./ 表示当前目录,../ 表示后退一级目录
  • 绝对路径,绝对路径可以确定唯一资源,例如 http://localhost:8080/ServletResponse/demo,或者 https://yzt.cool/index.html,或者 /ServletResponse/demo,项目内通常使用第三种表示方法,即省略协议、IP和端口号。
  • 何时使用相对路径,何时使用绝对路径?
    只要判断该路径给谁用,也就是判断请求将会从哪里发出
    • 请求从客户端浏览器发出:使用绝对路径,也就是需要加上项目的虚拟目录,以 /虚拟目录/ 开头,例如 <a> 标签中的 href 属性,<form> 表单中的 action 属性,重定向的目标资源路径。建议使用 request.getContextPath() 方法动态获取项目的虚拟目录,不要写死
    • 请求从服务器内部发出:使用相对路径,不需要加项目的虚拟目录,例如请求转发。

ServletContext

ServletContext 对象代表整个 web 应用,这个对象可以用于和服务器(web容器)通信

获取方式

  1. 通过 ServletRequest 获取
    request.getServletContext();
    
  2. 通过 HttpServlet 获取
    this.getServletContext();
    

功能

  1. 获取 MIME 类型
    MIME 类型是在互联网通信过程中定义的一种文件数据类型,一般的格式为 大类型/小类型,如 text/htmlimage/jpeg,Tomcat 的 web.xml 文件定义了全部普通文件后缀名和 MIME 类型的映射关系,如 video/x-msvideo 对应 .avi 文件,image/png 对应 .png 文件。
    String getMimeType(String file);
  1. 共享数据
    ServletContext 对象是一个域对象,其对象范围是所有客户端用户请求的数据,也就是说,它可以在不同访问用户之间共享数据,但是要注意,正因其范围很大,要谨慎使用,如果每个请求都创建新的共享数据,很快内存就会被消耗殆尽。
setAttribute(String name,Object value) // 设置共享数据,以键值对形式
getAttribute(String name) // 通过键获取共享数据(对象)
removeAttribute(String name) // 通过键删除共享数据(对象)
  1. 获取文件资源的真实路径
    我们在开发 web 项目时的工作空间目录,如下所示

工作空间目录

但是要注意,其中的文件路径,并不是编译打包后在服务器上部署的真实的路径,例如,部署的真实项目目录是这样的

真实项目目录

可以看到目标变化是这样的:工作空间下 web 目录下的文件(如index.jsp)和 WEB-INF 目录放在了项目的根目录下,src 下的普通文件会原封不动放到项目 WEB-INF 下的 classes 目录下,而 .java 文件会被编译,放在目 WEB-INF 下的 classes 目录下,并且包结构没有改变,所以,利用 ServletContext 对象获取文件真实路径的方式如下

  • 调用方法

    String getRealPath(String path)
    
  • 获取工作空间 web 目录下的资源 a.txt 的真实路径

    String path = context.getRealPath("/a.txt"); // context 是获取的`ServletContext`实例对象
    
  • 获取工作空间 web/WEB-INF 目录下的资源 b.txt 的真实路径

    String path = context.getRealPath("/WEB-INF/b.txt");
    
  • 获取工作空间 src 目录下的资源 c.txt 的真实路径

    String path = context.getRealPath("/WEB-INF/classes/c.txt");
    

案例:文件下载

需求

  1. 页面显示超链接
  2. 点击超链接后弹出下载提示框
  3. 完成文件的下载

分析

如果页面 html 文件中,<a> 标签的 href 属性直接指向目标资源(资源放在 工作目录/web/img 下),如果资源类型浏览器可以直接解析,例如 .jpg,则点击超链接时,会直接在页面打开该图片,而不是弹出下载提示框,如果是浏览器不能直接解析的文件,例如 .avi,才会弹出下载提示框

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>下载</title>
</head>
<body>
    <a href="./img/pic.jpg">图片1</a>
    <a href="./img/video.avi">视频</a>
</body>
</html>

解决方法

编辑 <a> 标签的 href 属性指向 Servlet,并携带资源名称,Servlet 设置响应头 Content-disposition 告知浏览器打开资源的方式,获取资源名称,以字节输出流的方式读取文件、传输文件

实现

package cool.yzt.web.servlet;

import cool.yzt.web.utils.DownloadUtils;
import javax.servlet.Servlet;
import javax.servlet.ServletContext;
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.File;
import java.io.FileInputStream;
import java.io.IOException;

/**
 * @Author: yzt
 * @Description:
 */
@WebServlet(urlPatterns = {"/downloadFile"})
public class DownloadFile extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        // 获取前端传过来参数:文件名称
        String filename = request.getParameter("filename");

        // 获取 ServletContext 对象
        ServletContext context = this.getServletContext();

        // 获取文件的真实路径
        String realPath = context.getRealPath("/img/"+filename);

        // 根据文件的真实路径,获取文件输入流
        FileInputStream fis = new FileInputStream(new File(realPath));

        // 根据文件名称获取 MIME 类型
        String mimeType = context.getMimeType(filename);

        // 设置响应头 Content-Type
        response.setContentType(mimeType);

        // 根据不同浏览器,解决中文文件名问题
        String agent = request.getHeader("user-agent");
        filename = DownloadUtils.getFileName(agent,filename);

        // 设置响应头 Content-disposition 告知浏览器以什么方式打开该文件,以及文件名称
        response.setHeader("content-disposition","attachment;filename="+filename);

        // 传输数据
        ServletOutputStream sos = response.getOutputStream();
        byte[] buffer = new byte[1024*8];
        int length = 0;
        while((length=fis.read(buffer))!=-1) {
            sos.write(buffer,0,length);
        }
        fis.close();
    }

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

如果不针对中文文件名作特殊处理,则在下载时会出现乱码问题

下载中文名称文件

下面是处理中文文件名的工具类,可以根据不同浏览器设置不同的编码方式

package cool.yzt.web.utils;

import sun.misc.BASE64Encoder;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

/**
 * @Author: yzt
 * @Description:
 */
public class DownloadUtils {
    public static String getFileName(String agent, String filename) throws UnsupportedEncodingException {
        if (agent.contains("MSIE")) {
            // IE浏览器
            filename = URLEncoder.encode(filename, "utf-8");
            filename = filename.replace("+", " ");
        } else if (agent.contains("Firefox")) {
            // 火狐浏览器
            BASE64Encoder base64Encoder = new BASE64Encoder();
            filename = "=?utf-8?B?" + base64Encoder.encode(filename.getBytes("utf-8")) + "?=";
        } else {
            // 其它浏览器
            filename = URLEncoder.encode(filename, "utf-8");
        }
        return filename;
    }
}

参考

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://yzt.cool/archives/http响应和servletresponse对象