86、【JavaEE】【Servlet 与 JSP】数据传输

1、概述

  • Web 中的数据传输,基本上核心都是 HTTP 协议(HTTPS 等协议仍是建立在 HTTP 基础上的)。

  • 不论开发所使用的框架如何发生变化(从普通的 Servlet 到 Spring 等),归根到底都是浏览器、开发框架、服务器软件(Web 容器)等自动构建出了 HTTP 协议请求报文或响应报文

2、前端向后端传输数据

  • 预备知识:

https://www.jianshu.com/p/85d95427f81d 的“第6章 HTTP 首部”

HTML 中<form>标签的enctype属性规定在发送到服务器之前应该如何对表单数据进行编码。默认值是application/x-www-form-urlencoded

HTTP-数据传输-1

application/x-www-form-urlencoded中“编码”方案采用的是“URL 编码”,参考:https://www.ruanyifeng.com/blog/2010/02/url_encoding.html

multipart/form-dataapplication/x-www-form-urlencoded机制是使用“URL 编码”将数据传输,但是只适用于字符,并不适合文件(尤其是非文本文件),所以产生了multipart/form-data
相当于将数据直接以二进制形式传输(可以简单理解成“字节流”)。
字符(尤其是中文等特殊一点的字符)采用的“编码”方案暂时不明确

HTML 中<form>标签的method属性的默认值是GET

  • 前端向后端传输数据,绝大多数情况的本质是将数据写入 HTTP 请求报文中。

  • 声明:以下所列举的 HTTP 请求报文的格式并不是规范的,为了便于查看,可能会有原本不存在的“换行”、“空格”、“注释”等;另外不同的 HTTP 抓包插件、浏览器控制台所解析出的 HTTP 请求报文也是不尽相同的。

  • 目前数据传输常见的几种形式:

2.1、HTML 表单(不含提交文件的表单)向后端传输数据:

HTTP-数据传输-2

HTTP 请求报文:

请求行中方法:GETPOST

实体首部字段:Content-type: application/x-www-form-urlencoded(方法为POST的时候有效,因为GET不会将数据写入报文主体中,而是写入请求行中,所以GET的时候不具有“实体首部字段”)


<!DOCTYPE html>
<html lang="zh">

<head>
    <meta charset="UTF-8">
    <title>数据传输</title>
</head>

<body>
<!-- method="get" -->
<form action="http://localhost:8080/receive-data/receive">
    <input type="text" name="my_text" />
    <input type="submit" />
</form>
</body>

</html>
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(urlPatterns = "/receive")
public class MyServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");

        String myText = request.getParameter("my_text");
        System.out.println(myText);
    }

}

抓包(HTTP 请求、GET):

GET /receive-data/receive?my_text=22222 HTTP/1.1

Host: localhost:8080
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
sec-ch-ua: "Chromium";v="94", "Google Chrome";v="94", ";Not A Brand";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36

<!DOCTYPE html>
<html lang="zh">

<head>
    <meta charset="UTF-8">
    <title>数据传输</title>
</head>

<body>
<form enctype="application/x-www-form-urlencoded" method="post" action="http://localhost:8080/receive-data/receive">
    <input type="text" name="my_text_1" />
    <input type="text" name="my_text_2" />
    <input type="submit" />
</form>
</body>

</html>
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(urlPatterns = "/receive")
public class MyServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");

        String myText1 = request.getParameter("my_text_1");
        String myText2 = request.getParameter("my_text_2");

        System.out.println(myText1);
        System.out.println(myText2);
    }

}

抓包(HTTP 请求、POST):

POST /receive-data/receive HTTP/1.1
Host: localhost:8080
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9

Content-Type: application/x-www-form-urlencoded

sec-ch-ua: "Chromium";v="94", "Google Chrome";v="94", ";Not A Brand";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: cross-site
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36


my_text_1=8988&my_text_2=8888

上述两种 HTTP 请求报文中的主体数据,在 Servlet 中,可以直接使用javax.servlet.ServletRequestjavax.servlet.http.HttpServlet的父接口)中的String getParameter(String var1)方法获取到。

2.2、HTML 表单(含提交文件的表单)向后端传输数据:

HTTP 请求报文:

请求行中方法:文件的提交,一般是POST

实体首部字段:Content-type: multipart/form-data; boundary=······


HTTP-数据传输-2
<!DOCTYPE html>
<html lang="zh">

<head>
    <meta charset="UTF-8">
    <title>数据传输</title>
</head>

<body>
<!-- 虽然没有“文件”表单项,enctype 也可以使用 multipart/form-data -->
<form enctype="multipart/form-data" method="post" action="http://localhost:8080/receive-data/receive">
    <input type="text" name="my_text_1" />
    <input type="text" name="my_text_2" />
    <input type="submit" />
</form>
</body>

</html>
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
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(urlPatterns = "/receive")
public class MyServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");

        ServletInputStream inputStream = request.getInputStream();
        byte[] bytes = inputStream.readAllBytes();
        System.out.println(new String(bytes, StandardCharsets.UTF_8));
    }

}

抓包(HTTP 请求、POST):

POST /receive-data/receive HTTP/1.1
Host: localhost:8080
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9

Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryed5ee49V0h9DnXhE

sec-ch-ua: "Chromium";v="94", "Google Chrome";v="94", ";Not A Brand";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: cross-site
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36


------WebKitFormBoundaryed5ee49V0h9DnXhE
Content-Disposition: form-data; name="my_text_1"

我(此处实际上应该是“乱码”)
------WebKitFormBoundaryed5ee49V0h9DnXhE
Content-Disposition: form-data; name="my_text_2"

你(此处实际上应该是“乱码”)
------WebKitFormBoundaryed5ee49V0h9DnXhE--

HTTP-数据传输-3
<!DOCTYPE html>
<html lang="zh">

<head>
    <meta charset="UTF-8">
    <title>数据传输</title>
</head>

<body>
<form enctype="multipart/form-data" method="post" action="http://localhost:8080/receive-data/receive">
    <input type="text" name="my_text_1" />
    <input type="text" name="my_text_2" />
    <input type="file" name="my_file_1"/>
    <input type="file" name="my_file_2"/>
    <input type="submit" />
</form>
</body>

</html>
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
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(urlPatterns = "/receive")
public class MyServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");

        ServletInputStream inputStream = request.getInputStream();
        byte[] bytes = inputStream.readAllBytes();
        System.out.println(new String(bytes, StandardCharsets.UTF_8));
    }

}

抓包(HTTP 请求、POST):

POST /receive-data/receive HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-Length: 302466
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="94", "Google Chrome";v="94", ";Not A Brand";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
Origin: null

Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryHakjPbK74YCPfsX0

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh-TW;q=0.9,zh;q=0.8,en-US;q=0.7,en;q=0.6


------WebKitFormBoundaryHakjPbK74YCPfsX0
Content-Disposition: form-data; name="my_text_1"

₩ネム
------WebKitFormBoundaryHakjPbK74YCPfsX0
Content-Disposition: form-data; name="my_text_2"

¦ᄑᅠ
------WebKitFormBoundaryHakjPbK74YCPfsX0
Content-Disposition: form-data; name="my_file_1"; filename="c3p0-1.jpg"
Content-Type: image/jpeg


------WebKitFormBoundaryHakjPbK74YCPfsX0
Content-Disposition: form-data; name="my_file_2"; filename="DBCP-1.jpg"
Content-Type: image/jpeg


------WebKitFormBoundaryHakjPbK74YCPfsX0--

通过上述过程,明确:
a、当Content-Type: multipart/form-data; boundary=······时,数据写入报文主体,不同数据(不同表单项)之间使用boundary所定义内容的分隔。
b、在传输字符的时候,可以肯定是采用了一种编码方式(具体不明确),所以会看到报文中中文出现乱码。
c、针对Content-Type: multipart/form-data这类 HTTP 请求报文中的主体数据,在 Servlet 中只有通过解析字节数入流的方式获取,使用javax.servlet.ServletRequest#getInputStream方法获取“字节输入流”。当然,一些第三方库、框架会有提供一些方法方便开发者解析主体数据,即“封装对字节流的解析”。经典的有:Apache Commons FileUpload、Spring 等。

2.3、JSON

  • 使用 JSON 格式传输数据(前向后,后向前)也是现在比较常见的。但是,HTML 表单默认不会使用 JSON 格式数据传输,因为 HTML 表单的enctype只支持三种。所以如果想使用 JSON 传输数据,需要借助 JavaScript。

HTTP 请求报文:

请求行中方法:JSON 传输必须使用 POST

实体首部字段:Content-type: application/json;charset=utf-8

<!DOCTYPE html>
<html lang="zh">

<head>
    <meta charset="UTF-8">
    <title>数据传输</title>
</head>

<body>
<form>
    <input type="text" name="my_text_1" id="my_text_1" />
    <input type="text" name="my_text_2" id="my_text_2" />
    <input type="button" id="submit" value="提交" />
</form>

<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
<script>
    $("#submit").on('click', function() {
        let obj = {
            'text1': $('#my_text_1').val(),
            'text2': $('#my_text_2').val()
        };

        $.ajax({
            // 发送数据到服务器时所使用的内容类型 JSON
            // 注意,contentType 默认值是 application/x-www-form-urlencoded
            contentType: 'application/json;charset=utf-8',

            // 请求的方法
            type: 'POST',

            // 请求发送的 URL
            url: 'http://localhost:8080/receive-data/receive',

            // 数据以原始形式送出,不作“键值化”处理
            processData: false,

            // 数据必须使用严格的 JSON
            data: JSON.stringify(obj),

            success: function(result, status, xhr) {
                console.log(status);
            },

            error: function(xhr,status,error) {
                console.log(status);
            }
        });
    });
</script>
</body>

</html>
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
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(urlPatterns = "/receive")
public class MyServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");

        ServletInputStream inputStream = request.getInputStream();
        byte[] bytes = inputStream.readAllBytes();
        System.out.println(new String(bytes, StandardCharsets.UTF_8));
    }

}

抓包(HTTP 请求,POST):

POST /receive-data/receive HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-Length: 41
sec-ch-ua: "Chromium";v="94", "Google Chrome";v="94", ";Not A Brand";v="99"
Accept: */*

Content-Type: application/json;charset=UTF-8

sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36
sec-ch-ua-platform: "Windows"
Origin: null
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh-TW;q=0.9,zh;q=0.8,en-US;q=0.7,en;q=0.6

{"text1":"₩ネム¥メフ¦ᄑᅠ","text2":"¥﾿テ│﾿゙¥﾿テ"}

针对Content-Type: application/json;charset=UTF-8这类 HTTP 请求报文中的主体数据,在 Servlet 中也是只有通过解析字节数入流的方式获取,使用javax.servlet.ServletRequest#getInputStream方法获取“字节输入流”。当然,一些第三方库、框架会有提供一些方法方便开发者解析 JSON 数据,即“封装对字节流的解析”。经典的有:Spring 中的@RequestBody等。

2.4、JavaScript 上传文件

HTTP 请求报文:

请求行中方法:文件的提交,一般是POST

实体首部字段:Content-type: multipart/form-data; boundary=······

需要借助FormData先对表单中的数据进行一个封装(https://developer.mozilla.org/zh-CN/docs/Web/API/FormData)。

<!DOCTYPE html>
<html lang="zh">

<head>
    <meta charset="UTF-8">
    <title>数据传输</title>
</head>

<body>
<form>
    <input type="text" name="my_text" id="my_text" />
    <input type="file" name="my_file" id="my_file" />
    <input type="button" id="submit" value="提交" />
</form>

<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
<script>
    $("#submit").on('click', function() {
        let dataForm = new FormData();
        dataForm.append("text", $("#my_text").val());
        dataForm.append("file", $("#my_file")[0].files[0]);

        $.ajax({
            // 注意:将 contentType 设置 false
            contentType: false,

            // 请求的方法
            type: 'POST',

            // 请求发送的 URL
            url: 'http://localhost:8080/receive-data/receive',

            // 数据以原始形式送出,不作“键值化”处理
            processData: false,

            data: dataForm,

            success: function(result, status, xhr) {
                console.log(status);
            },

            error: function(xhr,status,error) {
                console.log(status);
            }
        });
    });
</script>
</body>

</html>
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
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(urlPatterns = "/receive")
public class MyServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");

        ServletInputStream inputStream = request.getInputStream();
        byte[] bytes = inputStream.readAllBytes();
        System.out.println(new String(bytes, StandardCharsets.UTF_8));
    }

}
POST /receive-data/receive HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-Length: 58069
sec-ch-ua: "Chromium";v="94", "Google Chrome";v="94", ";Not A Brand";v="99"
Accept: */*

Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryXuF83krJUJqLHqA9

sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36
sec-ch-ua-platform: "Windows"
Origin: null
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh-TW;q=0.9,zh;q=0.8,en-US;q=0.7,en;q=0.6


------WebKitFormBoundaryXuF83krJUJqLHqA9
Content-Disposition: form-data; name="text"

wo
------WebKitFormBoundaryXuF83krJUJqLHqA9
Content-Disposition: form-data; name="file"; filename="c3p0-1.jpg"
Content-Type: image/jpeg


------WebKitFormBoundaryXuF83krJUJqLHqA9--

通过上述过程,明确:
a、为什么在 Ajax 中的contentTypefalse,而不是multipart/form-data
因为如果$.ajax({···})中的contentType直接设置为multipart/form-data,在 HTTP 请求报文的实体首部的Content-Type直接就是multipart/form-data

Content-Type: multipart/form-data

这样的话,没有boundary,但是请求报文的主体数据中还是会以一种 boundary 分隔。这种情况下,服务端无法进行正常解析获取的数据。(https://segmentfault.com/a/1190000007207128https://www.cnblogs.com/arduka/p/13127003.html

b、当Content-Type: multipart/form-data; boundary=······时,数据写入报文主体,不同数据(不同表单项)之间使用boundary所定义内容的分隔。
c、在传输字符的时候,可以肯定是采用了一种编码方式(具体不明确),所以会看到报文中中文出现乱码。
d、针对Content-Type: multipart/form-data这类 HTTP 请求报文中的主体数据,在 Servlet 中只有通过解析字节数入流的方式获取,使用javax.servlet.ServletRequest#getInputStream方法获取“字节输入流”。当然,一些第三方库、框架会有提供一些方法方便开发者解析主体数据,即“封装对字节流的解析”。经典的有:Apache Commons FileUpload、Spring 等。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容