zoukankan      html  css  js  c++  java
  • 04-Request&Response

    引入

    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
      • 注意,这两种方式也不可以:① 请求转发的情况,AServlet 调用了 getOutputStream(),然后再请求转发到 BServlet,BServlet 又调用了 getWriter();② 调用了 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 的原因包括:① forward ② redirect ③ 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 应用的名称
      • 如果路径是给 Server 用的,这个路径相对于【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);
        
      • 都使用绝对路径编写
  • 相关阅读:
    39 多线程(十一)——ThreadLocal
    38 多线程(十)——volatile 数据同步
    Linux内存描述之内存区域zone–Linux内存管理(三)
    Linux内存描述之内存节点node–Linux内存管理(二)
    Linux内存描述之概述--Linux内存管理(一)
    服务器体系(SMP, NUMA, MPP)与共享存储器架构(UMA和NUMA)
    乐观
    乱七八糟的学习资料汇总(python3.x,pyqt,svn,git)
    Linux学习资料网站汇总链接(持续更新ing)
    浅析十大常见排序(含C++代码)
  • 原文地址:https://www.cnblogs.com/liujiaqi1101/p/13388425.html
Copyright © 2011-2022 走看看