题目 1 - 题目 5
1.CORS 是如何实现跨域的?
CORS(Cross-Origin Resource Sharing)是一种用于实现跨域请求的机制,允许浏览器和服务器之间在不同源之间共享资源。CORS 通过在 HTTP 头部添加特定的字段来控制跨域访问。以下是 CORS 的工作原理及实现细节:
当浏览器发起一个跨域请求时,它会先发送一个预检请求(preflight request),以检查服务器是否允许实际请求。这是通过发送一个 OPTIONS 请求来实现的,主要用于确认目标服务器是否允许跨域请求。
- 服务器响应
服务器需要在响应中包含适当的 CORS 头部,以指示是否允许跨域请求。主要的 CORS 头部包括:
- Access-Control-Allow-Origin:指定允许的源。可以是具体的域名,如 https://example.com,或通配符 *(允许所有源)。
- Access-Control-Allow-Methods:指定允许的方法,如 GET, POST, PUT, DELETE。
- Access-Control-Allow-Headers:指定允许的请求头,如 Content-Type, Authorization。
- Access-Control-Allow-Credentials:指示是否允许发送带有凭据(如 Cookies)的请求。
- Access-Control-Max-Age:指示预检请求的结果可以缓存的时间(以秒为单位)。
- 客户端处理响应
- 简单请求:如果请求满足 CORS 规则(如使用 GET 或 POST 方法,且请求头仅包括简单头部),浏览器会直接发送实际请求并处理响应。
- 预检请求:对于复杂请求(如使用 PUT 方法或带有自定义头部),浏览器会先发送 OPTIONS 请求,然后根据服务器的响应决定是否发送实际请求。
- 简单请求
满足以下条件的请求被认为是简单请求,不需要预检请求:
- 方法:GET, POST, HEAD
- 请求头:Accept, Accept-Language, Content-Language, Content-Type(值为 application/x-www-form-urlencoded, multipart/form-data, text/plain)
- 复杂请求
不符合简单请求条件的请求会被认为是复杂请求,需要先发送预检请求。复杂请求包括:
- 方法:PUT, DELETE, OPTIONS
- 请求头:Authorization, Content-Type(值为其他非简单值)
6 服务器设置
6.1 响应 CORS 头部
服务器需要在响应中包含必要的 CORS 头部。例如 :
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Content-Type, Authorization
6.2 处理预检请求
对于预检请求,服务器需要回应适当的 CORS 头部:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 3600
7.CORS 安全性
- Access-Control-Allow-Origin:避免使用通配符 *,特别是当使用 Credentials 时,以确保跨域请求只被信任的源访问。
- Access-Control-Allow-Credentials:当设置为 true 时,Access-Control-Allow-Origin 不能为 *,必须为具体的源。
- Access-Control-Allow-Headers 和 Access-Control-Allow-Methods:应限制仅允许必要的头部和方法,以减少安全风险。
2.JSONP 是如何实现跨域的?
JSONP 的实现原理是通过添加一个 script 标签,指定 src 属性为跨域请求的 URL,而这个 URL 返回的不是 JSON 数据,而是一段可执行的 JavaScript 代码,这段代码会调用一个指定的函数,并且将 JSON 数据作为参数传入函数中。
例如,假设我们从 http://example.com 域名下请求数据,我们可以通过在 http://example.com 中添加如下代码实现 JSONP 请求:
function handleData(data) {
// 处理获取到的数据
}
const script = document.createElement('script')
script.src = 'http://example.org/api/data?callback=handleData'
document.head.appendChild(script)
在这个例子中,我们可以在 handleData 函数中处理获取到的数据。需要注意的是,在使用 JSONP 时,需要保证服务器端返回的数据是一个可执行的 JavaScript 代码,并且必须使用指定的回调函数名来包装数据,否则无法正确处理数据。
如何获取 jsonp 的相应参数
获取 JSONP 响应结果的方法有两种,一种是通过回调函数参数获取,另一种是通过 script 标签加载完成后解析全局变量获取。
假设服务器返回以下 JSONP 响应:
callback({ name: 'Alice', age: 20 })
我们可以使用以下两种方式获取响应结果:
- 通过回调函数参数获取 在客户端定义一个全局函数作为回调函数,服务器返回的数据会作为回调函数的参数传入,这个参数可以在回调函数中处理。
function handleResponse(data) {
console.log(data.name) // Alice
console.log(data.age) // 20
}
// 创建 script 标签
const script = document.createElement('script')
script.src = 'http://example.com/api?callback=handleResponse'
// 插入到文档中开始加载 数据
document.body.appendChild(script)
- 通过全局变量获取 在客户端定义一个全局函数作为回调函数,服务器返回的数据会作为一个全局变量赋值给该函数所在的对象,我们可以在 script 标签加载完成后解析全局变量获取响应结果。
function handleResponse() {
console.log(myData.name) // Alice
console.log(myData.age) // 20
}
// 创建 script 标签
const script = document.createElement('script')
script.src = 'http://example.com/api?callback=handleResponse'
// 插入到文档中开始加载数据
document.body.appendChild(script)
// script 标签加载完成后解析全局变量
window.myData = {}
script.onload = () => {
delete window.myData // 删除全局变量
}
注意,使用 JSONP 时要注意安全问题,应该对返回的数据进行验证,避免接收到恶意代码。此外 ,JSONP** 只能发送 GET 请求**,无法发送 POST 请求,也无法使用 HTTP 请求头和请求体传递数据。
3.ajax 如何获取下载进度?
- 使用 XMLHttpRequest 监控下载进度
1.1 创建 XMLHttpRequest 实例
const xhr = new XMLHttpRequest()
1.2 配置请求
设置请求的 URL 和方法(如 GET),并配置相关的事件监听器。
xhr.open('GET', 'https://example.com/large-file', true)
xhr.responseType = 'blob' // 如果下载的是二进制文件,可以设置为 'blob'
1.3 监听 progress 事件
使用 progress 事件来获取下载进度。该事件会在文件下载过程中被触发。
xhr.onprogress = function (event) {
if (event.lengthComputable) {
const percentComplete = (event.loaded / event.total) * 100
console.log(`Download progress: ${percentComplete.toFixed(2)}%`)
} else {
// 下载进度不可计算
console.log('Download progress: unknown')
}
}
1.4 处理请求完成
在请求完成后,处理下载的数据或执行其他操作。
xhr.onload = function () {
if (xhr.status === 200) {
// 处理成功的响应,例如保存文件
const blob = xhr.response
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = 'filename.ext' // 设置下载文件名
document.body.appendChild(a)
a.click()
URL.revokeObjectURL(url) // 释放 URL 对象
} else {
console.error('Download failed')
}
}
1.5 处理错误
监听 error 和 abort 事件来处理可能的错 误情况。
xhr.onerror = function () {
console.error('Download error')
}
xhr.onabort = function () {
console.log('Download aborted')
}
1.6 发送请求
xhr.send()
const xhr = new XMLHttpRequest()
xhr.open('GET', 'https://example.com/large-file', true)
xhr.responseType = 'blob'
xhr.onprogress = function (event) {
if (event.lengthComputable) {
const percentComplete = (event.loaded / event.total) * 100
console.log(`Download progress: ${percentComplete.toFixed(2)}%`)
} else {
console.log('Download progress: unknown')
}
}
xhr.onload = function () {
if (xhr.status === 200) {
const blob = xhr.response
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = 'filename.ext'
document.body.appendChild(a)
a.click()
URL.revokeObjectURL(url)
} else {
console.error('Download failed')
}
}
xhr.onerror = function () {
console.error('Download error')
}
xhr.onabort = function () {
console.log('Download aborted')
}
xhr.send()
4.HTTP 建立连接过程
HTTP/1.1
建立连接过程遵循以下步骤:
DNS 解析,建立 TCP 连接:客户端通过三次握手建立 TCP 连接。
-
发送请求:客户端向服务器发送一个 HTTP 请求报文。
-
服务器响应:服务器收到请求后,返回一个 HTTP 响应报文。
-
客户端接收响应:客户端收到响应后,根据响应 中的状态码判断请求是否成功。
-
关闭连接:如果响应中包含 Connection: close 头部,那么连接关闭,否则保持连接,可以继续发送请求。
HTTP/2
建立连接过程使用了多路复用,可以在一个连接上同时处理多个请求和响应,具体过程如下:
-
客户端和服务器建立 TCP 连接。
-
客户端发送一个 HTTP/2 的 SETTINGS 帧,其中包含一些配置信息,如帧的大小和流的并发数量等。
-
服务器返回一个 HTTP/2 的 SETTINGS 帧,确认了客户端发送的设置。
-
客户端发送一个 HTTP/2 的 HEADERS 帧,其中包含了第一个请求的信息,同时还包含了一个唯一的标识符,称为流 ID。
-
服务器返回一个 HTTP/2 的 HEADERS 帧,其中包含了响应的信息,同时也包含了与请求相同的流 ID。
-
客户端可以在同一个连接上发送多个请求和响应,每个请求和响应都包含一个流 ID,用于标识请求和响应之间的关系。
-
当客户端或服务器想要关闭连接时,它可以发送一个 HTTP/2 的 GOAWAY 帧,表示不再接受新的请求或响应,并且将连接关闭。
HTTP/3
基于 QUIC 协议:HTTP/3 使用 QUIC 协议,它在用户数据报协议(UDP)上实现了类似于 HTTP/2 的多路复用功能。它在一个连接上支持多个请求和响应,进一步优化了网络性能。