我们在 JavaWeb 阶段,如果 “真正” 的想要做些什么,那么这一篇是不得不好好看的,因为我们从前端发来的请求,都会被 Servlet 进行处理,接收一些参数,同时返回响应结果给前端。
请求和响应.png

1. Request

1.1 概述

request 是 Servlet.service() 方法的一个参数,在客户端发出每个请求时,服务器都会创建一个 request 对象,并把请求数据封装到 request 中,然后在调用 Servlet.service() 方法时传递给 service() 方法
HttpServletRequest 对象代表客户端的请求,当客户端通过HTTP协议访问服务器时,HTTP请求头中的所有信息都封装在这个对象中,开发人员通过这个对象的方法,可以获得客户这些信息

1.2 常用方法

1.2.1 域方法

存储

  1. // 用来存储一个对象,也可以称之为存储一个域属性
  2. void setAttribute(String name, Object value)
  3. // 在ServletContext中保存了一个域属性,域属性名称为xxx,域属性的值为XXX
  4. EgservletContext.setAttribute(“xxx”, XXX”)

获取

  1. // 用来获取ServletContext中的数据
  2. Object getAttribute(String name)
  3. // 获取名为xx的域属性
  4. EgString value = (String)servletContext.getAttribute(“xxx”);
  5. // 获取所有域属性的名称;
  6. Enumeration getAttributeNames()

移除

  1. // 用来移除ServletContext中的域属性
  2. void removeAttribute(String name)

1.2.2 获取请求头数据

  1. // 获取指定名称的请求头
  2. String getHeader(String name)
  3. // 获取所有请求头名称
  4. Enumeration getHeaderNames()
  5. // 获取值为int类型的请求头
  6. int getIntHeader(String name)

1.2.3 获取请求相关的其他方法

  1. // 获取请求体的字节数,GET请求没有请求体,没有请求体返回-1;
  2. int getContentLength()
  3. /*
  4. 获取请求类型,如果请求是GET,那么这个方法返回null;如果是POST请求,那么默认
  5. 为application/x-www-form-urlencoded,表示请求体内容使用了URL编码;
  6. */
  7. String getContentType()
  8. // 返回请求方法,例如:GET/POST
  9. String getMethod()
  10. // 返回当前客户端浏览器的Locale。java.util.Locale表示国家和言语,这个东西在国际化中很有用;
  11. Locale getLocale()
  12. /*
  13. 获取请求编码,如果没有setCharacterEncoding(),那么返回null,表示使用
  14. ISO-8859-1编码;
  15. */
  16. String getCharacterEncoding()
  17. /*
  18. 设置请求编码,只对请求体有效!注意,对于GET而言,没有请求体!!!所以此方法
  19. 只能对POST请求中的参数有效!
  20. */
  21. void setCharacterEncoding(String code)
  22. // 返回上下文路径,例如:/Dmoe1
  23. String getContextPath()
  24. // 返回请求URL中的参数,例如:username=zhangSan
  25. String getQueryString()
  26. // 返回请求URI路径,例如:/Demo1/ServletDemo1
  27. String getRequestURI()
  28. /*
  29. 返回请求URL路径,例如:http://localhost/Demo1/ServletDemo1即返回除了参数
  30. 以外的路径信息;
  31. */
  32. StringBuffer getRequestURL()
  33. // 返回Servlet路径,例如:/ServletDemo1
  34. String getServletPath()
  35. // 返回当前客户端的IP地址
  36. String getRemoteAddr()
  37. // 返回当前客户端的主机名,但这个方法的实现还是获取IP地址
  38. String getRemoteHost()
  39. // 返回请求协议,例如:http
  40. String getScheme()
  41. // 返回主机名,例如:localhost
  42. String getServerName()
  43. // 返回服务器端口号,例如:8080
  44. int getServerPort()

为了方便记忆,我们画一张图辅助记忆(图是过去画的,还带着以前我的公众号,已经不维护了)
请求方法.png

1.2.4 防盗链案例练习

顾名思义,就是说让用户只能在我们站内访问对应网页,而通过复制链接到地址栏以及贴链接到别人的网站进行盗链,则全部跳转回自己的链接页面
注意:有一部分响应代码未接触,可先照着敲,大致体会,后期回来看
先看一下效果:
这是我们所制定的网站,简单理解为官网
防盗链760x232.png
在官网中,正常点击链接访问,页面跳转正常
防盗链2.png
如果我们本地写一个页面,直接绕过 a.html 去访问 http://localhost:8080/web-001/ServletDemo3"此时页面就会跳转回a.html中去,也就会回到了我们的官网,并且控制台输出:非法盗链,已经跳回原页面访问!
防盗链3.png
下面是具体的代码实现
a.html

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <a href="/web-001/ServletDemo3">葫芦娃最新资源!!!</a>
  9. </body>
  10. </html>

ServletDemo3

  1. package cn.ideal.web.request;
  2. import javax.servlet.ServletException;
  3. import javax.servlet.annotation.WebServlet;
  4. import javax.servlet.http.HttpServlet;
  5. import javax.servlet.http.HttpServletRequest;
  6. import javax.servlet.http.HttpServletResponse;
  7. import java.io.IOException;
  8. @WebServlet("/ServletDemo3")
  9. public class RequestDemo1 extends HttpServlet {
  10. public RequestDemo1() {
  11. super();
  12. }
  13. @Override
  14. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  15. //获取网页来源
  16. String referer = req.getHeader("referer");
  17. //非法盗链
  18. if (referer == null || !referer.contains("localhost:8080/web-001/a.html")) {
  19. System.out.println("非法盗链,已经跳回原页面访问!");
  20. resp.sendRedirect("a.html");
  21. return;
  22. }
  23. //正常访问
  24. resp.setContentType("text/html;charset=UTF-8");
  25. resp.getWriter().write("是他就是他,是他就是他,我们的英雄葫芦娃!!!");
  26. }
  27. }

1.3 request获取请求参数

1.3.1 GET/POST请求的使用位置

  • 浏览器地址栏直接输入:一定是GET请求
  • 超链接:一定是GET请求
  • 表单:可以是GET,也可以是POST

    1.3.2 GET/POST请求的区别

    A:GET请求
  • 请求参数会在浏览器的地址栏中显示,所以不安全

  • 请求参数长度限制长度在1K之内
  • GET请求没有请求体,无法通过request.setCharacterEncoding()来设置参数的编码

    B:POST请求
  • 请求参数不会显示浏览器的地址栏,相对安全

  • 请求参数长度没有限制

    1.3.3 获取请求参数的通用方式(Get/Post均可)

    ```java // 根据参数名称获取参数值 String getParameter(String name)

// 根据参数名称获取参数值的数组 String[] getParameterValues(String name)

// 获取所有请求的参数名称 Enumeration getParameterNames()

// 获取所有参数的map集合 Map getParameterMap()

  1. <a name="Da9q8"></a>
  2. #### 1.3.3.1 表单提交数据【通过post方式提交数据】
  3. **b.html**
  4. ```html
  5. <!DOCTYPE html>
  6. <html>
  7. <head>
  8. <meta charset="UTF-8">
  9. <title>Insert title here</title>
  10. </head>
  11. <body>
  12. <form action="/web-001/RequestDemo3" method="post">
  13. <table>
  14. <tr>
  15. <td>用户名</td>
  16. <td><input type="text" name="username"></td>
  17. </tr>
  18. <tr>
  19. <td>密码</td>
  20. <td><input type="password" name="password"></td>
  21. </tr>
  22. <tr>
  23. <td>性别</td>
  24. <td><input type="radio" name="gender" value="男">
  25. <td><input type="radio" name="gender" value="女">
  26. </td>
  27. </tr>
  28. <tr>
  29. <td>爱好</td>
  30. <td>
  31. <input type="checkbox" name="hobbies" value="游泳">游泳
  32. <input type="checkbox" name="hobbies" value="跑步">跑步
  33. <input type="checkbox" name="hobbies" value="网球">网球
  34. </tr>
  35. <input type="hidden" name="aaa" value="this is hidden text!">
  36. <tr>
  37. <td>从哪来的?</td>
  38. <td>
  39. <select name="address">
  40. <option value="广州">广州</option>
  41. <option value="北京">北京</option>
  42. <option value="深圳">深圳</option>
  43. </select>
  44. </td>
  45. </tr>
  46. <tr>
  47. <td>补充说明</td>
  48. <td>
  49. <textarea rows="2" cols="30" name="textarea"></textarea>
  50. </td>
  51. </tr>
  52. <tr>
  53. <td><input type="submit" value="提交"></td>
  54. <td><input type="reset" value="重置"></td>
  55. </tr>
  56. </table>
  57. </form>
  58. </body>
  59. </html>

RequestDemo3

  1. package cn.ideal.web.request;
  2. import javax.servlet.ServletException;
  3. import javax.servlet.annotation.WebServlet;
  4. import javax.servlet.http.HttpServlet;
  5. import javax.servlet.http.HttpServletRequest;
  6. import javax.servlet.http.HttpServletResponse;
  7. import java.io.IOException;
  8. import java.util.Arrays;
  9. @WebServlet("/RequestDemo3")
  10. public class RequestDemo3 extends HttpServlet {
  11. public RequestDemo3() {
  12. super();
  13. }
  14. @Override
  15. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  16. // 设置request字符编码的格式
  17. req.setCharacterEncoding("UTF-8");
  18. // 通过html的name属性,获取到值
  19. String username = req.getParameter("username");
  20. String password = req.getParameter("password");
  21. String gender = req.getParameter("gender");
  22. // 复选框和下拉框有多个值,获取到多个值
  23. String[] hobbies = req.getParameterValues("hobbies");
  24. String[] address = req.getParameterValues("address");
  25. // 获取到文本域的值
  26. String description = req.getParameter("textarea");
  27. // 得到隐藏域的值
  28. String hiddenValue = req.getParameter("aaa");
  29. System.out.println("username: " + username);
  30. System.out.println("password: " + password);
  31. System.out.println("gender: " + gender);
  32. System.out.println("hobbies: " + Arrays.toString(hobbies));
  33. System.out.println("address: " + Arrays.toString(address));
  34. System.out.println("description: " + description);
  35. System.out.println("hiddenValue: " + hiddenValue);
  36. }
  37. }

1.3.3.2 超链接方式提交数据【通过get方式提交数据】

c.html

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <hr/>
  9. <form action="/web-001/RequestDemo4" method="get">
  10. 参数1:<input type="text" name="p1"/><br/>
  11. 参数2:<input type="text" name="p2"/><br/>
  12. <input type="submit" value="提交"/>
  13. </form>
  14. </body>
  15. </html>

RequestDemo4

  1. // 省略包
  2. @WebServlet("/RequestDemo4")
  3. public class RequestDemo4 extends HttpServlet {
  4. public RequestDemo4() {
  5. }
  6. @Override
  7. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  8. String v1 = req.getParameter("p1");
  9. String v2 = req.getParameter("p2");
  10. System.out.println("p1=" + v1);
  11. System.out.println("p2=" + v2);
  12. }
  13. }

1.4 中文乱码问题

乱码问题主要针对Tomcat8以前的版本,Tomcat8以上版本默认编码格式是UTF-8,而不是ISO 8859-1了

  1. // 设置request字符编码的格式
  2. request.setCharacterEncoding("UTF-8");

Tomcat服务器的默认编码是ISO 8859-1,而浏览器使用的是UTF-8编码。浏览器的中文数据提交给服务器,Tomacat以ISO 8859-1编码对中文编码,当我在Servlet读取数据的时候自然拿到乱码。所以设置request的编码为UTF-8,乱码就解决了

注意:按照上述例子中(使用post方式)乱码问题已经解决了,但是在get方式中尝试仍然是乱码。在此我们需要了解 post 方法是怎么进行参数传递的。

当我们点击提交按钮的时候,数据封装进了Form Data中,http请求中把实体主体带过去了【传输的数据称之为主体】,既然request对象封装了http请求,所以request对象可以解析到发送过来的数据,于是只要把编码设置成UTF-8就可以解决乱码问题
(对上例中post请求方式进行抓包)
中文乱码.png
而get方式不同,它的数据是从消息行带过去的,没有封装到request中,所以使用request设置编码是无效的。
解决方法: 我们既然知道Tomcat默认的编码是ISO 8859-1,那么get方式由消息体带过去给浏览器的时候肯定是用ISO 8859-1编码了。
(还可以通过修改Tomcat服务器的配置来解决,但是不推荐,因为会太依赖服务器了)

  1. // 此时得到的数据已经是被ISO 8859-1编码后的字符串了,这个是乱码
  2. String name = request.getParameter("username);
  3. // 乱码通过反向查ISO 8859-1得到原始的数据
  4. byte[] bytes = name.getBytes("ISO 8859-1);
  5. // 通过原始的数据,设置正确的码表,构建字符串
  6. String value = new String(bytes,"UTF-8");

1.5 实现转发

服务器内部的资源跳转方式

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Insert title here</title>
  6. </head>
  7. <body>
  8. <form action="/web-001/RequestDemo" method="get">
  9. <h1>这是转发后的首页,地址栏地址也没有发生变化</h1>
  10. </form>
  11. </body>
  12. </html>
  1. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  2. // 获取到requestDispatcher对象,跳转到c.html
  3. RequestDispatcher requestDispatcher = request.getRequestDispatcher("/c.html");
  4. // 调用requestDispatcher对象的forward()实现转发,传入req和resp方法
  5. requestDispatcher.forward(reqt, resp);

转发的结果就是地址栏没有发生变化,但是页面已经跳转到 c.html 页面
学习 Response 后我们会学习重定向问题,到时候与转发做区分对别,请留意这一部分

1.6 Servlet之间的通讯

  1. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  2. request.setAttribute("username", "admin");
  3. // 获取到requesetDispatcher对象
  4. RequestDispatcher requestDispatcher = req.getRequestDispatcher("/servletB");
  5. // 调用requestDispatcher对象的forward()实现转发,传入req和resp方法
  6. requestDispatcher.forward(req, resp);
  7. }

ServletB

  1. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  2. // 获取到存进requeset对象的值
  3. String username = (String)req.getAttribute("username");
  4. // 在浏览器输出该值
  5. respe.getWriter().write("i am: " + username);
  6. }

我们可以同时使用 ServletContext 和 request 实现 Servlet 之间的通讯。
一般来说我们尽量使用 request,因为 ServletContext 代表着整个 web 应用,使用 ServetContext 会消耗大量的资源,而 request 对象会随着请求的结束而技术,资源会被回收,使用 request 域进行 Servlet 进行 Servlet 之间的通讯在开发中是非常频繁的。

细节:
如果在调用foreard方法之前,在Servlet程序中写入的部分已经被真正地传到了客户端,forward方法将抛出IllegalStateException异常,也就是说,不要在在转发之前写数据给浏览器。
如果调用forward方法之前向Servlet引擎的缓冲区中写入了内容,只要写入到缓冲区中的内容还没有被真正输出到客户端,forward方法就可以被正常执行,原来写入到缓冲区中的内容将被清空,但是已写入到HttpServletResponse对象中的响应头字段信息保持有效。

2. Respone

前面学习的 Request 对象可以帮助我们获取到浏览器发过来的请求,想对应的,我们就需要学习代表响应的 response 对象,它可以帮助我们进行对客户端的响应工作

2.1 响应正文

response 作为响应对象,他提供了两个响应流对象,可以向客户端输出响应正文

  1. // 获取字符流
  2. PrintWriter out = response.getWriter()
  3. // 获取字节流
  4. ServletOutputStream out = response.getOutputStream()
  1. ServletOutputStream servletOutputStream = resp.getOutputStream();
  2. servletOutputStream.write("你好世界".getBytes());
  3. servletOutputStream.write("Just for test".getBytes());

如果Tomcat版本在8以下 在outputStream中使用print()方法接收字符串,由于编码的问题,输出中文字符串的时候,就会出现乱码问题
原因是,outputStream是输出二进制的数据,print()方法先有一个将字符串转为二进制的过程,Tomcat会使用IOS 8859-1编码转换,所以出现了问题
但是使用write()却可以很好的解决这个问题,这是因为,write("Just for test".getBytes());转换为byte[]数组的时候默认使用的是gb2312编码,所以不会出现问题
但是为了后续方便,我们还是要使用UFT-8编码,如果我们在上一步骤中指定编码,看看如何

  1. response.getOutputStream.write("你好世界".getBytes().getBytes("UTF-8"));

结果就是会出现乱码,这是因为客户端浏览器不知道响应数据是什么编码的,那么如何解决这个问题呢
解决方案:

A:设置消息头

  1. // 设置头信息,告诉浏览器我回送的数据是UTF-8的
  2. response.setHeader("Content-Type""text/html;charset=UTF-8");
  3. response.getOutputStream.write("你好世界".getBytes().getBytes("UTF-8"));

B:使用html标签模拟一个http消息头

  1. ServletOutputStream servletOutputStream = resp.getOutputStream();
  2. // 使用meta标签模拟http消息头,告诉浏览器回送数据的编码和格式
  3. servletOutputStream.write("<meta http-equiv='content-type' content='text/html;charset=UTF-8>".getBytes());
  4. servletOutputStream.write("你好世界".getBytes().getBytes("UTF-8"));

C:推荐方法

  1. //设置浏览器用UTF-8编码显示数据
  2. resp.setContentType("text/html;charset=UTF-8");
  3. //获取到printWriter对象
  4. PrintWriter printWriter = resp.getWriter();
  5. printWriter.writer("你好世界")

好处:不只会调用response.setCharaceterEncoding(“utf-8”),还会设置content-type响应头(客户端浏览器会使用content-type头来解读响应数据)
总结:响应正文内容为字符,那么使用respone.getWriter(),如果响应内容是字节,例如下载文件,可以使用 response.getOutputStream()
注意:在同一个请求中,不能同时使用这两个流,否则会抛出 IllegalStateException 异常

getWriter() 的缓冲区问题
它的类型是PrintWriter类型的,所以它有缓冲区,缓冲区的默认大小为8KB,在限定代销范围以内,数据先存放在缓冲区,等到超过范围后,服务器刷新流,缓冲区中的数据发送倒客户端,如果想要响应数据马上发送到客户端,可以调用response.flushBuffer()方法来手动刷新缓冲区

2.2 设置响应头信息、状态码以及其他

2.2.1 设置响应头

使用 response 对象的 setHeader() 方法来设置响应头

  1. // 设置content-type响应头,告诉浏览器响应内容为html类型,编码为utf-8。而且同时会设置response的字符流编码为utf-8,即response.setCharaceterEncoding(“uaav tf-8”);
  2. response.setHeader(“content-type”, text/html;charset=utf-8”)
  3. // 5秒后自动跳转到指定主页
  4. response.setHeader("Refresh","5; URL=http://www.xxx.com"):

2.2.2 设置状态码

  1. // 设置状态码
  2. response.setStatus(200)
  3. // 当发送错误状态码时,跳转到指定错误页面,但可以显示错误信息
  4. response.sendError(404, “您要查找的资源不存在”)

2.2.3 其他

  1. // 等同于response.setHeader(“content-type”, “text/html;charset=utf-8”)
  2. response.setContentType("text/html;charset=utf-8")
  3. // 设置字符响应流的字符编码为UTF-8
  4. response.setCharacterEncoding(“utf-8”)
  5. // 下例表示定时刷新,3秒后跳转页面
  6. response.setHeader("Refresh", "3;URL=Bservlet");

2.3 重定向

2.3.1 概述

当你访问 www.xxx.com的时候,页面被跳转到了另一个页面,并且浏览器地址栏中的URL也发生了变化,这种技术就叫做重定向

完成重定向有两个关键的地方

  • 设置响应码
  • 设置Location头

响应码200的意思是响应成功,而重定向对应的响应码为302,所以我们需要设置响应码
因为重定向的原理为,发出二次请求,所以你需要给浏览器指定第二次请求的URL,所以需要设置 Location 头
注意:同服务器下可以使用相对路径

  1. response.setStatus(302);
  2. response.setHeader("Location", "www.xxx.com");

简单的写法

  1. response.sendRedirect("www.xxx.com");

2.3.2 转发和重定向的区别与使用场景

2.3.2.1 区别

2.3.2.1.1 实际发生位置不同,地址栏不同

  • 转发是发生在服务器的
  • 转发是由服务器进行跳转的,转发时,浏览器的地址栏是没有发生变化的,(访 问了Servlet1后即使页面跳转到了Servlet2,但浏览器的地址还是Servlet1的) 也就是说浏览器是不知道该跳转的动作,实现转发只是一次的http请求,一次转 发中request和response对象都是同一个,这也解释了为什么可以使用request 作为域对象进行Servlet之间的通讯
  • 重定向是发生在浏览器的
  • 重定向是由浏览器进行跳转的,进行重定向跳转的时候,浏览器的地址会发生变化,实现重定向的原理是由response的状态码和location头组合而实现的,这 是由浏览器进行的页面跳转实现会发出两个http请求,request域对象是无效的, 因为它不是同一个request对象

2.3.2.1.2 用法不同
原则:给服务器用的直接从资源名开始写,给浏览器用的要把应用名协写上
Requst.getRequestDispatcher(“/资源名 URL”).forward(request,response);
转发时“/”代表的是本应用程序的根目录(web-01)
Response.send(“/web应用/资源名URL”);
重定向时“/”代表的是webapps目录

2.3.2.1.3 能够去往的URL的范围不同
转发是服务器跳转,只能去往当前web应用的资源
重定向是服务器跳转,可以去往任何的资源

2.3.2.1.4 传递数据的类型不同
转发的request对象可以传递各种类型的数据,包括对象
重定向只能传递字符串

2.3.2.1.5 跳转的时间不同
转发时:执行到跳转语句就会立刻跳转
重定向:整个页面执行完后才会执行跳转

2.3.2.2 应用场景

总结:转发是带着转发前的请求的参数。重定向时新的请求
典型的应用场景:

  1. 转发:访问Servlet处理业务逻辑,然后转发到jsp中去处理结果,浏览器里URL不变
  2. 重定向:提交表单,处理成功后重定向到另一个jsp,防止表单重复提交,浏览器里的URL变了