Skip to content

前端网络请求 ​

表单提交 ​

表单提交是前端项目中最原始的网络请求

html
<form id="myForm" enctype="application/x-www-form-urlencoded">
    <label for="name">姓名:</label>
    <input type="text" id="name" name="name" /><br /><br />

    <label for="email">邮箱:</label>
    <input type="email" id="email" name="email" /><br /><br />

    <label for="message">留言:</label><br />
    <textarea id="message" name="message"></textarea><br /><br />

    <input type="submit" value="提交" />
</form>

enctype ​

表单enctype属性用于指定在提交表单数据时所使用的编码类型

application/x-www-form-urlencoded,这是最常用的编码类型。在该编码类型下,表单数据会以键值对的形式进行 URL 编码,并将数据包含在请求的正文中。这是 HTML5 规范中指定的默认编码类型,这种编码格式将键值对的参数用&连接起来,如果有空格,将空格转换为+加号;有特殊符号,将特殊符号转换为ASCII HEX值

multipart/form-data,将数据编码成一整条消息,其中包含了多个 Parts,每个 Part 都包含头信息部和 type 等内容,通过boundary进行分割。如果要发送大量的二进制数据(non-ASCII),使用application/x-www-form-urlencoded是很浪费的,因此使用表单上传文件时往往需要指定为该编码格式

text-plain,纯文本,浏览器不进行任何编码

application/json,JSON数据类型,主要用于 AJAX 或 Fetch API 发送 JSON 格式的数据时使用,需要注意的是表单提交不支持这种数据类型,写在这里只是为了方便对比

针对不同的enctype,服务端需要按照对应的格式才能解析请求体的内容

URL编码 ​

相关面试题

为什么会存在url编码

url 长度过长在 android 中报错,如何处理

js

var originalString = 'Hello World!';

var encodedString = encodeURIComponent(originalString);
console.log(encodedString);
// 输出:Hello%20World%21

var decodedString = decodeURIComponent(encodedString);
console.log(decodedString);
// 输出:Hello World!

URL编码是一种将URL中的特殊字符转换为URL安全格式的过程。URL中允许使用一部分特殊字符(如?、&、=、/等),但某些字符在URL中具有特殊含义,或者在不同的上下文中可能被解析为其他目的。因此,为了确保URL的正确性和一致性,需要对URL进行编码。

主要原因包括以下几点:

特殊字符的含义:URL中的某些字符具有特殊含义,用于表示URL的结构、参数等信息。例如,?用于分隔URL和查询参数,&用于分隔多个查询参数,=用于分隔参数的键和值等。如果URL中包含这些特殊字符本身作为普通字符使用,而不是作为其特定含义,就需要对它们进行编码,以避免歧义和解析错误。

非ASCII字符和非安全字符:URL中可以包含非ASCII字符(如汉字、特殊符号等)和非安全字符(如空格、引号、尖括号等)。为了在传输过程中避免出现问题,这些字符需要被编码成安全的ASCII字符。URL编码使用了一种叫做百分号编码(Percent Encoding)的方式,将非ASCII字符和非安全字符转换为%xx的形式,其中xx代表字符的ASCII码值。

URL长度限制:某些网络环境对URL的长度有限制,如果URL中包含较长的特殊字符或非ASCII字符,可能会导致URL超过限制而出现问题。通过URL编码,可以将较长的字符转换为固定长度的编码形式,从而减小URL长度并确保其有效性。

总之,URL编码的目的是确保URL的正确性、一致性和安全性,避免歧义和解析错误,以及适应各种网络环境和限制。编码后的URL可以保证特殊字符和非ASCII字符在传输过程中能够正确处理,同时遵守URL的规范和标准。

XHR ​

如何封装一个可控制并发数量限制的网络请求方法?

之前遇见的一个面试题目是手写代码,封装 ajax,碰到这种面试官就赶紧跑路吧~

js
var xhr = new XMLHttpRequest()
var url = '/api'
xhr.open("GET", url, false)
xhr.onreadystatechange = function () {
    // 这里的函数异步执行,可参考之前 JS 基础中的异步模块
    if (xhr.readyState == 4) {
        if (xhr.status == 200) {
            alert(xhr.responseText)
        }
    }
}
xhr.send(null)...

在绝大多数情况下,前端开发都不需要再手动编写 XHR 代码了。

Axios ​

Axios 是一个流行的 JavaScript 库,用于进行 HTTP 请求。它是基于 Promise 的异步操作库,可以在浏览器和 Node.js 环境中使用。Axios 提供了简洁而强大的 API,可以轻松地发送 HTTP 请求,并处理响应数据。

在浏览器端,Axios底层使用的是XMLHttpRequestk

使用 Axios,你可以发送各种类型的 HTTP 请求,如 GET、POST、PUT、DELETE 等。它还支持设置请求头、发送请求参数、处理响应数据、处理错误等功能。

Axios 还提供了拦截器(interceptors)功能,可以在请求发送前或响应返回后对请求进行拦截和处理。

Axios 的主要优点之一是它的易用性和灵活性。它可以与各种前端框架(如 React、Vue.js、Angular)和后端服务器(如 Node.js、Express)无缝集成,也是 SSR 项目等前端同构项目中数据请求的常用方法。

Fetch ​

了解 fetch 吗?fetch 如何解决跨域问题?如何取消 fetch 请求?能否监听 fetch 的上传进度?

参考:Fetch-MDN 文档

Fetch API 是一种现代的网络请求 API,提供了更简洁和功能强大的方式来发送网络请求。Fetch 的核心在于对 HTTP 接口的抽象,包括 Request,Response,Headers,Body,

js
var url = "/index.php"
fetch(url, {
    method: "POST",
    body: JSON.stringify({code:'1'}).
}).then(res=>{
    return res.text()
}).then(res=>{
    console.log(res)
})

只有 POST 方式可以配置 body 参数

参考: fetch 使用的常见问题及解决办法

兼容性

fetch 本身存在浏览器兼容性的问题,此外由于 fetch 依赖 Promise,而 promise 本身也存在兼容性问题。

一种常规的 fetch-polyfill 的思路是:首先判断浏览器是否原生支持 fetch,否则结合 Promise 使用 XMLHttpRequest 的方式来实现。

cookie

fetch 可以手动控制是否需要在请求中携带 cookie,手动配置credentials,其取值有

  • omit: 默认值,忽略 cookie 的发送
  • same-origin: 表示 cookie 只能同域发送,不能跨域发送
  • include: cookie 既可以同域发送,也可以跨域发送

测试发现在 Chrome72 中 credentials 的默认值貌似已经调整为same-origin

响应错误的处理

fetch 返回的是一个 Promise,其抛出 reject 的机制为:在某些错误的 http 状态下如 400、500 等不会 reject,相反它会被 resolve;只有网络错误会导致请求不能完成时,fetch 才会被 reject;因此需要在 resolve 做一层判断

js
fetch(url)
    .then(function (response) {
        if (response.ok) {
            return response.json();
        }
        throw new Error("Network response was not ok.");
    })
    .catch(function (error) {
        console.log(error.message);
    });

不支持 timeout 和 abort

fetch 并没有提供请求超时时间的配置项,不过可以通过下面思路实现 fetch 的 timeout 功能

js
var oldFetchfn = window.fetch;
window.fetch = function (url, opts) {
    return new Promise((resolve, reject) => {
        // 超过timeout时间仍未响应,则抛出超时的错误
        var timeoutTimer = setTimeout(() => {
            reject(new Error("fetch timeout"));
        }, opts.timeout);

        oldFetchfn(url, opts).then(
            (res) => {
                clearTimeout(timeoutTimer);
                resolve(res);
            },
            (err) => {
                clearTimeout(timeoutTimer);
                reject(err);
            }
        );
    });
};

或者简单实用Promise.race[request, timeout]来模拟超时

另外,根据 Promise 指导规范标准,promise 实例是不能 abort 的,这表示在通过 fetch 发送请求之后,无法中断请求,根据上面的思路,我们可以手动实现 abort

js
var oldFetchfn = fetch;
window.fetch = function (input, opts) {
    return new Promise(function (resolve, reject) {
        var p = oldFetchfn(input, opts).then(resolve, reject);

        p.abort = function () {
            reject(new Error("fetch abort"));
        };
        return p;
    });
};

尽管上面实现了类似于 timeout 和 abort 的功能,但需要注意

  • 这里实现的 timeout 并不是“请求连接超时”的配置项,而包含了请求连接、服务器处理、响应回来直至改变 promise 状态的这一整段时间
  • 这里实现的 abort 功能,只是忽略了这次请求的响应,因为即使调用 abort,实际上本次请求也不会被 abort 掉,仍旧会发送到服务端

不支持 jsonp

jsonp 只是一种实现跨域的方法,而不是 xhr 和 fetch 这样的协议。fetch 不支持 jsonp 是理所应当的。

不支持监听进度

在 xhr 中,可以通过下面方式获取进度

js
xhr.upload.onprogress = () => {}; //上传的progress事件
xhr.onprogress = () => {}; //下载的progress事件

在 fetch 并,并没有提供相关的事件,因此fetch 是不支持 progress 的;不过在 fetch 中,response.body是一个可读字节流对象,因此可以用来模拟 progresss,详情可参考2016 - the year of web streams

js
fetch(url).then((response) => {
    var reader = response.body.getReader();
    var bytesReceived = 0;

    reader.read().then(function processResult(result) {
        if (result.done) {
            console.log("Fetch complete");
            return;
        }

        bytesReceived += result.value.length;
        console.log("Received", bytesReceived, "bytes of data so far");

        return reader.read().then(processResult);
    });
});

fetch跨域

与 ajax 相同,可以通过 CORS 解决 fetch 的跨域问题,服务器通过Access-Control-Allow-Origin响应头来允许指定的源进行跨域。

除此之外,fetch 还可以通过mode配置项,设置请求的模式,

  • same-origin,该模式不允许跨域的,发送的请求需要遵守同源策略
  • cors,该模式支持跨域请求,顾名思义它是以 CORS 的形式跨域;当然该模式也支持同域请求
  • no-cors,该模式用于跨域请求但是服务器不带 CORS 响应头,其作用是运行浏览器发送此次请求,但无法访问响应的内容,与 img 标签类似

WebSocket ​

由于 http 存在一个明显的弊端(消息只能有客户端推送到服务器端,而服务器端不能主动推送到客户端),导致如果服务器如果有连续的变化,这时只能使用轮询,而轮询效率过低,并不适合。于是 WebSocket 被发明出来。

  • 支持双向通信,实时性更强;
  • 可以发送文本,也可以二进制文件;
  • 协议标识符是 ws,加密后是 wss ;
  • 较少的控制开销。连接创建后,ws 客户端、服务端进行数据交换时,协议控制的数据包头部较小。在不包含头部的情况下,服务端到客户端的包头只有 2~10 字节(取决于数据包长度),客户端到服务端的的话,需要加上额外的 4 字节的掩码。而 HTTP 协议每次通信都需要携带完整的头部;
  • 支持扩展。ws 协议定义了扩展,用户可以扩展协议,或者实现自定义的子协议。(比如支持自定义压缩算法等)
  • 无跨域问题。

为什么 websocket 可以跨域

websocket 的请求报文中,有一个字段是 Origin,表示该请求的请求源(origin),即发自哪个域名。是因为有了 Origin 这个字段,所以浏览器没有对 websocket 执行同源策略显示,因为服务器可以根据这个字段,判断是否许可本次通信。

那么,如何保证 websocket 的安全性呢?

作为一项新技术,WebSockets 旨在从一开始就支持跨域场景。任何编写服务器逻辑的人都应该意识到跨域请求的可能性,并执行必要的验证,而无需使用浏览器端强大的同源策略。

跨域 ​

参考:浏览器中的跨域