Skip to content

Response

案例目标

  • 制作图片下载 Servlet
  • 统计某 Servlet 的访问次数
  • 验证码生成

HTTP 响应

HTTP-Composition

组成

Response-Content

  • 响应行

    tex
    HTTP/1.1 200 OK
  • 响应头

    tex
    Server: Apache/2.4.18(Ubuntu)
    Content-Encoding: gzip
    Content-Length: 4028
    Connection: Keep-Alive
    Content-Type: text/html
  • 响应体

    html
    <!DOCTYPE html SYSTEM "about:legacy-compat">
    <html lang="en">
    <head>
    <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="res/css/tomcat.css" rel="stylesheet" type="text/css">
    <link href="res/css/fonts/fonts.css" rel="stylesheet" type="text/css">
    <title>Apache Tomcat&reg; - Welcome!</title>
    <meta name="author" content="Apache Tomcat Project">
    <meta name="google-site-verification" content="nA9F0GvGNjVCU9W7HBziONQVx6FehvgQglI1X7WPfRw">
    </head>
    ...

响应行

tex
HTTP/1.1 200 OK
  • 协议/版本
  • 状态码及状态信息

常见的状态码

状态码状态信息说明
200OK请求已成功,请求所希望的响应头或数据体将随此响应返回。出现此状态码是表示正常状态
302Move temporarily重定向,请求的资源临时从不同的 URI 响应请求
403Forbidden服务器已经理解请求,但是拒绝执行它
404Not Found请求资源不存在。通常是用户路径编写错误,也可能是服务器资源已删除
405Method Not Allowed请求行中指定的请求方法不能被用于请求相应的资源
500Internal Server Error服务器内部错误,通常程序抛异常

响应头

tex
Server: Apache/2.4.18(Ubuntu)

Content-Encoding: gzip

Content-Length: 4028

Connection: Keep-Alive

Content-Type: text/html

常见的响应头

状态头说明
Server描述服务器信息,包含Web容器信息、服务器系统信息
Content-Length描述响应体的长度(字符数)
Content-Type请求体的编码格式,以及超文本类型。常见的有text/html, application/xml,application/json
Location指定响应的路径,需要与状态码302配合使用,完成跳转Location: http://localhost:8080/another..
Refresh3;url=www.dubai.com。
Content-Dispositionattachment; filename=1.gif 常用于文件下载,attachment指定,被访问的资源是一个下载(attachment)的资源,下载所使用的文件名为(1.gif)

响应体

html
<!DOCTYPE html SYSTEM "about:legacy-compat">
<html lang="en">
<head>
<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="res/css/tomcat.css" rel="stylesheet" type="text/css">
<link href="res/css/fonts/fonts.css" rel="stylesheet" type="text/css">
<title>Apache Tomcat&reg; - Welcome!</title>
<meta name="author" content="Apache Tomcat Project">
<meta name="google-site-verification" content="nA9F0GvGNjVCU9W7HBziONQVx6FehvgQglI1X7WPfRw">
</head>
...
tex
服务器发送给浏览器的内容。可以是字符流,也可以是字节流。

Chrome查看响应信息

Chrome-Tool

小结

  • HTTP 由哪两部分组成

  • 响应由哪三部分组成

    • 200 OK

      404 Not Found

      500 Internal Server Error

    • Location: 重定时,浏览器要跳转的地址

      Refresh: 刷新当前页面

      Content-Disposition: 告诉浏览器以附件形式下载,并且告诉浏览器,自动下载时的文件名

      • 字符流
      • 字节流

Response对象

HTTP 响应:包括行头体,服务端发给浏览器。请求内容放在 request 对象。服务器发给浏览器的信息放在 response 对象。

响应行相关API

  • void setStatus(int statusCode)

    tex
    设置响应行-状态码,Tomcat不会协助添加错误信息
  • void sendError(int errorCode, String msg)

    tex
    设置状态码(errorCode),并且Tomcat会协助添加错误信息(msg)

案例

  • 通过设置状态码,观察浏览器信息
java
package com.futureweaver.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("/response_line")
public class ResponseLineServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 设置状态码(没有信息)
        response.setStatus(500);
      
        // 设置状态码(有信息,并且多一个"messgae")
        response.sendError(500, "你吃了吗");
    }

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

响应头相关API

  • void setHeader(String name, String value)

    tex
    在响应头中,设置一个Header。Header所对应的名字(name),以及Header所对应的值(value)
  • void setContentTypeString type

    tex
    在响应头中,设置一个Header。Header所对应的名字为Content-Type,Header所对应的值为(value)

案例

  • 过 3 秒跳转到另一个网站
java
package com.futureweaver.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;

// 过3秒跳到百度
@WebServlet("/refresh_sample")
public class RefreshSampleServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 过3秒跳到百度
        response.setHeader("Refresh", "3;url=https://www.baidu.com");
    }

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

案例

  • 利用 302 状态码,以及 Location 响应头,完成浏览器端页面跳转
    • 浏览器两次请求
    • 在浏览器完成的跳转
    • 浏览器地址会变
java
package com.futureweaver.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("/redirect_sample")
public class RedirectSampleServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
      
        // 手动重定向
        // 设置状态码为302,Location为跳转的地址
        response.setStatus(302);
        response.setHeader("Location", request.getContextPath() + "/hello");
    }

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

Redirect-Chrome

浏览器跳转技术 - 重定向

  • void sendRedirect(String location)

案例

  • 利用 sendRedirect,完成浏览器端页面跳转
java
package com.futureweaver.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("/redirect_sample")
public class RedirectSampleServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//        response.setStatus(302);
//        response.setHeader("Location", request.getContextPath() + "/hello");
      
        // 不需要手动设置302以及Location了
        response.sendRedirect(request.getContextPath() + "/hello");
    }

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

重定向与转发的区别【重点】

  • request.getRequestDispatcher("/资源地址").forward(request, response)
  • response.sendRedirect("前端地址")

转发时,地址要有斜线;重定向时,地址没斜线

转发

  • 一次请求
  • 地址不变
  • 服务器内部跳转

重定向

  • 两次请求

  • 地址会变

  • 浏览器跳转

  • 转发

    java
    request.getRequestDispatcher("/bservlet").forward(request, response);

Forward

  • 重定向

    java
    response.sendRedirect(request.getContextPath() + "/bservlet");

Redirect

转发重定向
地址不变地址改变
服务器内部跳转浏览器完成跳转
一次请求二次请求

响应体相关API【重点】

  • PrintWriter getWriter()

    获取字符输出流(输出到浏览器)

  • OutputStream getOutputStream()

    获取字节输出流(输出到浏览器,一般下载时或展示的是一个纯图片时使用)

案例

  • 利用 getWriter,编写动态资源
java
package com.futureweaver.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("/hello")
public class HelloServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 获取字符输出流
        PrintWriter writer = response.getWriter();
      
        // 向字符输出流中,输出HTML内容
        writer.println("<HTML>");
        writer.println("<HEAD>");
        writer.println("</HEAD>");
        writer.println("<BODY>");
        writer.println("<H1>Hello Servlet!</H1>");
        writer.println("</BODY>");
        writer.println("</HTML>");
    }

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

案例

  • 利用 getWriter,编写中文动态资源
java
package com.futureweaver.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("/hello")
public class HelloServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 获取字符输出流
        PrintWriter writer = response.getWriter();

        // 可以选择write/print/println
        // 向字符输出流中,输出HTML内容
        writer.println("<HTML>");
        writer.println("<HEAD>");
        writer.println("</HEAD>");
        writer.println("<BODY>");
        writer.println("<H1>你好,Servlet!</H1>");
        writer.println("</BODY>");
        writer.println("</HTML>");
    }

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

响应体乱码解决

  • response.setCharacterEncoding("字符集")

    tex
    响应体使用指定的字符集传输
  • response.setContentType("text/html; charset=utf-8")

    tex
    setCharacterEncoding用于设置传输字符集,但是浏览器仍然不清楚页面所采用的字符编码。可以在Content-Type头给出。
    
    如果在Content-Type头部指定了字符编码,则setCharacterEncoding可以省略。
java
package com.futureweaver.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("/hello")
public class HelloServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 在未设置字符编码之前,打印默认的字符编码
        System.out.println(response.getCharacterEncoding());

        // 服务器需要告诉浏览器我发送的响应体是什么字符编码
        // 如果同时设置了Content-Type的编码,和response的characterENcoding的话,只需要写一个Content-Type即可
//        response.setCharacterEncoding("utf-8");
//        response.setHeader("Content-Type", "text/html; charset=utf-8");
        response.setContentType("text/html; charset=utf-8");

        PrintWriter writer = response.getWriter();
        // 可以选择write/print/println
        writer.println("<HTML>");
        writer.println("<HEAD>");
        writer.println("</HEAD>");
        writer.println("<BODY>");
      
        // 输出中文
        writer.println("<H1>你好, Servlet!</H1>");
        writer.println("</BODY>");
        writer.println("</HTML>");
    }

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

案例

  • 利用 getOutputStream,制作图片下载 Servlet
java
package com.futureweaver.web;

import org.apache.commons.io.IOUtils;

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.*;

@WebServlet("/download_sample")
public class DownloadServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 告诉浏览器,这是一个附件,要下载,下载的文件名是download.gif
        response.setHeader("Content-Disposition", "attachment; filename=download.gif");

        // 获取浏览器字节输出流以及文件输入流
        OutputStream out = response.getOutputStream();
        InputStream in = new FileInputStream("/Users/LEAF/download.gif");

        // 从输出流读取,写入到输出流
        byte[] buf = new byte[1024];
        int len = 0;
        while ((len = in.read(buf)) != -1) {
            out.write(buf, 0, len);
        }

        // 关闭流对象
        in.close();
        out.close();
    }

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

【扩展】下载文件名为中文,乱码解决

因为在文件下载的过程中,服务器需要通过响应头部的Content-Disposition,告诉浏览器下载的文件名。无论是请求,还是响应,行和头中的内容是不允许出现中文的。但是在下载的过程中,我们需要依赖响应头,设置一个中文的下载文件,那么就需要借助于 URLEncoder 来实现。

在百度中,输入中文"点墨星河",浏览器中能够看到地址栏中的中文,但是复制出来之后,发现浏览器中出现的每一个中文,都是以 3 个百分号为一组的一种编码形式,这种形式普遍在浏览器当中的 URL 出现。所以我们要使用的工具类,就是 URLEncoder

URLEncoder 有一个静态方法: encode(String character, String enc)

第一个参数是要编码的字符串

第二个参数是编码时采用的编码集

  • ByteStreamServlet.java
java
package com.futureweaver.web;

import org.apache.commons.io.IOUtils;

import javax.servlet.ServletContext;
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.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.InputStream;
import java.net.URLEncoder;

// 字节流
@WebServlet("/byte_stream")
public class ByteStreamServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 中文使用URLEncoder.encode进行编码
        response.setHeader("Content-Disposition", "attachment; filename="+ URLEncoder.encode("下载", "utf-8")+".gif");

        // 使用字节输出流,制作图片下载
        OutputStream out = response.getOutputStream();

        // 调用ServletContext对象的getRealPath方法
        ServletContext context = getServletContext();

        // 获取项目当中的WEB-INF下面的文件的真实路径
        InputStream in = context.getResourceAsStream("/WEB-INF/download.gif");

        IOUtils.copy(in, out);

        out.close();
        in.close();
    }

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

响应体压缩

流程

  1. 构造方法

gzip = GZIPOutputStream(OutputStream out) GZIPOutputStream会将写入到本对象当中的字符压缩,并且将压缩之后的内容,填入到out当中

  1. 向 GZIPOutputStream 填入数据

gzip.write(Byte[] byte)

  1. GZIPOutputStream 写入完成,并且压缩,把压缩好的内容填入到 out 当中

gzip.finish()

代码

java
package com.futureweaver.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.IOException;
import java.io.OutputStream;
import java.util.zip.GZIPOutputStream;

@WebServlet("/content_encoding")
public class ContentEncodingServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setHeader("Content-Encoding", "gzip");

        // 获取字节流
        OutputStream out = response.getOutputStream();

        // Content-Encoding: gzip
        GZIPOutputStream gzip = new GZIPOutputStream(out);

        for (int idx = 0; idx < 1000; ++idx) {
            gzip.write("abcd".getBytes());
        }
        gzip.finish();

        out.close();
    }

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

IOUtils 工具包

  • commons-io-2.1.jar

相关API

  • static int copy(InputStream input, OutputStream output)

    tex
    从输入流当中,读取数据,输出到输出流当中
    
    返回值:
    the number of bytes copied, or -1 if > Integer.MAX_VALUE
    被拷贝了多少个字节,如果发生错误的是,返回是-1
    如果超过了2GB的话,返回值是Inter.MAX_VALUE

案例

  • 利用 IOUtils,改进图片下载 Servlet

实现

java
package com.futureweaver.web;

import org.apache.commons.io.IOUtils;

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.*;

@WebServlet("/download_sample")
public class DownloadServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 告诉浏览器,这是一个附件,要下载,下载的文件名是download.gif
        response.setHeader("Content-Disposition", "attachment; filename=download.gif");

        // 获取浏览器字节输出流以及文件输入流
        OutputStream out = response.getOutputStream();
        InputStream in = new FileInputStream("/Users/LEAF/download.gif");

        // IOUtils读取、写入
        IOUtils.copy(in, out);

        // 关闭流对象
        in.close();
        out.close();
    }

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

小结

  • 响应行

  • 响应头

    response.setContentType("text/html; charset=utf-8") response.setHeader(name, value)

  • 响应体

    字符流 Writer out = response.getWriter()

    字节流 OutputStream out = response.getOutputStream()

  • 乱码

    响应体乱码的解决 response.setContentType("text/html; charset=utf-8")

    头部当中,如果出现了中文的话 URLEncoder.encode("中文", "utf-8")

  • IOUtils

    commons-io.jar IOUtils.copy(in, out);

  • 难点

    • 转发
      • 一次请求
      • 地址不变
      • 服务内部跳转
    • 重定向
      • 两次请求
      • 地址会变
      • 浏览器跳转
    • 转发与重定向填写的地址区别
      • 转发填写的地址,要有斜线
      • 重定向填写的地址,不要有斜线

学习目标总结

  • 能够理解 HTTP 协议的响应部分
  • 能够应用 Response 对象的方法
  • 能够应用 Response 对象处理中文乱码
  • 能够应用转发和重定向的方法
  • 能够应用 Response 对象完成文件下载代码