js · 2021-08-20 0

XMLHttpRequest与AJAX的Content-Type解析及跨域

一、客户端

google浏览器

    <!-- 引入jquery -->
    <script src="http://libs.baidu.com/jquery/2.1.4/jquery.js"></script>

二、服务端

public class MyServer {

    public static void main(String[] args) {
        try {
            openServer(8888);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void openServer(int port) throws IOException {
        // 创建ServerSocket
        ServerSocket serverSocket = new ServerSocket();
        // 绑定端口
        serverSocket.bind(new InetSocketAddress(port));
        System.out.println("Server启动...");

        while (true){
            // 堵塞,等待客户端连接
            Socket socket = serverSocket.accept();
            BufferedReader br = null;
            BufferedWriter bw = null;

            try {
                System.out.println("port " + socket.getPort()  + " 连接成功...");

                br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));

                // 接受数据
                StringBuilder reqMsg = new StringBuilder();
                char[] buffer = new char[1024];
                while (true) {
                    int count = br.read(buffer);
                    reqMsg.append(buffer, 0, count);
                    if (count < 1024) {
                        break;
                    }
                }

                if (reqMsg.length() > 0) {
                    System.out.println(String.format("- - - 接受数据长度: %s - - -", reqMsg.length()));
                    System.out.println(reqMsg.toString());
                }

                // 返回数据
                // 设置允许跨域
                StringBuilder respMsg = new StringBuilder();
                respMsg.append("HTTP/1.1 200");
                respMsg.append("n");
                respMsg.append("Access-Control-Allow-Origin: *");
                respMsg.append("n");
                respMsg.append("Access-Control-Allow-Methods: GET,POST");
                respMsg.append("n");
                respMsg.append("Access-Control-Allow-Headers: content-type");
                respMsg.append("n");
                respMsg.append("Access-Control-Max-Age: 2");

                bw.write(respMsg.toString());

                System.out.println("port " + socket.getPort()  + " 连接结束...");
                System.out.println("- - - - - - - - - - - - - - - - - - - -");
            } catch (Exception e) {
                if (bw != null) {
                    bw.write("HTTP/1.1 404 ERROR:FILE NOT FINDED");
                }
            } finally {
                if (bw != null) {
                    bw.close();
                }
                if (br != null) {
                    br.close();
                }
                if (socket != null) {
                    socket.close();
                }
            }
        }

    }

}

三、XMLHttpRequest

1.xhr发送get请求

代码:

function xhrForGet() {
    var obj = {"username": "active pirate", "age": 18};

    // $.param 把对象进行url编码
    // username=active+pirate&age=18
    var encodeObj = $.param(obj);

    var xhr = new XMLHttpRequest();

    xhr.open("GET", "http://localhost:8888/hello?" + encodeObj);

    // 设置请求头,可以设置任意值,XMLHttpRequest对content-type的值,没有限制校验,相当于直接写入一行数据
    // 对于XMLHttpRequest,content-type与request body(请求体)没有关系
    // xhr.setRequestHeader("content-type", "application/x-www-form-urlencoded");

    // send函数参数request body,GET请求没有request body
    xhr.send();

    xhr.onreadystatechange = function(){
        //若响应完成
        if(xhr.readyState === 4){
            // 请求成功
            if (xhr.status === 200) {
                console.log("success");
            } else {
                console.log(xhr.status);
            }
        }
    }
}

结果:

port 36052 连接成功...
- - - 接受数据长度: 458 - - -
GET /hello?username=active+pirate&age=18 HTTP/1.1
Host: localhost:8888
Connection: keep-alive
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Accept: */*
Origin: http://localhost:63342
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:63342/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7

port 36052 连接结束...
- - - - - - - - - - - - - - - - - - - -

2.xhr发送post请求

如果没有设置content-type, content-type会是text/plain

代码:

function xhrForPost2() {
    var obj = {"username": "active pirate", "age": 18};
    // $.param 把对象进行url编码
    // username=active+pirate&age=18
    var encodeObj = $.param(obj);

    var xhr = new XMLHttpRequest();

    xhr.open("POST", "http://localhost:8888/hello");

    // 设置request body,放入字符串
    xhr.send(encodeObj);

    xhr.onreadystatechange = function(){
        //若响应完成
        if(xhr.readyState === 4){
            // 请求成功
            if (xhr.status === 200) {
                console.log("success");
            } else {
                console.log(xhr.status);
            }
        }
    }
}

结果:

port 37316 连接成功...
- - - 接受数据长度: 518 - - -
POST /hello HTTP/1.1
Host: localhost:8888
Connection: keep-alive
Content-Length: 29
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Content-Type: text/plain;charset=UTF-8
Accept: */*
Origin: http://localhost:63342
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:63342/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7

username=active+pirate&age=18
port 37316 连接结束...
- - - - - - - - - - - - - - - - - - - -

3.xhr发送文件

xhr上传文件需要使用FormData对象

如果没有设置content-type, content-type会是multipart/form-data

代码:

<form id="myForm">
    username: <input id="username" name="username" type="text"><br/>
    age: <input id="age" name="age" type="text"><br/>
    <input id="myfile" name="myfile" type="file"><br/>
    <input type="button" value="ajax sumbmit" onclick="ajaxSubmit()"><br>
</form>
        function xhrForPost3() {
            var formData = new FormData();
            formData.append("username", $('#username').val());
            formData.append("age", $('#age').val());
            formData.append("myfile", $("#myfile")[0].files[0]);

            var xhr = new XMLHttpRequest();

            xhr.open("POST", "http://localhost:8888/hello");

            // 设置request body,放入FormData对象
            // 如果没有设置content-type, content-type会是multipart/form-data
            xhr.send(formData);

            xhr.onreadystatechange = function(){
                //若响应完成
                if(xhr.readyState === 4){
                    // 请求成功
                    if (xhr.status === 200) {
                        console.log("success");
                    } else {
                        console.log(xhr.status);
                    }
                }
            }
        }

上传普通文件结果:

port 37800 连接成功...
- - - 接受数据长度: 925 - - -
POST /hello HTTP/1.1
Host: localhost:8888
Connection: keep-alive
Content-Length: 391
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryk9AH5gOzWLTc09nL
Accept: */*
Origin: http://localhost:63342
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:63342/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7

------WebKitFormBoundaryk9AH5gOzWLTc09nL
Content-Disposition: form-data; name="username"

active pirate
------WebKitFormBoundaryk9AH5gOzWLTc09nL
Content-Disposition: form-data; name="age"

18
------WebKitFormBoundaryk9AH5gOzWLTc09nL
Content-Disposition: form-data; name="myfile"; filename="a.txt"
Content-Type: text/plain

abcd
efgh

------WebKitFormBoundaryk9AH5gOzWLTc09nL--

port 37800 连接结束...
- - - - - - - - - - - - - - - - - - - -

上传图片结果:

port 38838 连接成功...
- - - 接受数据长度: 2164 - - -
POST /hello HTTP/1.1
Host: localhost:8888
Connection: keep-alive
Content-Length: 1689
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarypAybx5xfLXDlKhqh
Accept: */*
Origin: http://localhost:63342
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:63342/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7

------WebKitFormBoundarypAybx5xfLXDlKhqh
Content-Disposition: form-data; name="username"

active pirate
------WebKitFormBoundarypAybx5xfLXDlKhqh
Content-Disposition: form-data; name="age"

18
------WebKitFormBoundarypAybx5xfLXDlKhqh
Content-Disposition: form-data; name="myfile"; filename="mypic.png"
Content-Type: image/png

�PNG

�4�����&&��ffl�I"w���y��[^�Q�ǹ_3��ט���YYY.��2?�81��{r��D��� ���ٷ9�7l��o��s��$�Jw}!y�>���������
�4k'���o+�V1�����@��@Pp� ���5l{5�`P���P�GÊ&����s�aHX�+�c؜D�K�$�����u��F�u��``��%���)F�7��D�p���8;�W
�N7��c��cǾ�   �_��z5�j���y��2����u� a���(
Z�)(��Z������;��H�%
 >Z�[9ݥ n�%!(8��S'�X��&�M#u�����G
���(��a�](L��nc
��vq��vv9M���+��j���Q��9�y��� C�H�;ɩ�a�Y>����<*
�y&��Ӊ�#�$��v�RW�_�n"�� byz3�� �GCm3^Շ��@U{U�B��*���҈�i� �H��l�z�Kյ4�8q߾�ǫ':f:���X�L+�ͺ����� �6���`g    IEND�B`�
------WebKitFormBoundarypAybx5xfLXDlKhqh--

port 38838 连接结束...
- - - - - - - - - - - - - - - - - - - -

四、AJAX

1.ajax发送get请求

常用的 AJAX 库,如 jQuery,内部已经封装了参数序列化(编码)的方法 (jquery.param)

.serialize()是jquery把表单编码的方法

指定data参数值是对象时,ajax内部会调用$.param(obj),转化为编码后的字符串

代码:

function ajaxForGet() {
    var obj = {"username": "active pirate", "age": 18};

    // 指定data参数值是对象时,ajax内部会调用$.param(obj),转化为编码后的字符串
    $.ajax({
        type: "GET",
        url: "http://localhost:8888/hello" ,
        data: obj,
        success: function (result) {
            console.log(result);
        },
        error : function(err) {
            console.log(err);
        }
    });
}

结果:

port 55260 连接成功...
- - - 接受数据长度: 458 - - -
GET /hello?username=active+pirate&age=18 HTTP/1.1
Host: localhost:8888
Connection: keep-alive
Accept: */*
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Origin: http://localhost:63342
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:63342/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7

port 55260 连接结束...
- - - - - - - - - - - - - - - - - - - -

2.ajax发送post请求

如果没有指定contentType,传送给服务端的Content-Type是application/x-www-form-urlencoded

如果指定contentType: false,传送给服务端的Content-Type: text/plain

代码:

function ajaxForPost2() {
    var obj = {"username": "active pirate", "age": 18};

    // 指定data参数值是对象时,ajax内部会调用$.param(obj),转化为编码后的字符串
    $.ajax({
        type: "POST",
        url: "http://localhost:8888/hello" ,
        data: obj,
        success: function (result) {
            console.log(result);
        },
        error : function(err) {
            console.log(err);
        }
    });
}

结果:

port 57082 连接成功...
- - - 接受数据长度: 542 - - -
POST /hello HTTP/1.1
Host: localhost:8888
Connection: keep-alive
Content-Length: 29
Accept: */*
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Origin: http://localhost:63342
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:63342/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7

username=active+pirate&age=18
port 57082 连接结束...
- - - - - - - - - - - - - - - - - - - -

3.ajax上传文件

processData: false 表示不对data参数进行编码

如果没有指定contentType,传送给服务端的Content-Type是application/x-www-form-urlencoded

如果指定contentType: false,传送给服务端的Content-Type: multipart/form-data

代码:

<form id="myForm">
    username: <input id="username" name="username" type="text"><br/>
    age: <input id="age" name="age" type="text"><br/>
    <input id="myfile" name="myfile" type="file"><br/>
    <input type="button" value="ajax sumbmit" onclick="ajaxSubmit()"><br>
</form>
function ajaxForPost3() {
    var formData = new FormData();
    formData.append("username", $('#username').val());
    formData.append("age", $('#age').val());
    formData.append("myfile", $("#myfile")[0].files[0]);

    $.ajax({
        type: "POST",
        url: "http://localhost:8888/hello" ,
        data: formData,
        processData: false,
        contentType: false,
        success: function (result) {
            console.log(result);
        },
        error : function(err) {
            console.log(err);
        }
    });
}

上传普通文件结果:

port 36814 连接成功...
- - - 接受数据长度: 925 - - -
POST /hello HTTP/1.1
Host: localhost:8888
Connection: keep-alive
Content-Length: 391
Accept: */*
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryKADGPVYcZgoFO0dn
Origin: http://localhost:63342
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:63342/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7

------WebKitFormBoundaryKADGPVYcZgoFO0dn
Content-Disposition: form-data; name="username"

active pirate
------WebKitFormBoundaryKADGPVYcZgoFO0dn
Content-Disposition: form-data; name="age"

18
------WebKitFormBoundaryKADGPVYcZgoFO0dn
Content-Disposition: form-data; name="myfile"; filename="a.txt"
Content-Type: text/plain

abcd
efgh

------WebKitFormBoundaryKADGPVYcZgoFO0dn--

port 36814 连接结束...
- - - - - - - - - - - - - - - - - - - -

上传图片结果:

port 37442 连接成功...
- - - 接受数据长度: 2164 - - -
POST /hello HTTP/1.1
Host: localhost:8888
Connection: keep-alive
Content-Length: 1689
Accept: */*
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryAS0m2a4NFg2AvWTJ
Origin: http://localhost:63342
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:63342/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7

------WebKitFormBoundaryAS0m2a4NFg2AvWTJ
Content-Disposition: form-data; name="username"

active pirate
------WebKitFormBoundaryAS0m2a4NFg2AvWTJ
Content-Disposition: form-data; name="age"

18
------WebKitFormBoundaryAS0m2a4NFg2AvWTJ
Content-Disposition: form-data; name="myfile"; filename="mypic.png"
Content-Type: image/png

�PNG

�4�����&&��ffl�I"w���y��[^�Q�ǹ_3��ט���YYY.��2?�81��{r��D��� ���ٷ9�7l��o��s��$�Jw}!y�>���������
�4k'���o+�V1�����@��@Pp� ���5l{5�`P���P�GÊ&����s�aHX�+�c؜D�K�$�����u��F�u��``��%���)F�7��D�p���8;�W
�N7��c��cǾ�   �_��z5�j���y��2����u� a���(
Z�)(��Z������;��H�%
 >Z�[9ݥ n�%!(8��S'�X��&�M#u�����G
���(��a�](L��nc
��vq��vv9M���+��j���Q��9�y��� C�H�;ɩ�a�Y>����<*
�y&��Ӊ�#�$��v�RW�_�n"�� byz3�� �GCm3^Շ��@U{U�B��*���҈�i� �H��l�z�Kյ4�8q߾�ǫ':f:���X�L+�ͺ����� �6���`g    IEND�B`�
------WebKitFormBoundaryAS0m2a4NFg2AvWTJ--

port 37442 连接结束...
- - - - - - - - - - - - - - - - - - - -

五、跨域

当一个请求url的协议、域名、端口三者之间的任意一个与当前页面url不同即为跨域

跨域请求虽然会被浏览器拦截下来,但拦截的是响应(Response)而不是请求(Request)

AJAX请求是由XMLHttpRequest对象实现的,而XMLHttpRequest遵守同源策略(same-origin policy)。

如果要跨域请求,此时需要遵守CORS(Cross-Origin Resource Sharing) (跨域资源共享)机制。

浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)

非普通请求在发送请求前,会发送一个方法为OPTIONS的预请求(preflighted request)

以下三种情况会发送OPTIONS的预请求(preflighted request):

  1. 请求方法不是GET/HEAD/POST
  2. POST请求的Content-Type并非application/x-www-form-urlencoded, multipart/form-data, 或text/plain
  3. 请求设置了自定义的header字段

对于普通跨域请求:

设置响应头Access-Control-Allow-Origin: *

对于非普通跨域请求:

设置允许域的方法:
Access-Control-Allow-Methods: GET,POST

设置允许跨域的请求头:
Access-Control-Allow-Headers: content-type

六、SpringBoot 解决跨域

设置 ExposedHeader,允许 js 得到的响应头信息

1.配置 CORS

@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**") // 所有接口
                .allowCredentials(true) // 是否发送 Cookie
                .allowedOrigins("*") // 支持域
                .allowedMethods("GET", "POST", "PUT", "DELETE") // 支持方法
                // .exposedHeaders("token")
                .allowedHeaders("*");
    }
}

2.CorsFilter

@Configuration
public class BeanConfig {

    @Bean
    public CorsFilter corsFilter() {
        // 1.创建 CORS 配置对象
        CorsConfiguration config = new CorsConfiguration();
        // 支持域
        config.addAllowedOrigin("*");
        // 是否发送 Cookie
        config.setAllowCredentials(true);
        // 支持请求方式
        config.addAllowedMethod("*");
        // 允许的原始请求头部信息
        config.addAllowedHeader("*");
        // 暴露的头部信息
        // config.addExposedHeader("token");
        // 2.添加地址映射
        UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
        corsConfigurationSource.registerCorsConfiguration("/**", config);
        // 3.返回 CorsFilter 对象
        return new CorsFilter(corsConfigurationSource);
    }
}

3.CrossOrigin 注解

@RestController
@CrossOrigin
public class CrossController {

    @GetMapping("/cross")
    public String cross() {
        return "success";
    }
}