详解 HttpServletResponse

    • 核心方法
    • 代码示例
      • 1.设置响应状态码
      • 2.设置响应头
      • 3.设置响应内容
        • (1)响应一个网页(简单HTML)
        • (2)响应一个网页(复杂HTML)
    • 返回已有的一个网页
      • 1.重定向
      • 2.转发
    • 返回一个文件
      • 渲染展示与下载
    • 返回 json 数据
    • 请求响应流程小结

前言

Servlet 中的 doXXX 方法的目的就是根据请求计算得到响应, 然后把响应的数据设置到HttpServletResponse 对象中,
然后 Tomcat 就会把这个 HttpServletResponse 对象按照 HTTP 协议的格式, 转成一个字符串, 并通过Socket 写回给浏览器;

核心方法

方法描述
void setStatus(int sc)设置响应状态码
void setHeader(String name,String value)设置一个带有给定的名称和值的Header,如果name已经存在,则覆盖旧的值
void addHeader(int sc)设置一个带有给定的名称和值的Header,如果name存在,不会覆盖旧的值,并列添加新的值
void setContentType(String type)设置被发送到客户端的响应的内容类型
void setCharacterEncoding(String charset)设置被发送到客户端的响应的字符编码(MIME 字符集)
void sendRedirect(String location)使用指定的重定向位置 URL 发送临时重定向响应到客户端
PrintWriter getWriter()用于往 body 中写入文本格式数据
OutputStream getOutStream()用于往 body 中写入二进制格式数据

需要注意的是:

  • 响应对象是服务器要返回给浏览器的内容, 这里的重要信息都是程序猿设置的, 因此上面的方法都是 "写" 方法
  • 对于状态码/响应头的设置要放到 getWriter /getOutputStream 之前, 否则可能设置失效;

代码示例

1.设置响应状态码

前端代码

<html lang="en"><head>    <meta charset="UTF-8">    <meta http-equiv="X-UA-Compatible" content="IE=edge">    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <title>Document</title></head><body>    <h3>设置状态码</h3>    <input type="text" id="status">    <br>    <button onclick="setStatus()">提交</button></body><script>    function setStatus(){        //js中发请求:(1)ajax (2)直接修改地址栏URL        let status = document.querySelector("#status");        //后端将文本框输入的值作为响应状态码        window.location.href= "response?status="+status.value;    }</script></html>

后端代码

package 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("/response")public class ResponseStudyServlet extends HttpServlet {    @Override    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {       //获取请求发送的queryString数据:status=xxx        String status = req.getParameter("status");        resp.setStatus(Integer.parseInt(status));        resp.getWriter().write("响应状态码设置成功");    }}

启动Tomcat 后,页面如下所示:

404 状态码进行提交后,fiddler抓包工具可查看到如下信息:

2.设置响应头

修改前端代码

<html lang="en"><head>    <meta charset="UTF-8">    <meta http-equiv="X-UA-Compatible" content="IE=edge">    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <title>Document</title></head><body>    <h3>设置状态码</h3>    <input type="text" id="status">    <br>    <button onclick="setStatus()">提交</button>    <h3>设置响应头</h3>    <a href="response">设置</a></body><script>    function setStatus(){        //js中发请求:(1)ajax (2)直接修改地址栏URL        let status = document.querySelector("#status");        //后端将文本框输入的值作为响应状态码        window.location.href= "response?status="+status.value;    }</script></html>

修改后端代码

package 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("/response")public class ResponseStudyServlet extends HttpServlet {    @Override    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {        //获取请求发送的queryString数据:status=xxx        String status = req.getParameter("status");        //当请求数据中包含需要设置状态码,才会执行        if (status != null) {            resp.setStatus(Integer.parseInt(status));            resp.getWriter().write("响应状态码设置成功");        }        //设置响应头的键值对,键可以是标准的HTTP响应头的键,也可以是自定义的        //响应状态码是 301,302,307,响应头有location字段,才是重定向        resp.setHeader("location","http://www.baidu.com" );        resp.setHeader("username","晓茹");    }}

重新启动Tomcat ,刷新页面:

fiddler 抓包结果:

会发现设置了location字段,但并没有跳转,发现响应状态码为200(原因:只有3xx的状态码才会重定向);

注意:

  • 若响应头name键已有,就会覆盖原有的键值对;
  • addHeader ,当name键已存在时,不会覆盖,会添加一个新的;

3.设置响应内容

(1)响应一个网页(简单HTML)

前端代码

<html lang="en"><head>    <meta charset="UTF-8">    <meta http-equiv="X-UA-Compatible" content="IE=edge">    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <title>Document</title></head><body>    <h3>响应正文为简单的html网页</h3>    <a href="html" />

(2)响应一个网页(复杂HTML)

前端代码

 <body> <h3>响应正文为复杂的html(动态变化的)</h3>    <input type="text" id="username" placeholder="输入姓名">    <br>    <button onclick="toWelcome()">跳转</button></body><script>    function toWelcome(){        let username = document.querySelector("#username");        window.location.href = "html?type=2&username=" +username.value;    }</script>

后端代码

package 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;import java.io.PrintWriter;@WebServlet("/html")public class HtmlTypeServlet extends HttpServlet {    @Override    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {        //响应html:设置响应的content-type        resp.setContentType("text/html;charset=utf-8");        PrintWriter pw = resp.getWriter();        //获取queryString中,type的值        String type = req.getParameter("type");        if("1".equals(type)){            //返回简单的html            pw.println("

获取网页成功

"
); }else if("2".equals(type)){ //返回复杂的html //html?type=2&username=xxx String username = req.getParameter("username"); pw.println("

"); pw.println("欢迎你:"+username); pw.println("

"
); } }}

启动Tomcat ,刷新页面:

输入Java小菜鸟,点击跳转:

当输入另一个姓名(张三)时:

思考:如上Java代码中,写入了许多HTML代码,这样开发好嘛?

答案是肯定不好的,缺点:耦合性太强(两个完全不同的编程语言在一起开发),导致维护性和可扩展性变差!
解决方案:(1)模板技术(也存在一些问题) (2)进一步产生ajax技术;

返回已有的一个网页

1.重定向

前端代码

<body>   <h3>重定向到request.html</h3>   <a href="goto" />
小结

特点URL地址栏会发生变化,会发起两次请求:
原理
第一次返回301、302、307响应状态码及响应头location:网页的地址;
第二次:浏览器自动跳转到location设置的地址;
可以用在登陆成功后跳转到某个页面!

2.转发

前端代码

<body>    <h3>转发到request.html</h3>    <a href="goto?type=2">跳转</a> </body>  

修改后的后端代码

package 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("/goto")public class GoToServlet extends HttpServlet {    @Override    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {        //重定向到request.html        //goto?type=xxx        String type = req.getParameter("type");        if("1".equals(type)){            //重定向            //设置响应状态码:301            //设置location字段            resp.setStatus(301);            resp.setHeader("Location","request.html");            //以上代码可简化为:           // resp.sendRedirect("request.html");        }else if("2".equals(type)){            //转发            req.getRequestDispatcher("request.html")                    .forward(req,resp);        }    }}

启动Tomcat,刷新页面:

会发现URL地址栏并没有发生变化!!!

小结

特点URL地址栏不会发生变化,只有一次请求;
原理:当次请求servlet时,由servlet获取到转发路径的资源,并把这个路径的内容设置到响应正文;

返回一个文件

需要设置一下 content-Typecontent-Length ,然后将文件的二进制数据放在响应正文即可;

渲染展示与下载

示例:图片与音乐

前端代码

<body>   <h3>获取一个图片(渲染展示)</h3>    <img src="file?type=photo&show=1">    <h3>获取一个音乐(渲染展示)</h3>    <audio src="file?type=music&show=1" controls></audio>    <h3>获取一个图片(下载)</h3>    <a href="file?type=photo&show=0">下载</a>    <h3>获取一个音乐(下载)</h3>    <a href="file?type=music&show=0">下载</a> </body>

后端代码

package 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.File;import java.io.IOException;import java.io.OutputStream;import java.nio.file.Files;@WebServlet("/file")public class FileServlet extends HttpServlet {    @Override    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {        // 获取响应对象的字节输出流        OutputStream os = resp.getOutputStream();        // 返回的文件类型:1.图片        String type = req.getParameter("type");        // 返回时的操作:1.渲染 2.下载        String show = req.getParameter("show");        File file = null;        //         }else if("music".equals(type)){//返回音乐            if("1".equals(show)) {                resp.setContentType("audio/mp3");//mp3格式            }else {                resp.setContentType("application/octet-stream");            }            file = new File("D:\\servlet-study\\src\\main\\resources\\ 晴天.mp3");        }        //返回一个文件类型:Content-Length,body        byte[] data = Files.readAllBytes(file.toPath());        resp.setContentLength(data.length);//=setHeader("Content-Length", xxx)        os.write(data);    }}

启动Tomcat ,刷新页面,渲染方式如下:

下载方式如下:


打开文件,修改文件后缀名即可查看图片;

http 菜鸟教程:查看 content-type 对应格式

思考:图片、音乐、视频等都是静态文件,直接放在webapp 下,就可以直接访问,还需要servlet来返回嘛?


如果文件总的大小非常大,放在web应用的webapp下就不合适(打包比较费劲),但通过servlet去读取本地其他地方的文件来返回就比较合适;

返回 json 数据

常用于ajax请求,返回一些数据,用于动态的填充网页;

前端代码

 <body> <h3>获取ajax响应数据,动态生成网页内容</h3>    <button onclick="gen()">试试呗</button>    <div id="content"></div></body><script>    function gen(){        let content = document.querySelector("#content");        ajax({            url: "ajax-response",            method: "get",            callback: function(status, resp){                console.log(resp);//resp是一个字符串                //转换为json对象                let array = JSON.parse(resp);                for(json of array){//遍历                    //每一个json对象,创建一个dom来保存信息                    let p = document.createElement("p");                    p.innerHTML = json.from+" 对 "+json.to+" 说:"+json.info;                    content.appendChild(p);                }            }        });    }    function ajax(args){//var ajax = function(){}        let xhr = new XMLHttpRequest();        // 设置回调函数        xhr.onreadystatechange = function(){            // 4: 客户端接收到响应后回调            if(xhr.readyState == 4){                // 回调函数可能需要使用响应的内容,作为传入参数                args.callback(xhr.status, xhr.responseText);            }        }        xhr.open(args.method, args.url);        //如果args中,contentType属性有内容,就设置Content-Type请求头        if(args.contentType){//js中,if判断,除了判断boolean值,还可以判断字符串,对象等,有值就为true            xhr.setRequestHeader("Content-Type", args.contentType);        }        //如果args中,设置了body请求正文,调用send(body)        if(args.body){            xhr.send(args.body);        }else{//如果没有设置,调用send()            xhr.send();        }    }</script>

后端代码

package response;import com.fasterxml.jackson.databind.ObjectMapper;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.ArrayList;import java.util.List;@WebServlet("/ajax-response")public class AjaxJsonServlet extends HttpServlet {    @Override    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {        List<Message> messages = new ArrayList<>();        Message m1 = new Message("花花", "小菜鸟", "你一定可以顺利毕业");        Message m2 = new Message("小菜鸟", "花花", "我肯定行!!!");        messages.add(m1);        messages.add(m2);        ObjectMapper mapper = new ObjectMapper();        //把Java对象,转换为一个json字符串。list和数组会转换为[],一个对象{成员变量名: 值}        String json = mapper.writeValueAsString(messages);        System.out.println("转换的json字符串:"+json);        //设置json可以不设置Content-Length,tomcat会设置        resp.setContentType("application/json; charset=utf-8");        resp.getWriter().println(json);    }    static class Message{        private String from;//谁        private String to;//对谁        private String info;//说了什么        //构造方法        public Message(String from, String to, String info) {            this.from = from;            this.to = to;            this.info = info;        }        //getter与setter        public String getFrom() {            return from;        }        public void setFrom(String from) {            this.from = from;        }        public String getTo() {            return to;        }        public void setTo(String to) {            this.to = to;        }        public String getInfo() {            return info;        }        public void setInfo(String info) {            this.info = info;        }    }}

启动Tomcat,打开网页,查看:

点击试试呗按钮,网页显示:

后端输出:

请求响应流程小结

简单来说就是:

  1. 客户端浏览器发起HTTP请求

客户端浏览器发起请求的方式:
(1)queryString
(2)表单:
(3)form-data
(4)json

如下表所示

  1. 服务端进行处理

服务端处理步骤
(1)通过 HttpServletRequest 先获取到请求数据;

获取方式queryString表单form-datajson
getParameter可以获取简单类型
getPart√(上传的文件)
getInputStream(可以获取任意格式的请求正文的数据)

注意json字符串中的键需要和自定义类型中的成员变量名一致(一般为自定义格式)!

(2)根据请求数据进行校验,业务逻辑处理(如:数据库操作或者根据请求数据的某个字段,执行不同的逻辑,如上面的type=1,返回简单的htmltype=2,返回动态变化的 html);
(3)返回响应的内容;内容有

  • 网页:一般使用模板技术,而不是在servlet拼接动态的html
  • 文件:根据需要;
  • json:前端发送的是ajax请求;

3.返回HTTP响应给客户端

客户端接收并处理服务端HTTP响应

三种方式

(1)网页:渲染方式(浏览器自动完成);
(2)文件:下载或渲染(浏览器自动完成)
(3)jsonJavaScriptajax 代码完成;