引入
web 服务器收到客户端的 HTTP 请求, 会针对每一次请求, 分别创建一个用于代表请求的 request 对象和代表响应的 response 对象.
request 和 response 对象既然代表请求和响应, 那我们要获取客户机提交过来的数据, 只需要找 request 对象就行了. 要向客户机输出数据, 只需要找 response 对象就行了.
Resp 简述
继承结构
- ServletResponse<I> // 通用的 Response, 提供了一个响应应该具有的最基本的属性和方法
- HttpServletResponse<I>
- // 在 ServletResponse 的基础上针对 HTTP 协议增加了很多强化的属性和方法
HTTP 响应 API
HTTP 协议规定一个 HTTP 响应分为状态行, 响应头, 实体内容三个部分. 于是 response 对象中封装了向客户端发送响应状态码, 响应头, 实体数据的方法.
设置响应码:
public void setStatus(int sc)
设置响应头:
- public void setHeader(String name, String value)
- public void setIntHeader(String name, int value)
- public void setDateHeader(String name, long date)
- public void addHeader(String name, String value)
- public void addIntHeader(String name, int value)
- public void addDateHeader(String name, long date)
设置响应体
- public ServletOutputStream getOutputStream() throws IOException
- public java.io.PrintWriter getWriter() throws IOException
Resp 功能
输出数据
public void setContentType(String type)
如果该响应尚未提交, 设置将发送到客户端的响应的内容类型. 给定内容类型可能包含字符编码规范, 例如
- text/html;charset=UTF-8
- .
如果在调用 getWriter 之前调用此方法, 则只根据给定内容类型设置响应的字符编码. 如果在已调用 getWriter 之后或者在已提交响应之后调用此方法, 则该方法不会设置响应的字符编码.
public void setCharacterEncoding(String charset)
设置将发送到客户端的响应的字符编码. 例如, 将它设置为 UTF-8.
如果已通过 setContentType 设置了字符编码, 则此方法将重写该字符编码, 也就是说 setContentType 兼并了
setCharacterEncoding
的功能.
response.getOutputStream().write("计算机".getBytes());
getBytes() 使用平台的默认字符集 (GBK) 将此 String 编码为 byte[]
browser 默认用操作系统默认编码 (GBK) 打开
response.getWriter().write("计算机");
默认使用 ISO-8859-1 编码, 而 ISO-8859-1 里没有汉字, 故编码时会自动将码表中没有的字符用 ? 的二进制编码代替
所有码表都支持 ISO-8859-1, 所以到达 Client 的 browser 用 GBK 解码, 显示:???
文件下载
响应头: Content-Disposition
通知浏览器以附件形式打开文件
响应头的值:
attachment;filename=...
代码演示
- public void doGet(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- // HTTP 请求头和响应头中不能包含中文, 只能包含 ISO 中有的字符
- // response.setHeader("Content-Disposition", "attachment;filename = 成绩单. png");
- response.setHeader("Content-Disposition", "attachment;filename="
- + URLEncoder.encode("成绩单", "UTF-8") + ".png");
- InputStream is = new FileInputStream(getServletContext().getRealPath("成绩单. png"));
- OutputStream out = response.getOutputStream();
- byte[] bys = new byte[1024];
- int i = 0;
- while((i=is.read(bys)) != -1)
- out.write(bys, 0, i);
- is.close();
- // response 提供的输出流用不着你来关
- }
但是, 浏览器那头出问题了. 原因: HTTP 请求头和响应头不允许包含中文, 只能包含 ISO-8859-1 中有的字符
解决办法: 使用 URL 编码 → URLEncoder 类:
static String encode(String s, String enc)
参数 1: 要进行转换的数据; 参数 2: 进行转换使用的编码, browser 只识别 U8 的, 所以必须 U8
进行转换的时候, 只会把 !ASCII 码的字符进行转换, 像字母和符号本身就是 ASCII 码, 所以不会被转换
browser 原生支持 URL 编码的数据, 所以当它看到 URL 编码的数据时, 自动就会解码 URL 编码过的数据
补充: 既然有 URLEncoder 类, 肯定也就有 URLDecoder 类.
控制定时刷新
需要设置的响应头
response.setHeader("Refresh", "1");
每隔 1s 刷新当前页面
response.setHeader("Refresh", "5;url=/03_ReqResp/index.jsp");
5s 后跳转到 index.jsp
通过 HTML 页面的 <meta> 也可以模拟响应头的功能
如:
<meta http-equiv="Refresh" content="3;url=/03_ReqResp/index.jsp"></meta>
browser 会把 <meta> 的内容当作响应头来处理
控制浏览器是否缓存资源
不缓存资源
- response.setIntHeader("Expires", -1);
- response.setHeader("Cache-Control", "no-cache");
- response.setHeader("Pragma", "no-cache");
缓存资源
- // 这个时间是个毫秒值, 是从 1970 年 1 月 1 日 00:00:00 开始算的
- response.setDateHeader("Expires", System.currentTimeMillis() + 1000l*3600*24*30);
请求重定向
代码演示
- response.setStatus(302);
- response.setHeader("Location", "/03_RequestAndResponse/index.jsp");
- // ↑等价↓
- response.sendRedirect("/03_RequestAndResponse/index.jsp");
效果展示
在大部分情况下请求重定向和转发的效果是差不多的, 这时候我们推荐使用转发, 以减少对服务器的访问. 而在某些情况下是需要使用转发的, 目的往往是为了改变浏览器地址栏里的地址 (如登录成功后转到主页), 和更改刷新操作 (如加入商品到购物车后转到购物车页面的操作)
实现验证码
- public class ImageServlet extends HttpServlet {
- public void doGet(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- response.setDateHeader("Expires", -1);
- response.setHeader("Cache-Control", "no-cache");
- response.setHeader("Pragma", "no-cache");
- // 在内存中构建出一张图片
- int height = 30;
- int width = 120;
- int posX = 5;
- int posY = 22;
- int bang = 20;
- BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
- // 获取图像上的画布
- Graphics2D graphics = (Graphics2D) image.getGraphics();
- // 设置画布背景色
- graphics.setColor(Color.GRAY);
- graphics.fillRect(0, 0, width, height);
- // 设置边框
- graphics.setColor(Color.RED);
- // 边框本身也有宽度, 边框线本身的宽度 + width/height => 右边和下边的线正好出去了
- graphics.drawRect(0, 0, width-1, height-1);
- // 画一些干扰先
- graphics.setColor(Color.CYAN);
- for(int i = 0; i <5; i++)
- // x1, y1, x2, y2: 使用当前颜色在点 (x1, y1) 和 (x2, y2) 之间画一条线
- graphics.drawLine(randNum(0, width), randNum(0, height), randNum(0, width), randNum(0, height));
- // 写字
- String base = ""; // 中文字库
- for(int i = 0; i < 4; i++) {
- graphics.setColor(new Color(randNum(0, 255), randNum(0, 255), randNum(0, 255)));
- graphics.setFont(new Font("黑体", Font.BOLD, bang));
- int r = randNum(-45, 45);
- graphics.rotate(1.0 * r / 180 * Math.PI, posX + i*30, posY);
- graphics.drawString(base.charAt(randNum(0, base.length()-1))+"", posX + i*30, posY);
- graphics.rotate(1.0 * -r / 180 * Math.PI, posX + i*30, posY);
- }
- // 将图片输出到 browser
- ImageIO.write(image, "jpg", response.getOutputStream());
- }
- private Random rand = new Random();
- private int randNum(int begin, int end) {
- return begin + rand.nextInt(end - begin);
- }
- public void doPost(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- doGet(request, response);
- }
- }
- <script type="text/javascript">
- function changeImg(obj) {
- obj.src = "/03_ReqAndResp/servlet/ValiImg?time="+new Date().getTime();
- }
- </script>
- <body>
- 验证码:<input type="text" name="valiStr" />
- <img src="/03_ReqAndResp/servlet/ValiImg" style="cursor: pointer;" onClick="changeImg(this)"/>
- </body>
Resp 注意事项
getOutputSteam() 和 getWriter() 这两个方法互斥, 调用了其中的任一方法后, 就不能再调用另一方法
在拿这个流的时候就已经确定了 response 缓冲区只能存字符数据 / 字节数据
如果两个方法都调用, 则会抛出异常
IllegalStateException: getOutputStream() has already been called for this response
注意, 这两种方式也不可以:1 请求转发的情况, AServlet 调用了 getOutputStream(), 然后再请求转发到 BServlet,BServlet 又调用了 getWriter();2 调用了 getOutputStream(), 然后 close(), 再调用 getWriter().
Servlet 程序向 ServletOutputStream / PrintWriter 中写入的数据将会被 Servlet 引擎 从 response 中获取, Servlet 引擎将这些数据当作 [响应消息的正文] , 然后再与响应状态行和各响应头组合后输出到 client
Servlet 引擎调用的 Servlet 的 service() 结束后, Servlet 引擎将检查 getWriter()/getOutputStream() 返回的输出流对象是否已经调用过 close(), 如果没有, Servlet 引擎会调用 close() 关闭该输出流 (不建议自己手动关)
Req 继承结构
- ServletRequest // 通用 request, 提供一个 request 应该具有的最基本的方法
- HttpServletRequest // ServletRequest 的子类, 针对 HTTP 协议进行了进一步增强
Req 功能
获取 HTTP 请求相关信息
获取客户机信息
- // 获取 Client 请求的完整 URL
- String url = request.getRequestURL().toString();
- // url: http://localhost/03_ReqResp/servlet/ClientInfoServlet
- System.out.println("url:" + url);
- // 获取 client 请求的资源部分的 URI
- String uri = request.getRequestURI();
- // uri: /03_ReqResp/servlet/ClientInfoServlet
- System.out.println("uri:" + uri);
- // 获取请求行中参数部分
- String qStr = request.getQueryString();
- // qStr: name=liu&pw=123
- System.out.println("qStr:" + qStr);
- // 获取 client 的 IP 地址
- String ip = request.getRemoteAddr();
- // ip: 127.0.0.1
- System.out.println("ip:" + ip);
- // 获取 client 请求方式
- String method = request.getMethod();
- // method: GET
- System.out.println("method:" + method);
- // [*] 获取当前 Web 应用的虚拟目录名称!!!
- String webName = request.getContextPath();
- // webName: /03_ReqResp
- System.out.println("webName:" + webName);
获取请求头信息
获得客户机请求头
- String getHeader(String name)
- Enumeration<String> getHeaders(String name)
- Enumeration<String> getHeaderNames()
获得具体类型客户机请求头
- int getIntHeader(String name)
- long getDateHeader(String name) // 日期对应的毫秒值
案例: Referer 请求头实现 "防盗链"
获取请求参数
- API
- String getParameter(name)
- String[] getParameterValues(name)
- Enumeration<String> getParameterNames()
- Map<String, String[]> getParameterMap()
乱码问题
Browser 用什么码表打开的表单页面, 它就用什么码表编码请求参数
根据请求方式不同
利用请求域传递对象
常用方法
- void setAttribute(String name, Object o)
- Object getAttribute(String name)
- void removeAttribute(String name)
- Enumeration getAttributeNames()
作用范围: 整个请求链上
生命周期
当 Sever 收到一个请求, 创建出一个代表当前请求的 request 对象
当请求结束, Sever 销毁代表请求的 request 对象
作用
在整个请求链范围内共享数据
通常将在 Servlet 中处理好的数据, 会存入 request 域后请求转发到 JSP 页面来进行展示
请求转发 & 请求包含
[请求转发] request.getRequestDispatcher("_____").forward(request, response);
如果在请求转发前, 已经有数据被写入 response 缓冲区, 但是这些数据还没有被发送到 Client; 那么在转发时, response 缓冲区将会被清空 (清空的是实体部分), 响应头信息不会被清空.
若请求转发时, 已经有数据被发送给 Browser(如: 调用 response.flush()), 再请求转发, 不会成功, 将会抛出异常
IllegalStateException: Cannot forward after response has been committed
. 原因: HTTP 一次请求对应一次响应. 当响应 committed 之后, 这一次的请求响应就已经结束了. 这时候你再转发 (转发: 请求 A,A 不做响应, 交给 B 来作响应), 让别的 Servlet 来做响应, 已经晚了, 故抛出异常.
导致 response commit 的原因包括:1 forward 2 redirect 3 flush
[请求包含] request.getRequestDispatcher("_____").include(request, response);
RequestDispatcher.include()
用于将 RequestDispatcher 对象封装的资源内容作为当前响应内容的一部分包含进来, 从而实现可编程的服务器端包含功能. 即: 将两个资源的输出进行合并后输出
被包含的 Servlet 程序不能改变响应消息的状态码和响应头, 就算它里面存在这样的语句, 这些语句的执行结果也将会被忽略
include 在程序执行上效果类似 forward, 但是使用 forward 只有一个程序可以生成响应, include 可以由多个程序一同生成响应 → 常用来页面布局
请求重定向和请求转发的区别
RequestDispatcher.forward()
只能将请求转发给同一个 Web 应用中的组件; 而
HttpServletResponse.sendRedirect()
还可以重定向到同一个站点上的其他应用程序中的资源, 甚至是使用绝对 URL 重定向到其他站点的资源.
如果传递给
HttpServletResponse.sendRedirect()
的相对 URL 以 / 开头, 它是相对于服务器的根目录; 如果创建 RequestDispatcher 对象时指定的相对 URL 以 / 开头, 它是相对于当前 Web 应用程序的根目录.
调用
HttpServletResponse.sendRedirect()
重定向的访问过程结束后, 浏览器地址栏中显示的 URL 会发生改变, 由初始的 URL 地址变成重定向的目标 URL; 调用
RequestDispatcher.forward()
的请求转发过程结束后, 浏览器地址栏保持初始的 URL 地址不变.
HttpServletResponse.sendRedirect()
对浏览器的请求直接作出响应, 响应的结果就是告诉浏览器去重新发出对另外一个 URL 的访问请求;
RequestDispatcher.forward()
在服务器端内部将请求转发给另外一个资源, 浏览器只知道发出了请求并得到了响应结果, 并不知道在服务器程序内部发生了转发行为.
RequestDispatcher.forward()
的调用者与被调用者之间共享相同的 request 对象和 response 对象, 它们属于同一个访问请求和响应过程; 而
HttpServletResponse.sendRedirect()
调用者与被调用者使用各自的 request 对象和 response 对象, 它们属于两个独立的访问请求和响应过程.
请求重定向: 两次请求, 两次响应
请求转发: 一次请求, 一次响应
路径专题
路径写法:
相对路径: 不以 '/' 开头, 相对路径基于当前所在的路径, 计算得到最终路径
绝对路径: 以 '/' 开头, 绝对路径在相对于的路径上, 直接拼接得到最终路径
硬盘路径: 以盘符开头的路径, 是哪个路径就是哪个路径, 没有 "相对于" 问题
真实路径
根据原理, 具体问题具体分析
举例
- servletContext.getRealPath(""); // 给一个相对于 Web 应用目录的路径
- ClassLoader.getResource(""); // 给一个相对于类加载目录的路径
- File file = new File(""); // 相对于程序的启动目录
- new InputStream(""); // 相对于程序的启动目录
都使用相对路径编写
虚拟路径
如果路径是给 Browser 用的, 这个路径相对于 [虚拟主机] , 需要写上 Web 应用的名称
如果路径是给 Sever 用的, 这个路径相对于 [Web 应用] , 可以省写 Web 应用的名称
举例
- <a href="/webName/...">
- <form action="/webName/...">
- <img src="/webName/...">
- response.setHeader("Location", "/webName/..."); response.setHeader("refresh",
- "/webName/..."); response.sendRedirect("/webName/..."); Request.getRequestDispatcher("/index.jsp").include(req,
- resp); Request.getRequestDispatcher("/index.jsp").forward(req, resp);
都使用绝对路径编写
来源: http://www.bubuko.com/infodetail-3649002.html