JavaWeb核心篇(2)——Request和Response

2022-10-25 18:19:37 浏览数 (2)

JavaWeb核心篇(2)——Request和Response

上篇文章中提及到了Servlet,在Servlet中我们主要继承了HTTPServlet类,在HTTPServlet类中我们会接触到两个对象

这篇文章主要介绍Servlet中使用的Request和Respnse对象

简单介绍

首先我们先了解一下客户端与服务端之间信息如何传递:

从上图,我们可以看到:

  • 客户端发送请求,而请求是以HTTP请求数据格式进行发送,因而Servlet就创建了一个Request类来封装这些接收数据
  • 服务端给出响应,而响应是以HTTP响应数据格式进行发送,因而Servlet就创建了一个Response类来封装这些返回数据

简单来说:

  • Request对象:获得请求数据
  • Response对象:设置响应数据

Request介绍

我们在介绍Request之前,应当先了解一下Request的继承体系:

我们可以看到HttpServletRequest是基于ServletRequest接口创建的针对Http协议的请求对象接口

我们在使用Request对象时,也常常使用HttpServletRequest接口

Request获得请求数据

Request对象被创建的主要目的就是获得请求数据

我们将根据HTTP请求数据对象的三种格式分开介绍获得请求数据方法

  1. 请求行:

请求行格式:

代码语言:javascript复制
GET/request-demo/req1?username=zhangsan HTTP/1.1

请求行获得代码:

函数

解释

String getMethod()

获得请求方式(GET/POST)

String getContextPath()

获得虚拟目录(项目访问路径):/request-demo

StringBuffer getRequestURL()

获得URL(统一资源定位符):http://localhost:8080/request-demo/req1

String getRequestURI()

获得URI(统一资源标识符):/request-demo/req1

String getQueryString

获得请求参数(GET方法):username=zhangsan HTTP/1.1

  1. 请求头:

请求头格式:

代码语言:javascript复制
User-Agent: Mozilla/5.0 Chrome/91.0.4472.106

请求头获得代码:

函数

解释

String getHeader(String name)

根据请求头名称,获得值

  1. 请求体:

请求体格式:

代码语言:javascript复制
username=superbaby&password=123456

请求体获得代码:

函数

解释

ServletInputStream getInputStream()

获得字节输入流

BufferedReader getReader()

获得字符输入流

最后给出代码展示:

代码语言:javascript复制
package com.itheima.web.request;

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.BufferedReader;
import java.io.IOException;

/**
 * request 获取请求数据
 */
@WebServlet("/req1")
public class RequestDemo1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // String getMethod():获取请求方式: GET
        String method = req.getMethod();
        System.out.println(method);//GET
        // String getContextPath():获取虚拟目录(项目访问路径):/request-demo
        String contextPath = req.getContextPath();
        System.out.println(contextPath);
        // StringBuffer getRequestURL(): 获取URL(统一资源定位符):http://localhost:8080/request-demo/req1
        StringBuffer url = req.getRequestURL();
        System.out.println(url.toString());
        // String getRequestURI():获取URI(统一资源标识符): /request-demo/req1
        String uri = req.getRequestURI();
        System.out.println(uri);
        // String getQueryString():获取请求参数(GET方式): username=zhangsan
        String queryString = req.getQueryString();
        System.out.println(queryString);


        //------------
        // 获取请求头:user-agent: 浏览器的版本信息
        String agent = req.getHeader("user-agent");
        System.out.println(agent);


    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取post 请求体:请求参数

        //1. 获取字符输入流
        BufferedReader br = req.getReader();
        //2. 读取数据
        String line = br.readLine();
        System.out.println(line);


    }
}

Request通用方式获得请求参数

在请求参数的获取方法上GET与POST有所不同:

  • GET:String getQueryString()
  • POST: BufferedReader getReader()

那么如果我们能够采用一种方法同时使GET和POST获得参数,就可以实现两者的通用

Request对此提供了一下方法:

函数

解释

Map<String,String[]> getParameterMap()

获得所有参数Map的集合

String[] getParametervalues(String name)

根据名称获得参数值(数组)

String getParameter(String name)

根据名称获得参数值

我们给出通用方法,并做出解释:

代码语言:javascript复制
package com.itheima.web.request;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
import java.util.Map;

/**
 * request 通用方式获取请求参数
 */
@WebServlet("/req2")
public class RequestDemo2 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //GET请求逻辑
        //System.out.println("get....");

        //1. 获取所有参数的Map集合
        Map<String, String[]> map = req.getParameterMap();
        for (String key : map.keySet()) {
            // username:zhangsan lisi
            System.out.print(key ":");

            //获取值
            String[] values = map.get(key);
            for (String value : values) {
                System.out.print(value   " ");
            }

            System.out.println();
        }

        System.out.println("------------");

        //2. 根据key获取参数值,数组(我们希望查询参数为hobby的值,在查询中hobby的值为1,2)
        String[] hobbies = req.getParameterValues("hobby");
        for (String hobby : hobbies) {

            System.out.println(hobby);
        }

        //3. 根据key 获取单个参数值
        String username = req.getParameter("username");
        String password = req.getParameter("password");

        System.out.println(username);
        System.out.println(password);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //POST请求逻辑

        // 因为两者可以共用一个方法,所以doPost直接调用doGet即可
        this.doGet(req,resp);

        /*System.out.println("post....");

        //1. 获取所有参数的Map集合
        Map<String, String[]> map = req.getParameterMap();
        for (String key : map.keySet()) {
            // username:zhangsan lisi
            System.out.print(key ":");

            //获取值
            String[] values = map.get(key);
            for (String value : values) {
                System.out.print(value   " ");
            }

            System.out.println();
        }

        System.out.println("------------");

        //2. 根据key获取参数值,数组
        String[] hobbies = req.getParameterValues("hobby");
        for (String hobby : hobbies) {

            System.out.println(hobby);
        }

        //3. 根据key 获取单个参数值
        String username = req.getParameter("username");
        String password = req.getParameter("password");

        System.out.println(username);
        System.out.println(password);*/


    }
}

请求参数中文化导致乱码问题

当我们的请求参数中如果存在中文数据,可能会出现乱码(Tomcat8以下版本)

我们分别介绍POST和GET的中文乱码解决方案

  1. POST:

首先我们从根本上解释一下为什么会出现乱码:

  • POST底层以getReader()的方式以ISO-8859-1的形式获得输入流
代码语言:javascript复制
//1. 解决乱码:POST,getReader()

// 默认情况下POST以ISO-8859-1的形式获取流
// POST中可以直接设置字符输入流的编码

// request.setCharacterEncoding() 改变字符输入流的获得格式
request.setCharacterEncoding("UTF-8");
  1. GET:

首先我们从根本上解释一下为什么会出现乱码:

  • 当HTML识别到内容后,会以UTF-8的形式进行编码,并发送给服务端
  • 但服务端在接收到数据后,会以ISO-8859-1的形式进行解码
  • 编码解码方式不同,对中文的处理方式不同,导致中文数据出现乱码
代码语言:javascript复制
// 我们以一个例子来模拟GET乱码的解决过程

package com.itheima.web.request;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;

public class URLDemo {

    public static void main(String[] args) throws UnsupportedEncodingException {
        String username = "张三";

        // 1.2步模拟了乱码过程;3,4步解决乱码过程
        
        
        //1. URL编码(HTML传递)
        //URLEncoder.encode(username, "utf-8");表示对username进行utf-8形式的编码,形成code
        String encode = URLEncoder.encode(username, "utf-8");
        System.out.println(encode);

        //2. URL解码
        //URLDecoder.decode(encode, "ISO-8859-1");表示对encode进行ISO-8859-1形式的解码,形成decode
        String decode = URLDecoder.decode(encode, "ISO-8859-1");

        System.out.println(decode);

        //3. 转换为字节数据,编码
        byte[] bytes = decode.getBytes("ISO-8859-1");

        //4. 将字节数组转为字符串,解码
        String s = new String(bytes, "utf-8");

        System.out.println(s);

    }
}

因为GET的方法属于通用方法,所以在整个项目中可以直接通过第二种方法来进行GET和POST的中文乱码修改问题

代码语言:javascript复制
package com.itheima.web.request;


import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

/**
 * 中文乱码问题解决方案
 */
@WebServlet("/req4")
public class RequestDemo4 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1. 解决乱码:POST,getReader()
        //request.setCharacterEncoding("UTF-8");//设置字符输入流的编码

        //2. 获取username
        String username = request.getParameter("username");
        System.out.println("解决乱码前:" username);

        //3. GET,获取参数的方式:getQueryString
        // 乱码原因:tomcat进行URL解码,默认的字符集ISO-8859-1
       /* //3.1 先对乱码数据进行编码:转为字节数组
        byte[] bytes = username.getBytes(StandardCharsets.ISO_8859_1);
        //3.2 字节数组解码
        username = new String(bytes, StandardCharsets.UTF_8);*/

        username  = new String(username.getBytes(StandardCharsets.ISO_8859_1),StandardCharsets.UTF_8);

        System.out.println("解决乱码后:" username);

    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}

Request请求转发

首先讲解一下请求转发的概念:

  • 请求转发:一种在服务器内部的资源跳转方法
  • 当客户端发送请求后,服务端可以选择把这个请求转发出去或者说是共享出去

实现方式:

代码语言:javascript复制
req.getRequestDispatcher("资源B地址").forward(req,resp)

实现所需函数:

函数

解释

void setAttribute(String name,Object o)

存储数据到request域(一般在资源A地址存储)

Object getAttribute(String name)

根据key,获得value(一般在资源B地址存储)

void removeAttribute(String name)

根据key,删除该键值对

请求转发特点:

  • 浏览器地址栏路径不发生变化
  • 只能转发到当前服务器的内部资源
  • 一次请求,可以在转发的资源间使用request共享数据

我们给出两个/demo服务器端进行模拟:

代码语言:javascript复制
// 客户端资源A

package com.itheima.web.request;


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.nio.charset.StandardCharsets;

/**
 * 请求转发
 */
@WebServlet("/req5")
public class RequestDemo5 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("demo5...");
        System.out.println(request);
        //存储数据
        request.setAttribute("msg","hello");

        //请求转发
        request.getRequestDispatcher("/req6").forward(request,response);

    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}
代码语言:javascript复制
// 客户端资源B

package com.itheima.web.request;


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("/req6")
public class RequestDemo6 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("demo6...");
        System.out.println(request);
        //获取数据
        Object msg = request.getAttribute("msg");
        System.out.println(msg);

    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}

Response介绍

同样,我们也来介绍一下Response的体系结构:

我们在使用Response时,也以HttpServletResponse为主

设置响应数据

和获取数据相同,我们把函数根据响应数据的三部分分别展示:

  1. 响应行:

响应行格式:

代码语言:javascript复制
HTTP/1.1 200 OK

响应行设置函数:

函数

解释

void setStatus(int sc)

设置响应状态码

  1. 响应头:

响应头格式:

代码语言:javascript复制
Content-Type:text/html

响应头设置函数:

函数

解释

void setHeader(String name,String value)

设置响应头键值对

  1. 响应体:

响应体格式:

代码语言:javascript复制
<html><head></head><body></body></html>

响应体设置函数:

函数

解释

PrintWriter getWriter()

获得字符输出流

ServletOutputStream getOutputStream()

获得字节输出流

下面给出代码示例:

代码语言:javascript复制
package com.itheima.web.response;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;

@WebServlet("/resp1")
public class ResponseDemo1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("resp1....");

        //1.设置响应状态码 302
        response.setStatus(302);
        //2. 设置响应头 Location
        response.setHeader("Location","/request-demo/resp2");
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}

重定位

首先我们介绍一下重定位:

  • 重定位:一种资源跳转方式
  • 客户端向服务器A发出请求,当该资源A无法满足客户端发出的请求,资源A返回响应(响应码302表示无法处理,并给出响应头location:xxx告诉客户端应该去哪个资源B解决问题),然后客户端再向资源B发送请求,由资源B来设置响应数据

实现方式:

代码语言:javascript复制
resp.setStatus(302);
resp.setHeader("location","资源B的路径")
代码语言:javascript复制
resp.sendRedirect("资源B的路径")

重定位特点:

  • 浏览器地址栏路径发生变化
  • 可以重定位到任意位置的资源(服务器内部,外部均可以)
  • 两次请求,不能在多个资源使用request共享数据

下面给出代码示例:

代码语言:javascript复制
package com.itheima.web.response;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;

@WebServlet("/resp1")
public class ResponseDemo1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("resp1....");

        //重定向
        /*//1.设置响应状态码 302
        response.setStatus(302);
        //2. 设置响应头 Location
        response.setHeader("Location","/request-demo/resp2");*/

        //简化方式完成重定向
        //动态获取虚拟目录
        String contextPath = request.getContextPath();

        // 这里资源B的路径是虚拟目录 设置名
        response.sendRedirect(contextPath "/resp2");
        //response.sendRedirect("https://www.itcast.cn");
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}

资源路径问题: 浏览器使用:需要加虚拟目录(项目访问路径) 服务端使用:不需要加虚拟目录 目前已学习内容: resp.sendRedirect("路径") : 加虚拟目录 req.getRequestDispatcher("路径") : 不加虚拟目录

Response响应字符数据

我们可以使用Response对象向客户端发送数据

具体步骤:

  1. 通过Response对象获得字符输出流:
代码语言:javascript复制
PrintWriter writer = resp.getWriter();
  1. 写数据
代码语言:javascript复制
writer.write("aaa");

注意:

  • 该流不需要关闭,随着响应结束,response对象销毁,由服务器关闭
  • 中文数据乱码:原因通过Response获取的字符输出流默认编码:ISO-8859-1
代码语言:javascript复制
resp.setContentType("text/html;charset=utf-8");

代码展示:

代码语言:javascript复制
package com.itheima.web.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("/resp3")
public class ResponseDemo3 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        //1. 获取字符输出流
        PrintWriter writer = response.getWriter();
        //content-type
        //response.setHeader("content-type","text/html");

        writer.write("你好");
        writer.write("<h1>aaa</h1>");
        //细节:流不需要关闭

    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}

Response响应字节数据

我们可以使用Response对象向客户端发送数据

传统具体步骤:

  1. 通过Response对象获得字符输出流
代码语言:javascript复制
ServletOutputStream outputStream = resp.getOutputStream();
  1. 写数据
代码语言:javascript复制
outputStream.write(字节数据);

IOUtils工具类使用:

  1. 导入坐标:
代码语言:javascript复制
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>
  1. 使用即可:
代码语言:javascript复制
IOUtils.copy(输入流,输出流);

代码展示:

代码语言:javascript复制
package com.itheima.web.response;


import org.apache.commons.io.IOUtils;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * 响应字节数据:设置字节数据的响应体
 */
@WebServlet("/resp4")
public class ResponseDemo4 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        //1. 读取文件
        FileInputStream fis = new FileInputStream("d://a.jpg");

        //2. 获取response字节输出流
        ServletOutputStream os = response.getOutputStream();

        //3. 完成流的copy
       /* byte[] buff = new byte[1024];
        int len = 0;
        while ((len = fis.read(buff))!= -1){
            os.write(buff,0,len);
        }*/

        IOUtils.copy(fis,os);

        fis.close();

    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}

结束语

好的,关于Request和Respone的内容就到这了

附录

该文章属于学习内容,具体参考B站黑马程序员陈老师的JavaWeb课程

这里附上链接:01-Request和Response介绍&Request继承体系_哔哩哔哩_bilibili

0 人点赞