详解 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-Type
和 content-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
,打开网页,查看:
点击试试呗按钮,网页显示:
后端输出:
请求响应流程小结
简单来说就是:
- 客户端浏览器发起
HTTP
请求
客户端浏览器发起请求的方式:
(1)queryString
:
(2)表单:
(3)form-data
:
(4)json
:
如下表所示:
- 服务端进行处理
服务端处理步骤:
(1)通过 HttpServletRequest
先获取到请求数据;
获取方式 queryString 表单 form-data json getParameter √ √ 可以获取简单类型 getPart √(上传的文件) getInputStream(可以获取任意格式的请求正文的数据) √
注意:json
字符串中的键需要和自定义类型中的成员变量名一致(一般为自定义格式)!
(2)根据请求数据进行校验,业务逻辑处理(如:数据库操作或者根据请求数据的某个字段,执行不同的逻辑,如上面的type=1
,返回简单的html
,type=2
,返回动态变化的 html
);
(3)返回响应的内容;内容有:
- 网页:一般使用模板技术,而不是在
servlet
拼接动态的html
; - 文件:根据需要;
- json:前端发送的是
ajax
请求;
3.返回HTTP响应给客户端
客户端接收并处理服务端HTTP
响应
三种方式
:
(1)网页:渲染方式(浏览器自动完成);
(2)文件:下载或渲染(浏览器自动完成)
(3)json:JavaScript
写 ajax
代码完成;