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 旨在从一开始就支持跨域场景。任何编写服务器逻辑的人都应该意识到跨域请求的可能性,并执行必要的验证,而无需使用浏览器端强大的同源策略。

跨域

参考:浏览器中的跨域