Servlet响应【httpServletResponse】

x33g5p2x  于2022-03-09 转载在 其他  
字(12.1k)|赞(0)|评价(0)|浏览(601)

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

核心方法汇总

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

注意:

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

代码示例

1.setStatus 设置状态码

前端代码:

  1. <body>
  2. <h3>设置状态码</h3>
  3. <input type="text" id="status">
  4. </br>
  5. <button onclick="setStatus()">提交</button>
  6. </body>
  7. <script>
  8. function setStatus() {
  9. // js中发请求: 1)ajax; 2)直接修改url
  10. let status = document.querySelector("#status");
  11. // 后端会设置这个文本框输入的值为响应状态码: 严格来说 需要验证,此处省略
  12. window.location.href = "response?status=" + status.value;
  13. }
  14. </script>

后端代码:

  1. @WebServlet("/response")
  2. public class ResponseServletStudy extends HttpServlet {
  3. @Override
  4. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  5. // 获取请求发送的 queryString数据: status=xxx
  6. String status = req.getParameter("status");
  7. resp.setStatus(Integer.parseInt(status));
  8. resp.getWriter().write("设置响应状态码成功");
  9. }
  10. }

启动之后,打开页面:
(提交之后出现乱码,暂时不管)

使用 fiddler 抓包:

变换不同的 status 的值,就可以看到不同的响应结果

注意: 如果没有调用这个方法,默认返回200状态码(若正常执行,则没有异常;若出现异常,就返回500)

2.setHeader 设置响应头 [了解]

修改前端代码:

  1. <body>
  2. <h3>设置状态码</h3>
  3. <input type="text" id="status">
  4. </br>
  5. <button onclick="setStatus()">提交</button>
  6. <h3>设置响应头</h3>
  7. <a href="response">设置</a>
  8. </body>
  9. <script>
  10. function setStatus() {
  11. // js中发请求: 1)ajax; 2)直接修改url
  12. let status = document.querySelector("#status");
  13. // 后端会设置这个文本框输入的值为响应状态码: 严格来说 需要验证,此处省略
  14. window.location.href = "response?status=" + status.value;
  15. }
  16. </script>

修改后端代码:

  1. @WebServlet("/response")
  2. public class ResponseServletStudy extends HttpServlet {
  3. @Override
  4. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  5. // 获取请求发送的 queryString数据: status=xxx
  6. String status = req.getParameter("status");
  7. // 若请求数据包含需要设置的状态码,才执行
  8. if(status != null) {
  9. resp.setStatus(Integer.parseInt(status));
  10. resp.getWriter().write("设置响应状态码成功");
  11. }
  12. // 设置响应头的键值对,键可以是标准的Http响应头的键,也可以是自定义的
  13. // 响应状态码是301,302,307,响应头有Location字段,才是重定向
  14. resp.setHeader("Location","http://www.baidu.com");
  15. resp.setHeader("username","花花");
  16. }
  17. }

重新启动,刷新页面:

点击后,发现我们设置了 location,但并没有跳转,使用 fiddler 抓包:
发现状态码是200

注意: 若响应头 name 键已有,就会覆盖原有的键值对

3.addHeader 设置响应头 [了解]

和 setHeader 唯一不同的是:name键已有,不会影响添加一个新的

4.setContentType

设置响应头 Content-Type 的值,等同于 setHeader(“Content-Type”, String type)
因为 Content-Type 是标识 body 的数据格式,所以调用该方法还需要设置 body 的内容

  1. 返回简单的一个网页

修改前端代码:

  1. <body>
  2. <h3>响应正文为简单的html网页</h3>
  3. <a href="html?type=1">查看</a>
  4. </body>

后端代码:

  1. @WebServlet("/html")
  2. public class HtmlTypeServlet extends HttpServlet {
  3. // html?type=1...
  4. @Override
  5. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  6. // 响应html: 设置响应的Content-Type
  7. resp.setContentType("text/html;charset=utf-8");
  8. PrintWriter pw = resp.getWriter();
  9. // 获取queryString中type的值
  10. String type = req.getParameter("type");
  11. // 返回简单的 html
  12. if("1".equals(type)) {
  13. pw.println("<h3>获取网页成功</h3>");
  14. }
  15. }
  16. }

重新启动,刷新页面,点击跳转:

  1. 返回一个动态网页(复杂的html)

前端代码:

  1. <body>
  2. <h3>响应正文为复杂的html(动态变化的)</h3>
  3. <input type="text" id="username" placeholder="输入用户名">
  4. </br>
  5. <button onclick="toWelcome()">跳转</button>
  6. </body>
  7. <script>
  8. function toWelcome() {
  9. let username = document.querySelector("#username");
  10. window.location.href = "html?type=2&username=" + username.value;
  11. }
  12. </script>

后端代码:

  1. @WebServlet("/html")
  2. public class HtmlTypeServlet extends HttpServlet {
  3. // html?type=1...
  4. @Override
  5. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  6. // 响应html: 设置响应的Content-Type
  7. resp.setContentType("text/html;charset=utf-8");
  8. PrintWriter pw = resp.getWriter();
  9. // 获取queryString中type的值
  10. String type = req.getParameter("type");
  11. // 返回简单的 html
  12. if("1".equals(type)) {
  13. pw.println("<h3>获取网页成功</h3>");
  14. }
  15. // 返回复杂的动态html
  16. else if("2".equals(type)) {
  17. // html>type=2&username=
  18. String username = req.getParameter("username");
  19. pw.println("<p>");
  20. pw.println("欢迎你!!" + username);
  21. pw.println("</p>");
  22. }
  23. }
  24. }

重新启动,刷新页面:

输入别的用户名尝试:

关于动态网页,在 Java 代码中,写很多的 html 代码,耦合性太强(两个完全不同的编程语言,放在一起开发),维护性、扩展性很差
解决方法: ①模板技术 (也存在一些问题,进一步发展就有了 ②ajax 技术的产生) (模板技术后边继续讲解)

返回已有的一个网页

1.重定向

前端代码:

  1. <body>
  2. <h3>重定向到hello.html</h3>
  3. <a href="goto?type=1">跳转</a>
  4. <h3>转发到hello.html</h3>
  5. <a href="goto?type=2">跳转</a>
  6. </body>

后端代码:

  1. @WebServlet("/goto")
  2. public class GoToServlet extends HttpServlet {
  3. @Override
  4. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  5. // goto?type=xxx
  6. String type = req.getParameter("type");
  7. // 重定向
  8. if("1".equals(type)) {
  9. resp.setStatus(301);
  10. resp.setHeader("Location","hello.html");
  11. // 以上代码可以简化为 sendRedirect
  12. // resp.sendRedirect("hello.html");
  13. }
  14. // 转发
  15. else if("2".equals(type)) {
  16. req.getRequestDispatcher("hello.html").forward(req,resp);
  17. }
  18. }
  19. }

重新启动服务,刷新页面:

点击重定向的跳转,使用 fiddler 抓包:

我们可以看到 url 地址栏的改变:

特点: ①url 地址栏会变;②发送两次请求
原理:
第一次返回 301 / 302 / 307 响应状态码,以及响应头 Location: 网页的地址;
第二次浏览器自动跳转到 Location 设置的地址

2.转发

前后端代码同上

点击转发的跳转,使用 fiddler 抓包:

观察 url 地址栏:

**特点:**①url 地址栏不变;②只有一次请求
原理: 当此请求 Servlet 时,由 Servlet 获取到转发路径的资源,把这个路径的内容设置到响应正文

返回一个文件

设置一下 Content-Type 和 Content-Length,然后把文件的二进制数据,放在响应正文即可

例1.渲染展示

以图片和音乐文件为例

前端代码:

  1. <body>
  2. <h3>获取一个图片(渲染展示)</h3>
  3. <img src="file?type=photo&show=1">
  4. <h3>获取一个音乐(渲染展示)</h3>
  5. <audio src="file?type=music&show=1" controls></audio>
  6. </body>

后端代码:

  1. @WebServlet("/file")
  2. public class FileServlet extends HttpServlet {
  3. @Override
  4. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  5. // 获取响应对象的字节输出流
  6. OutputStream os = resp.getOutputStream();
  7. // 返回的文件类型: photo-图片 music-音乐
  8. String type = req.getParameter("type");
  9. // 返回时的操作: 1-渲染; 2-下载
  10. String show = req.getParameter("show");
  11. File file = null;
  12. // <img src="file?type=photo&show=1">
  13. if("photo".equals(type)) {
  14. resp.setContentType("image/jpeg"); //jpg格式
  15. file = new File("保存在 resources 里的完整路径");
  16. }
  17. // <audio src="file?type=music&show=1" controls></audio>
  18. else if("music".equals(type)) {
  19. resp.setContentType("audio/mp3"); // MP3格式
  20. file = new File("保存在 resources 里的完整路径,如: D:\\xxx\\xxx\\Servlet-Study\\src\\main\\resources\\love.mp3");
  21. }
  22. // 返回一个文件类型: Content-Length, body
  23. byte[] data = Files.readAllBytes(file.toPath());
  24. resp.setContentLength(data.length); // 等同于 setHeader("Content-Length",xxx)
  25. os.write(data);
  26. }
  27. }

可在 菜鸟教程 里查看 content-type 对照表:

启动服务,打开页面:
(此处 gif 格式,听不到声音)

例2.下载

修改前端代码:

  1. <body>
  2. <h3>获取一个图片(下载)</h3>
  3. <a href="file?type=photo&show=2">下载</a>
  4. <h3>获取一个音乐(下载)</h3>
  5. <a href="file?type=photo&show=2">下载</a>
  6. </body>

修改后端代码:

  1. @WebServlet("/file")
  2. public class FileServlet extends HttpServlet {
  3. @Override
  4. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  5. // 获取响应对象的字节输出流
  6. OutputStream os = resp.getOutputStream();
  7. // 返回的文件类型: photo-图片 music-音乐
  8. String type = req.getParameter("type");
  9. // 返回时的操作: 1-渲染; 2-下载
  10. String show = req.getParameter("show");
  11. File file = null;
  12. // <img src="file?type=photo&show=1">
  13. if("photo".equals(type)) {
  14. if("1".equals(show)) {
  15. resp.setContentType("image/jpeg"); //jpg格式
  16. }
  17. else if("2".equals(show)) {
  18. resp.setContentType("application/octet-stream");
  19. }
  20. file = new File("D:\\Bite\\JAVA\\JavaCode\\Servlet-Study\\src\\main\\resources\\doge.jpg");
  21. }
  22. // <audio src="file?type=music&show=1" controls></audio>
  23. else if("music".equals(type)) {
  24. if("1".equals(show)) {
  25. resp.setContentType("audio/mp3"); // MP3格式
  26. }
  27. else if("2".equals(show)) {
  28. resp.setContentType("application/octet-stream");
  29. }
  30. file = new File("D:\\Bite\\JAVA\\JavaCode\\Servlet-Study\\src\\main\\resources\\love.mp3");
  31. }
  32. // 返回一个文件类型: Content-Length, body
  33. byte[] data = Files.readAllBytes(file.toPath());
  34. resp.setContentLength(data.length); // 等同于 setHeader("Content-Length",xxx)
  35. os.write(data);
  36. }
  37. }

重新启动,刷新页面:

打开文件:

修改文件后缀名:

**思考:**图片、音乐、视频是静态文件,直接放在 webapp 下,就可以直接访问,还需要Servlet来返回吗?这样是否多此一举?
.
若文件总的大小非常大,放在 web 应用的 webapp 下就不合适了(之后打包比较费劲),使用 Servlet 去读取本地其他地方的文件来返回,就比较合适

返回 json 数据

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

后端代码:

  1. @WebServlet("/ajax-response")
  2. public class AjaxJsonServlet extends HttpServlet {
  3. @Override
  4. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  5. List<Message> messages = new ArrayList<>();
  6. Message m1 = new Message("杰杰子","花花","你一定能找到好工作的!");
  7. Message m2 = new Message("花花","杰杰子","我一定可以的!");
  8. messages.add(m1);
  9. messages.add(m2);
  10. ObjectMapper mapper = new ObjectMapper();
  11. // 把 java对象转换为 json字符串; List和数组会转换为[],一个对象{成员变量名: 值}
  12. String json = mapper.writeValueAsString(messages);
  13. System.out.println("转化的json字符串:" + json);
  14. // 设置json, 可以不设置 Content-Length, tomcat会设置
  15. resp.setContentType("application/json; charset=utf-8");
  16. resp.getWriter().println(json);
  17. }
  18. static class Message {
  19. private String from; //谁
  20. private String to; //对谁
  21. private String info; //说了什么
  22. public Message(String from, String to, String info) {
  23. this.from = from;
  24. this.to = to;
  25. this.info = info;
  26. }
  27. public String getFrom() {
  28. return from;
  29. }
  30. public void setFrom(String from) {
  31. this.from = from;
  32. }
  33. public String getTo() {
  34. return to;
  35. }
  36. public void setTo(String to) {
  37. this.to = to;
  38. }
  39. public String getInfo() {
  40. return info;
  41. }
  42. public void setInfo(String info) {
  43. this.info = info;
  44. }
  45. }
  46. }

启动服务,打开网页,查看:

后端输出:

加上前端代码:

  1. <body>
  2. <h3>获取ajax响应数据,动态生成网页内容</h3>
  3. <button onclick="generate()">试试丫</button>
  4. <div id="content"></div>
  5. </body>
  6. <script>
  7. function generate() {
  8. let content = document.querySelector("#content");
  9. ajax({
  10. url: "ajax-response",
  11. method: "get",
  12. callback: function(status,resp){
  13. console.log(resp);
  14. // resp 是一个字符串
  15. //转换为 json对象
  16. let array = JSON.parse(resp);
  17. // 遍历
  18. for(json of array) {
  19. // 每个json对象,创建一个dom来保存信息
  20. let p = document.createElement("p");
  21. p.innerHTML = json.from + "对" + json.to + "说" + json.info;
  22. content.appendChild(p);
  23. }
  24. }
  25. });
  26. }
  27. function ajax(args){//var ajax = function(){}
  28. let xhr = new XMLHttpRequest();
  29. // 设置回调函数
  30. xhr.onreadystatechange = function(){
  31. // 4: 客户端接收到响应后回调
  32. if(xhr.readyState == 4){
  33. // 回调函数可能需要使用响应的内容,作为传入参数
  34. args.callback(xhr.status, xhr.responseText);
  35. }
  36. }
  37. xhr.open(args.method, args.url);
  38. //如果args中,contentType属性有内容,就设置Content-Type请求头
  39. if(args.contentType){//js中,if判断,除了判断boolean值,还可以判断字符串,对象等,有值就为true
  40. xhr.setRequestHeader("Content-Type", args.contentType);
  41. }
  42. //如果args中,设置了body请求正文,调用send(body)
  43. if(args.body){
  44. xhr.send(args.body);
  45. }else{//如果没有设置,调用send()
  46. xhr.send();
  47. }
  48. }
  49. </script>

刷新页面:

过程分析:

客户端发起 Http 请求:

几种请求数据对应可以使用的数据格式:

数据格式queryString表单from-datajson
js: window.location.href = " "
html: <a href=" " / <img src=" " / …
浏览器地址栏直接输入 url
< form> 表单标签√ (post)√ (post,form-data)
js: ajax

服务端处理:

  1. HttpServletRequest 获取请求数据:
获取方式queryString表单from-datajson
getParameter简单类型可以获取
getPart√ (上传的文件)
getInputStream (可以获取任意格式的请求正文的数据)× (自己解析比较复杂,一般不用)×

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

  1. 根据请求数据进行校验,业务逻辑处理 (如,数据库操作)

校验: 如,账号手机号的格式
业务逻辑处理:根据请求数据的某个字段,执行不同的逻辑,如 type=1 返回登陆页面;type=0 返回首页,还可能执行数据库操作等

  1. 返回响应的内容
    返回 http 响应给客户端
  • 网页: 一般使用模板技术返回网页,而不是在 Servlet 拼接动态的 html
  • 文件: 根据需要
  • json: 前段是发送的 ajax 请求,比较常见

客户端处理 Http 响应:

  • 网页: 渲染 (浏览器自动执行)
  • 文件: 渲染或下载 (浏览器自动执行)
  • json: JavaScript 写 ajax 代码来完成
    涉及前端代码:
    解析响应正文 (json字符串) 为一个 json 对象: JSON.parse(body);
    使用 JavaScript DOM api,生成一些些 DOM 元素,把 json 对象中的字段设置到 DOM 元素中(属性,标签内容…)

相关文章