前言

前后端通讯(Frontend-Backend Communication)指的是前端和后端之间进行数据交互和通信的过程。

在 Web 开发中,前端通常负责展示数据和用户交互,后端负责处理业务逻辑和数据存储。为了实现前后端的数据交互和通信,需要使用一些通信方式和协议。

通信方案介绍

在 Web 开发中,前后端通信是指前端(用户界面)和后端(服务器端)之间的数据交换;而前后端通信是Web应用的基础,其技术方案多种多样,涵盖了传统的 HTTP 请求响应模式以及现代的实时通信技术。以下介绍一些常见的前后端通信技术方案:

  1. HTTP 请求 - 响应模式

  • 同步通信:表单提交、页面链接

  • 异步通信:AJAX、FetchAPI、Axios、SuperAgent……

  1. 实时通信

  • WebSocket

  • Server-Sent Events (SSE)

  • Long Poling

  • HTTP/2 Server Push

  1. 新兴通信技术

  • gRPC

  • GraphQL

  • ……

  1. 跨域通信解决方案

  • JSONP (JSON with Padding)

  • CORS (Corss-Origin Resource Sharing)

1. HTTP 请求 - 响应模式

在前后端通信中,HTTP 请求方案包含同步通信和异步通信两种形式。这些通信形式的选择取决于特定应用的需求、用户体验和系统架构。

1.1. 同步通信

同步通信通常指的是客户端发送请求到服务器,然后等待服务器响应。在响应到达之前,客户端通常不会执行其他操作,即用户界面可能会出现阻塞,直到收到服务器的响应。

工作原理

同步通信的核心是请求 - 响应周期。当用户在浏览器中进行如点击一个链接、提交表单等操作时,浏览器向服务器发送一个 HTTP 请求。服务器接收到这个请求后,根据请求的内容处理用户发送的数据或者生成新的页面,然后将响应数据(通常是 HTML 页面)发送回浏览器。浏览器接收到数据后会对页面进行重新渲染,显示新的内容。

使用场景

在 Web 应用中,同步通信的典型例子包括表单提交、页面链接。

  1. 表单提交:这是同步通信最常见的使用场景之一。

  • 用户在 Web 表单中输入数据,如登录、注册、或填写联系信息等,然后提交表单到服务器。

  • 服务器处理表单数据(如验证用户输入、存储到数据库等),然后生成新的 HTML 页面作为响应,或者重定向到其他页面。

  1. 页面导航:用户点击链接时,浏览器向服务器发送请求,服务器返回新页面的 HTML 内容,浏览器接着渲染这个新页面。

1.1.1. 表单提交

<!-- HTML表单 -->
<form action="/submit-form" method="post">
  <label for="name">Name:</label>
  <input type="text" id="name" name="name">
  <label for="email">Email:</label>
  <input type="email" id="email" name="email">
  <button type="submit">Submit</button>
</form>

此例中,用户填写姓名和电子邮件后点击提交,表单数据会发送到服务器的 /submit-form 地址,服务器处理后可能会显示一个感谢页面或显示表单提交错误信息。

1.1.2. 页面导航

当用户点击一个链接,如:

<a href="/about">Learn More About Us</a>

浏览器会发出一个 GET 请求到服务器的 /about 路由,服务器则返回关于我们页面的 HTML 内容。用户的浏览器接收到 HTML 后,会渲染这个新的页面。

<a href="/files/user-guide.pdf" download>Download User Guide</a>

文件下载:虽然用户的直观感觉可能是 “下载”,实际上从 HTTP 的角度看,这也是一次 GET 请求,服务器响应的内容是文件数据。因此,文件下载可以看作是特殊类型的页面导航,因为它导致浏览器接收新的响应数据(文件)。

1.2. 🔥异步通信

在Web开发中,异步通信是一种强大的技术,允许客户端(如Web浏览器)在等待服务器响应的同时继续执行其他操作,这种通信方式可以提高应用程序的响应性和用户体验,尤其是在交互密集型的应用中。

使用场景

以下是几种流行的异步 HTTP 请求方案:

  1. XMLHttpRequest (XHR)XMLHttpRequest 是 Web API 的一部分,最初由 Microsoft 开发,并被所有主要浏览器采纳。它允许 Web 应用能够异步发送 HTTP 或 HTTPS 请求到服务器,并载入返回的数据。

  • 特点: 支持文本和二进制数据的传输,可用于从服务器请求数据或提交表单内容。

  1. AJAX (Asynchronous JavaScript and XML) AJAX 不是一种技术,而是多种技术的组合,它允许 Web 页面实现异步更新。这意味着可以在不重新加载整个页面的情况下,与服务器交换数据并更新部分网页内容。XHR 是 AJAX 的基础,是 AJAX 的底层实现原理

  • 特点: 通常与 XMLHttpRequest 结合使用,但不限于 XML 数据格式,也常用 JSON 和其他数据格式。

  1. jQuery.ajax()jQuery 提供的.ajax() 方法简化了 AJAX 操作。它是一个功能强大的方法,可以发送 GET、POST 请求等,并处理不同数据类型。

  • 特点: 支持全局事件处理器、详细的配置选项,并且易于使用和理解,封装了 XHR 的复杂性。

  1. Fetch API 现代的 Web API,用于替代 XMLHttpRequest。Fetch API 使用 Promise,提供了一个更强大、更灵活的网络请求工具。

  • 特点: 更简洁的 API,原生支持 Promise,不依赖任何外部库,使得异步操作更加方便。

  1. AxiosAxios是一个基于Promise的HTTP客户端,适用于浏览器和Node.js。它提供了丰富的接口和功能,如拦截请求和响应、转换请求数据和响应数据等。

  • 特点: 支持请求和响应的拦截,自动转换JSON数据,处理并发请求。

  1. SuperAgentSuperAgent 是一个轻量级、进度化的 AJAX API 库,适用于 Node.js 和 Web 浏览器。它被设计为更灵活和易于使用。

  • 特点: 有着类似 jQuery 的链式语法,支持同步和异步处理,提供了简洁而强大的 API 接口。

区别

优点

缺点

XMLHttpRequest

XHR 是一种原生的浏览器对象,用于发送和接收数据的异步通信技术

原生浏览器支持,无需引入额外的库或依赖。

可以直接操作底层的网络请求和响应,具有较高的灵活性。

可以处理各种数据格式,不仅限于 XML,包括文本、HTML、XML 和 JSON

  • XHR API 相对底层,需要手动处理状态和事件。

  • 不支持 Promise,导致管理复杂异步逻辑较为困难。

  • 跨域请求设置较为复杂。

jQuery.ajax()

jQuery.ajax () 是 jQuery 库提供的封装了 AJAX 的方法。

  • 简单易用,支持链式调用和丰富的配置选项。

  • 强大的社区和插件生态。

  • 兼容多个浏览器,并提供了一致的 API 接口。

  • 需要引入整个 jQuery 库,增加了额外的文件大小。

  • 在现代前端框架中使用减少,可能逐渐被原生方法取代。

  • 不如 Fetch API 和 Axios 那样现代和简洁。

Fetch API

Fetch API 是现代浏览器提供的用于发送和接收网络请求的 API。

  • 原生支持 Promise,使得异步代码更简洁。

  • 现代浏览器支持良好。

  • 不需要额外的库或框架。

  • 在老版本的浏览器中不被支持,需要 polyfill。

  • 默认不发送 cookies,需要额外配置。

  • 只在网络故障时抛出错误,需要手动检查 HTTP 错误状态。

Axios

Axios 是一个基于 Promise 的 HTTP 客户端库,用于浏览器和 Node.js 环境。

  • 基于 Promise,易于使用和管理。

  • 支持浏览器和 Node.js,使其适用于前后端。

  • 自动转换 JSON 数据,配置简单。

  • 丰富的请求和响应配置选项,如请求取消、拦截器、错误处理等。

  • 需要引入额外的库文件,增加了文件大小,虽然文件不大但是是额外依赖

  • 对于简单的 AJAX 请求,可能显得过于重。

  • 相较于 Fetch API,社区和生态较小。

SuperAgent

SuperAgent 是一个轻量级的 HTTP 请求库,用于浏览器和 Node.js 环境。

  • 简洁的 API 和链式语法,易于使用。

  • 同样支持 Node.js 和浏览器。

  • 强大的功能,包括进度事件和文件上传。

  • 功能相对较少,可能缺乏一些高级特性(如请求取消、拦截器等)。

  • 由于其轻量级特性,某些功能可能需要自行扩展或引入其他库。

  • 虽然比 Axios 小,但仍然需要额外依赖。

  • 比 Fetch API 和原生 XHR 支持的社区小。

总结

Fetch API:推荐用于现代 Web 应用,特别是在不需要支持 IE 浏览器的项目中。

Axios:适用于需要支持老旧浏览器或在 Node.js 环境中进行 HTTP 通信的项目。

SuperAgent:适用于快速开发场景,尤其是在喜欢链式语法和需要简洁 API 的情况下。

1.2.1. XMLHttpRequest

XMLHttpRequest - Web APIs | MDN

1.2.1.1. 介绍

XMLHttpRequest 是一个Web API,用于在浏览器中执行异步请求。它允许前端应用与服务器进行数据交换,而无需重新加载页面。这使得开发者能够创建平滑的用户体验和动态的Web应用。

核心特性

  • 异步或同步请求:虽然 XHR 最常用于异步操作,它也可以配置为同步请求。然而,同步请求会阻塞主线程,因此不推荐在生产环境中使用。

  • 支持多种数据格式:包括文本、HTML、XML、JSON 等。

  • 细粒度的事件模型:通过监听不同的事件(如 load, error, abort, progress 等),开发者可以对请求的不同阶段做出响应。

  • 能够发送 GET 和 POST 请求:以及其他 HTTP 方法,如 PUT, DELETE 等。

  • 访问 HTTP 响应数据:包括状态码、响应头和响应体。

使用过程中的状态码

  • 0: UNSENTopen() 尚未被调用。

  • 1: OPENEDopen() 已被调用。

  • 2: HEADERS_RECEIVEDsend() 已被调用,响应头和响应状态已可获取。

  • 3: LOADING— 下载中;responseText 中已经获取了部分数据。

  • 4: DONE — 请求操作已完成。

1.2.1.2. 使用

使用 XMLHttpRequest 步骤:

  1. 创建 XMLHttpRequest 对象 const xhr = new XMLHttpRequest()

  2. 配置请求方法请求 url 地址 xhr.open('请求方法', '请求url地址')

  3. 监听事件,接收响应结果 xhr.addEventListener('loadend', () => {// 响应结果 xhr.response})

  4. 发起请求 xhr.send()

假如我们借助 json-server 搭建了一个本地 JSON 服务器,现在有一个本地测试的接口 http://localhost:3000/users,这个接口会返回一些用户数据(关于搭建本地接口测试的可以参考文章 __ json-server),我们以此为例来使用 XMLHttpRequest 请求:

我的测试 JSON 数据是这样的:

{
  "users":  [
      {
        "id": 1,
        "name": "Joe",
        "age": 30,
        "email": "joe@gmail.com",
        "password": "123456",
        "address": "123 Main St."
      },
      {
        "id": 2,
        "name": "Jane",
        "age": 25,
        "email": "jane@gmail.com",
        "password": "123456",
        "address": "456 Main St."
      },
      {
        "id": 3,
        "name": "John",
        "age": 40,
        "email": "john@gmail.com",
        "password": "123456",
        "address": "789 Main St."
      },
      {
        "id": 4,
        "name": "Jimmy",
        "age": 35,
        "email": "jimmy@gmail.com",
        "password": "123456",
        "address": "321 Main St."
      }
    ]
}
/**
 * 发送 GET 请求
 * */
// 1.创建XMLHttpRequest对象
const xhr = new XMLHttpRequest()
// 2.配置请求
xhr.open("GET", "http://localhost:3000/users", true)
// 3.(可选)设置响应类型
xhr.responseType = "json"
// 4.处理响应 监听请求状态改变事件
xhr.onreadystatechange = function () {
  if (xhr.readyState === XMLHttpRequest.DONE) {  // 请求完成
    if (xhr.status === 200) {  // HTTP状态码200 OK
      console.log(xhr.response);  // 输出响应内容
    } else {
      console.error('Request failed. Returned status of ' + xhr.status); // 处理请求失败
    }
  }
}
// 5.发送请求
xhr.send()

/**
 * 发送 POST 请求
 * */
const xhr = new XMLHttpRequest()
xhr.open('POST', 'http://localhost:3000/users', true)

// 设置期望的返回数据类型为JSON
xhr.responseType = 'json'

// 设置请求头,告诉服务器发送的数据类型为JSON
xhr.setRequestHeader('Content-Type', 'application/json')

// 定义请求完成的回调函数
xhr.onload = function() {
  if (xhr.status >= 200 && xhr.status < 300) {
    // 请求成功,打印返回的数据
    console.log('Response:', xhr.response)
  } else {
    // 请求失败,打印状态码
    console.error('Request failed. Status:', xhr.status)
  }
}

// 发送JSON数据
let data = JSON.stringify({
  id: 8,
  name: 'Joy',
  age: 30,
  email: 'joy@example.com',
  password: '123456',
  address: '123 Main St'
})
xhr.send(data)

1.2.2. AJAX

XMLHttpRequest 文档:XMLHttpRequest - Web APIs | MDN

AJAX(Asynchronous JavaScript and XML)是一种利用 XHR 进行异步通信的前端技术。它使用 JavaScript 在后台与服务器进行数据交互,实现无需刷新整个页面的数据更新。在 AJAX 中,XHR 对象是用于发送 HTTP 请求和接收服务器响应的核心组件。

虽然 XHR 是 AJAX 的传统核心组件,现代的 Fetch API 则提供了一个更先进的接口,用于执行异步 HTTP 请求。Fetch API 通常被视为 XHR 的现代替代品,扩展了 AJAX 的概念,使得异步通信更简洁和强大。

AJAX 核心原理

AJAX 基于以下几个核心技术:

  1. JavaScript: 使用 JavaScript 发起 HTTP 请求,并处理服务器响应。

  2. XMLHttpRequest (XHR): XHR 对象是 AJAX 的核心,它提供了在 JavaScript 代码中执行 HTTP 请求的方法和属性。

  3. 异步通信: AJAX 允许在后台向服务器发送请求,并在收到响应后处理它,而不会阻塞页面加载或用户交互。

AJAX 通常与 XMLHttpRequest 结合使用,关于使用直接参照:1.2.1.2使用

1.2.3. jQuery.ajax()

jQuery 官网jQuery

更多关于 jQuery.ajax() 介绍:jQuery.ajax() | jQuery API Documentation

1.2.3.1. 介绍

jQuery.ajax () 是 jQuery 库中的一个方法,用于发送异步 HTTP 请求。它是一种简化和优化 AJAX 操作的方式,提供了更简洁易用的 API。

基本语法

$.ajax(options)

其中,options 是一个包含各种设置选项的对象,用于指定请求的参数和回调函数。以下是常用的选项及其说明:

  • url:指定请求的 URL。

  • method:指定请求的 HTTP 方法,如 GET、POST、PUT、DELETE 等,默认为 GET。

  • data:指定发送到服务器的数据。可以是一个普通对象或以查询字符串形式的字符串。

  • dataType:指定预期的响应数据类型,如 'json'、'xml'、'html'、'text' 等。根据响应类型,jQuery 将自动解析响应数据。

  • headers:指定请求头的键值对,用于设置自定义的请求头信息。

  • success:请求成功时调用的回调函数。参数为成功响应的数据、状态文本和请求对象。

  • error:请求失败时调用的回调函数。参数为请求对象、错误信息和错误对象。

  • complete:请求完成时调用的回调函数。无论请求成功或失败,都会触发该回调函数。

核心特性:

  1. 支持多种 HTTP 方法:包括 GET、POST、PUT、DELETE 等。

  2. 数据类型处理:自动处理 JSON、XML、HTML 以及纯文本等数据类型。

  3. 灵活的配置选项:提供了大量配置选项,如 urltypedatatimeoutcontentTypedataType 等。

  4. Promise 接口:jQuery 1.5+ 版本的 $.ajax() 返回一个可用于链式调用的 Promise 对象。

  5. 全局 AJAX 事件处理:如 ajaxStart, ajaxStop, ajaxComplete, ajaxError, ajaxSuccessajaxSend

1.2.3.2. 使用

如果是在 Vue 项目中使用,需要先安装对应依赖:

npm install jquery
// 导入 jQuery
import $ from 'jquery'

// 使用 jQuery 发送 AJAX 请求 -- GET 请求
$.ajax({
  url: 'http://localhost:3000/users',
  method: 'GET',
  dataType: 'json',
  success: function(response, status, xhr) {
    console.log('success', response) // 处理成功响应
  },
  error: function(xhr, status, error) {
    console.log(error) // 处理请求错误
  },
  complete: function(xhr, status) {
    console.log('Request completed.') // 请求完成时的回调函数
  }
})

// 使用 jQuery 发送 AJAX 请求 -- POST 请求
$.ajax({
    url: 'http://localhost:3000/users',
    type: 'POST',
    contentType: 'application/json', // 设置发送信息至服务器时内容编码类型
    data: JSON.stringify({
      id: 8,
      name: 'Joy',
      age: 30,
      email: 'joy@example.com',
      password: '123456',
      address: '123 Main St'
    }),  // 将对象转换为 JSON 字符串
    dataType: 'json', // 预期服务器返回的数据类型
    success: function(response) {
        console.log('Server responded with:', response)
    },
    error: function(xhr, status, error) {
        console.error('Submission failed:', error)
    }
})

1.2.4. Fetch API

Fetch API - Web APIs | MDN

1.2.4.1. 介绍

Fetch API 是一种现代的浏览器内置的网络请求 API,用于发送和接收数据。它提供了一种简洁、灵活且功能强大的方式来处理异步请求。与传统的 XMLHttpRequest (XHR) 相比,Fetch API 使用基于 Promise 的接口,使得管理复杂的异步逻辑变得更加简单。此外,Fetch 支持数据流、请求取消等现代网络功能,且默认不携带 cookies,增强了请求的安全性。这些特性使 Fetch API 成为开发现代 Web 应用的首选工具。

核心特性:

  1. 基于 Promise: Fetch API 使用 Promises 来处理结果,这使得异步代码更加简洁并易于管理。

  2. 更好的语义性: 方法名如 fetch() 直观地描述了API的功能。

  3. Request 和 Response 对象: 这些对象代表请求和响应的详细信息,提供了丰富的API来管理这些数据。

  4. 流式数据: Fetch 支持数据流,使得处理大型响应数据如视频或高分辨率图片变得更加高效。

  5. 无需担心跨域: 默认情况下,Fetch 遵守同源政策,但它也支持 CORS,使得处理跨源请求更加直接。

发送请求

使用 Fetch API 发送请求的基本语法如下:

// url 是请求的目标 URL。
// options 是可选的配置对象,用于设置请求的参数,例如请求方法、请求头、请求体等。
fetch(url, options)
  .then(response => {
    // 处理响应
  })
  .catch(error => {
    // 处理错误
  });

处理响应

Fetch API 返回一个 Promise 对象,可以使用 .then() 方法处理成功的响应和 .catch() 方法处理错误。在 .then() 中,可以使用 response 对象来访问响应的相关信息。

以下是一些常用的响应处理方法:

  • response.json():将响应解析为 JSON 格式。

  • response.text():将响应解析为文本格式。

  • response.blob():将响应解析为二进制数据。

  • response.formData():将响应解析为 FormData 对象。

  • response.arrayBuffer():将响应解析为 ArrayBuffer 对象。

1.2.4.2. 使用
1.2.4.2.1. GET 请求
// 发送 GET 请求 查询数据
fetch('http://localhost:3000/users')
    .then(response => response.json())
    .then(data => console.log('Users:', data))
    .catch(error => console.error('Error:', error))
1.2.4.2.2. POST 请求
import {Random} from "mockjs"

// 发送 POST 请求 添加数据
const user = {
  id: Random.guid(),
  name: Random.cname(),
  age: Random.integer(18, 60),
  email: Random.email(),
  password: Random.string(8),
  address: Random.city()
}
fetch('http://localhost:3000/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(user)
})
    .then(response => response.json())
    .then(data => console.log('User created:', data))
1.2.4.2.3. PUT 请求
//发送 PUT 请求 更新数据
const user = {
  name: 'Jane Doe',
  age: 30,
  email: 'jane@example.com',
  password: '123456',
  address: '北京'
}
fetch('http://localhost:3000/users/1', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(user)
})
    .then(response => response.json())
    .then(data => console.log('User updated:', data))
    .catch(error => console.error('Error:', error))
1.2.4.2.4. DELETE 请求
//发送 DELETE 请求 删除数据
fetch('http://localhost:3000/users/1', {
  method: 'DELETE'
})
    .then(response => response.json())
    .then(data => console.log('User deleted:', data))
    .catch(error => console.error('Error:', error))

1.2.5. Axios

Axios

1.2.5.1. 介绍

Axios 是一个基于 promise 的 HTTP 客户端,作用于浏览器和 node.js。它是一个非常流行的库,因为它简化了发送 HTTP 请求的过程,并提供了易于使用的 API 来处理请求和响应。在服务端它使用原生 node.js http 模块,而在客户端 (浏览端) 则使用 XMLHttpRequests。

核心特性:

  1. 从浏览器中发送 XMLHttpRequests:Axios 允许你在浏览器中执行 GET、POST 和其他 HTTP 方法的请求,行为类似于 Fetch API,但接口更友好。

  2. 从 Node.js 创建 http 请求:在服务端,Axios 能够执行 HTTP 请求到远端服务器,非常适合 SSR (Server-Side Rendering) 或 API 数据抓取。

  3. 支持 Promise API:所有请求方法都返回一个 promise 对象,可以使用 .then.catch 和 async/await 来处理响应或错误。

  4. 拦截请求和响应:在请求或响应被 then 或 catch 处理前,允许你截取它们,添加统一的处理逻辑。

  5. 转换请求数据和响应数据:自动转换 JSON 数据,并且可以使用自定义函数来自动转换请求或响应的数据。

  6. 取消请求:可以使用取消令牌取消请求,非常适合用于例如搜索自动完成功能,用户输入时频繁发送请求,但只关心最新请求的响应。

  7. 客户端支持防御 XSRF:当使用 withCredentials 时,Axios 可以自动从 cookie 读取 CSRF token 并添加到 HTTP 请求头中。

1.2.5.2. 使用

安装 Axios:

npm install axios
1.2.5.2.1. GET 请求
import axios from 'axios'
// 发送 GET 请求
axios.get('http://localhost:3000/users')
      .then(function (response) {
         // 处理成功的响应
        console.log('Users', response.data);
      })
      .catch(function (error) {
        // 处理发生错误的情况
        console.log('Error', error)
      })
1.2.5.2.2. POST 请求
import { Random } from "mockjs"
import axios from "axios"

const user = {
  id: Random.guid(),
  name: Random.cname(),
  age: Random.integer(18, 60),
  email: Random.email(),
  password: Random.string(8),
  address: Random.city()
}
axios.post('http://localhost:3000/users', user)
  .then(response => {
    console.log(response.data)  // 数据被成功创建时的响应
  })
  .catch(error => {
    console.log(error)  // 发生错误时的处理
  })
1.2.5.2.3. 设置请求头

有时你需要设置特定的 HTTP 头部,例如发送认证信息。你可以在请求中添加 headers 配置:

axios.get('https://api.example.com/data', {
  headers: {
    'Authorization': `Bearer YOUR_ACCESS_TOKEN`
  }
})
.then(response => {
  console.log(response.data)
})
.catch(error => {
  console.error('Error:', error)
})
1.2.5.2.4. 处理错误

处理 Axios 请求中的错误是非常重要的。你应该总是设置 catch 块来捕获可能发生的异常:

axios.get('https://api.example.com/data')
  .then(response => {
    console.log(response.data)
  })
  .catch(error => {
    if (error.response) {
      // 请求已发出,但服务器响应的状态码不在 2xx 范围内
      console.error('Error', error.response.status);
      console.error('Error response', error.response.data)
    } else if (error.request) {
      // 请求已经成功发出,但没有收到响应
      console.error('Error request', error.request)
    } else {
      // 发生在设置请求时触发的错误
      console.error('Error', error.message)
    }
  })
1.2.5.2.5. 使用 async/await

Axios 返回的是 promise,所以你可以使用现代 JavaScript 的 async/await 语法来处理异步逻辑:

async function fetchData() {
  try {
    const response = await axios.get('http://localhost:3000/users')
    console.log(response.data)
  } catch (error) {
    console.error('Error:', error)
  }
}

fetchData()
1.2.5.2.6. 拦截器

Axios 拦截器提供了一种强大的方法来捕获请求或响应在它们被处理前或处理后的瞬间。这允许你对 HTTP 请求和响应进行全局的修改、日志记录、授权、或错误处理。拦截器可以在发送请求之前或在收到响应之后执行代码,分别称为请求拦截器和响应拦截器。

请求拦截器:

请求拦截器可以在发送请求到服务器之前修改请求配置,或者在请求发送之前执行某些操作。常见的用途包括添加通用的请求头(如认证令牌)、日志记录或设置请求超时。

响应拦截器:

响应拦截器在服务器返回响应数据之后、达到 then 或 catch 前被调用。它们常用于统一处理 API 返回的错误、格式化响应数据等。

// 添加请求拦截器
axios.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么,例如添加请求头
    config.headers.Authorization = 'Bearer YOUR_TOKEN'
    return config
}, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error)
})

// 添加响应拦截器
axios.interceptors.response.use(function (response) {
    // 对响应数据做点什么
    // 例如,你可以根据自己的需求修改数据格式:
    response.data = {
        ...response.data,
        additionalProperty: 'value'
    }
    return response
}, function (error) {
    // 对响应错误做点什么
    if (error.response && error.response.status === 401) {
        // 处理例如未授权的逻辑
        console.error('You are not authorized.')
    }
    return Promise.reject(error)
})

移除拦截器:

如果不再需要某个拦截器,可以使用 eject 方法将其移除,保持应用的性能和清晰度。

const myInterceptor = axios.interceptors.request.use(function () {/*...*/})
axios.interceptors.request.eject(myInterceptor)

1.2.6. SuperAgent

GitHub - superagent: Ajax for Node.js and browsers (JS HTTP client).

1.2.6.1. 介绍

SuperAgent 是一个小巧、灵活且功能强大的 HTTP 请求库,适用于 Node.js 和浏览器。它的 API 简洁易懂,支持链式语法,使得 HTTP 请求变得直观。SuperAgent 除了支持标准的 HTTP 方法外,还包含丰富的请求和响应处理功能,如 JSONP 请求和进度事件。

核心特点:

  1. 轻量级与简洁性:SuperAgent 相对于其他请求库如 Axios 或 Fetch API,提供了一个更简单、更轻量级的接口。

  2. 链式语法:支持流畅的链式调用方法,使得构建请求变得简单直观。

  3. 丰富的请求方法:支持 GET、POST、PUT、DELETE 等 HTTP 方法,并易于处理请求参数、头部、cookies 和响应数据。

  4. 兼容性:同时支持在 Node.js 环境和各种主流浏览器中使用。

  5. 内置支持 JSON:自动处理 JSON 数据,无需额外配置即可发送 JSON 请求和解析 JSON 响应。

  6. 文件上传支持:在 Node.js 和浏览器中均支持文件上传功能,使用简单。

  7. 响应处理:支持事件监听,例如监听下载进度或上传进度。

1.2.6.2. 使用

安装 SuperAgent:

npm install superagent
1.2.6.2.1. GET 请求

在浏览器或 Node.js 中发送一个 GET 请求并处理响应:

import superagent from 'superagent'

// 示例
superagent
  .get('http://localhost:3000/users')
  .end((err, res) => {
    console.log(res.body)
  })

superagent
    .get('https://api.example.com/items')
    .query({ user: '123' }) // 添加 URL 查询字符串
    .set('Accept', 'application/json') // 设置请求头
    .end((err, res) => {
        if (err) {
            console.error('Request failed', err)
        } else {
            console.log('Response received', res.body)
        }
    })
1.2.6.2.2. POST 请求

发送一个带有 JSON 数据的 POST 请求:

import superagent from 'superagent'

superagent
    .post('https://api.example.com/items')
    .send({ name: 'New Item', price: 39.99 }) // 发送 JSON 数据
    .set('Accept', 'application/json')
    .end((err, res) => {
        if (err) {
            console.error('Error posting data', err)
        } else {
            console.log('Posted successfully', res.body)
        }
    })
1.2.6.2.3. 文件上传

SuperAgent 也支持通过 attach 方法上传文件,这在处理表单时非常有用:

import superagent from 'superagent'

superagent
    .post('https://api.example.com/upload')
    .attach('file', '/path/to/file.jpg') // 指定要上传的文件
    .end((err, res) => {
        if (err) {
            console.error('Upload failed', err)
        } else {
            console.log('Upload successful', res.text)
        }
    })
1.2.6.2.4. 使用 Promise

为了让 SuperAgent 支持 Promise,你可以使用 .then().catch() 而不是 .end()

superagent
    .get('https://api.example.com/resource')
    .then(res => {
        console.log('Response:', res.body)
    })
    .catch(err => {
        console.error('Error:', err)
    })
1.2.6.2.5. 设置超时

设置请求超时是网络请求中常见的需求,SuperAgent 允许你轻松配置超时时间:

superagent
    .get('https://api.example.com/resource')
    .timeout({
        response: 5000,  // Wait 5 seconds for the server to start sending,
        deadline: 60000, // but allow 1 minute for the file to finish loading.
    })
    .then(res => console.log(res.body))
    .catch(err => console.error('Timeout or other error:', err))

2. 实时通信

实时通信在前后端通信中起着越来越重要的作用,特别是对于需要实时更新数据的应用程序,如聊天应用、在线游戏、股票交易等。

当考虑实现实时数据通信的不同技术时,选择合适的工具依赖于具体的应用需求、用户环境和所需的通信模式。以下是对 几种常用的实时通信技术方案 的介绍:

区别

应用场景

优点

缺点

WebSocket

WebSocket 提供全双工通信机制,允许数据在客户端和服务器之间双向流动。

实时多玩家游戏。

聊天应用。

实时协作工具。

实时交易或拍卖平台。

  • 提供全双工通信,允许客户端和服务器同时发送和接收数据。

  • 一旦建立连接,消息交换速度快,开销小。

  • 在某些网络环境中(如使用代理、防火墙、负载均衡器等)配置和兼容性可能会有问题。

  • 对服务器资源的占用比 HTTP 请求更持久,因此在高并发场景下对服务器的压力更大。

Server-Sent Events (SSE)

SSE 使得服务器可以主动向浏览器客户端发送更新,是一种建立在 HTTP 上的轻量级方法。

股票或金融市场的实时数据更新。

新闻或社交媒体实时推送更新。

在线直播的评论或互动消息。

  • 简单的服务器到客户端单向通信。

  • 基于 HTTP 协议,易于实现和部署。

  • 自动重连机制,提高稳定性。

  • 仅支持文本数据。

  • 浏览器支持不如 WebSocket 广泛(例如,旧版的 Internet Explorer 不支持)。

Long Poling

Long Polling 是一种在客户端发起请求后,服务器等待数据可用即刻返回响应的技术。

适用于实时性要求不是非常高的场景。

当其他更高效的技术(如 WebSocket 和 SSE)不可用时作为后备方案。

  • 相对于传统的轮询,响应更及时。

  • 兼容性好,几乎所有的浏览器都支持。

  • 效率较低,每次通信完成后需要重新建立 HTTP 连接。

  • 服务器端长时间保持连接,可能导致资源浪费。

HTTP/2 Server Push

HTTP/2 Server Push 允许服务器主动向客户端推送资源,而无需客户端明确请求。

预加载必要的资源以加速网页应用的首屏加载。

优化用户浏览体验,如推送用户可能马上需要的资源。

  • 提前推送资源,可以显著提高页面加载速度。

  • 整合于 HTTP/2 协议,提高了网络通信的整体效率。

  • 若推送的资源客户端已缓存,可能导致带宽浪费。

  • 需要精确控制服务端推送的逻辑,避免过度推送。

总结

WebSocket 是最佳选择用于高交互性、双向通信的实时应用。 双向实时通信

Server-Sent Events 是实现单向实时数据流(如实时新闻、更新推送)的最佳方案。 单向数据流

Long Polling 适用于兼容性要求极高的情况,或者作为更先进技术的后备方案。 浏览器兼容性问题

HTTP/2 Server Push 最适用于优化资源加载,减少网页渲染时间。 提高网页加载速度

2.1. WebSocket

介绍: WebSocket 是一种在单个 TCP 连接上提供全双工通信的协议。它允许客户端和服务器之间进行实时、双向通信,无需客户端发起多次 HTTP 请求。

如何使用

  • 在客户端,使用 JavaScript 中的 WebSocket API 与服务器建立 WebSocket 连接。

  • 在服务器端,需要实现 WebSocket 协议的服务器端代码,以接受 WebSocket 连接并处理来自客户端的消息。

示例:

// 客户端
var socket = new WebSocket('ws://example.com/socket');
socket.onopen = function(event) {
  console.log('WebSocket连接已建立');
};
socket.onmessage = function(event) {
  console.log('接收到消息:', event.data);
};
socket.onclose = function(event) {
  console.log('WebSocket连接已关闭');
};

// 服务器端(Node.js示例)
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', function connection(ws) {
  console.log('客户端已连接');
  ws.on('message', function incoming(message) {
    console.log('接收到消息:', message);
  });
});

2.2. Server-Sent Events (SSE)

介绍: Server-Sent Events (SSE) 是一种客户端从服务器接收推送式事件的技术。它允许服务器向客户端发送任意数量的消息,而客户端可以接收这些消息而无需发起额外的请求。

如何使用

  • 在客户端,使用 JavaScript 中的 EventSource API 建立到服务器的 SSE 连接,并监听服务器发送的事件。

  • 在服务器端,需要实现 SSE 协议的服务器端代码,以便向客户端发送事件。

示例:

// 客户端
var eventSource = new EventSource('http://example.com/events');
eventSource.onmessage = function(event) {
  console.log('接收到事件:', event.data);
};
eventSource.onerror = function(event) {
  console.error('连接错误');
};

// 服务器端(Node.js示例)
const http = require('http');
http.createServer((req, res) => {
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive'
  });
  res.write('data: Hello\n\n');
}).listen(8080);

2.3. Long Polling

介绍: Long Polling 是一种模拟实时通信的技术,它通过客户端向服务器发送HTTP请求,服务器保持请求打开直到有新数据可用,然后立即响应客户端请求。

如何使用

  • 在客户端,定期发送 HTTP 请求到服务器,并等待服务器响应。

  • 在服务器端,接收客户端请求,并保持请求打开直到有新数据可用,然后响应客户端请求。

示例

// 客户端
function longPoll() {
  fetch('http://example.com/data')
    .then(response => {
      if (!response.ok) {
        throw new Error('请求失败');
      }
      return response.json();
    })
    .then(data => {
      console.log('接收到数据:', data);
      // 继续进行下一次长轮询
      longPoll();
    })
    .catch(error => {
      console.error('长轮询失败:', error);
      // 重新发起长轮询
      longPoll();
    });
}
longPoll();

// 服务器端(Node.js示例)
const http = require('http');
http.createServer((req, res) => {
  if (req.url === '/data') {
    // 处理数据请求,并在有数据可用时响应客户端请求
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ message: 'Hello' }));
  } else {
    // 处理其他请求
    res.writeHead(404);
    res.end();
  }
}).listen(8080);

2.4. HTTP/2 Server Push

介绍: HTTP/2 Server Push 是 HTTP/2 协议的一个特性,它允许服务器在客户端明确请求之前主动发送资源。这种技术旨在改善页面加载时间,通过预测客户端将需要哪些资源并提前发送它们,从而减少往返延迟。

工作原理: 当客户端如浏览器请求一个HTML页面时,服务器不仅发送请求的HTML文件,还可以主动发送额外资源(如CSS、JavaScript文件或图片),这些资源可能很快就会被HTML引用。这意味着客户端可以立即开始下载这些资源,而不是等待解析HTML后再发现需要再次发起这些请求。

优点:

  1. 提高性能:减少了额外的往返时间,因为客户端不需要发现需要一个资源后再去请求它。

  2. 减少延迟:可以在一个连接中推送多个资源,减少了由于多次手动请求造成的延迟。

缺点:

  1. 资源预测错误:如果服务器推送的资源不是客户端所需要的,那么这将导致带宽的浪费。

  2. 复杂性增加:服务器必须更智能地决定何时使用 Server Push,这增加了服务器端的逻辑复杂性。

如何使用 HTTP/2 Server Push

服务器端配置

大多数现代 web 服务器都支持 HTTP/2 和 Server Push,例如 Apache, Nginx, 和 Node.js。下面是在不同环境中配置和使用 HTTP/2 Server Push 的例子。

Nginx

在 Nginx 中,你可以通过配置添加 HTTP/2 Server Push。首先确保你的 Nginx 版本支持 HTTP/2(Nginx 1.9.5+)。

server {
  listen 443 ssl http2;
  server_name example.com;

  ssl_certificate /path/to/ssl/cert.pem;
  ssl_certificate_key /path/to/ssl/key.pem;

  location / {
    root /path/to/website;
    index index.html;

    # HTTP/2 Server Push
    http2_push /style.css;
    http2_push /script.js;
    http2_push /logo.png;
  }
}

Apache

对于 Apache,你需要使用 mod_http2 模块,它自 Apache 2.4.17 起就支持 HTTP/2。

<IfModule http2_module>
    Protocols h2 h2c http/1.1
    <Directory /path/to/website>
        # HTTP/2 Server Push
        H2Push on
        H2PushResource /style.css
        H2PushResource /script.js
        H2PushResource /logo.png
    </Directory>
</IfModule>

Node.js(使用 HTTP/2 模块):

const http2 = require('http2');
const fs = require('fs');

const server = http2.createSecureServer({
    key: fs.readFileSync('localhost-privkey.pem'),
    cert: fs.readFileSync('localhost-cert.pem')
});

server.on('stream', (stream, headers) => {
    // 推送 style.css
    const pushStream = stream.pushStream({ ':path': '/style.css' }, (err) => {
        if (err) throw err;
    });
    pushStream.respondWithFile('./style.css', {
        'content-type': 'text/css'
    });

    // 响应主请求
    stream.respondWithFile('./index.html', {
        'content-type': 'text/html'
    });
});

server.listen(8443);

3. 新兴通信技术

随着现代网络技术的发展,前后端通信领域也出现了一些新兴的技术,这些技术不仅提高了通信效率,还优化了用户体验和开发过程。

以下是一些较为新兴的前后端通信技术:

区别

应用场景

优点

缺点

gRPC

gRPC 是一个高性能的远程过程调用 (RPC) 框架。

微服务架构中服务间的内部通信。

性能敏感的应用,如实时数据处理和大规模分布式系统。

  • 高性能与效率:使用 Protocol Buffers(一种轻量级的二进制序列化工具),效率比 JSON 高。

  • 跨语言支持:支持多种编程语言,包括 Java、C#, Python, Go 等。

  • 强类型系统:接口通过 .proto 文件定义,确保系统间通信的类型安全。

  • 内建负载均衡和认证功能:支持高级功能如负载均衡、TLS/SSL 安全认证等。

  • 学习曲线:需要了解 Protocol Buffers 和 gRPC 的特定概念。

  • 环境兼容性:不如 RESTful API 广泛支持,有时在某些环境下集成较复杂。

  • 调试和跟踪:由于基于二进制,调试和错误跟踪可能不如 JSON 易于处理。


GraphQL

GraphQL 是一个数据查询和操作语言,由 Facebook 开发。

需要大量不同数据的客户端应用,如复杂的单页应用(SPA)。

当应用需要经常变更前端设计而不影响后端时。

  • 高度灵活:客户端可以精确指定所需数据,减少无效数据的传输。

  • 单一端点:所有数据通过一个 API 端点请求,简化了复杂应用的数据管理。

  • 实时数据:通过订阅机制支持实时数据更新。

  • 自省系统:内置的类型系统允许查询 API 的能力,便于开发和维护。

  • 复杂查询性能问题:复杂的查询可能导致性能问题,因为每个查询可能需要不同的数据库操作。

  • 缓存复杂性:由于每个请求可能都是唯一的,常规的 HTTP 缓存机制不易直接应用。

  • 学习曲线:虽然灵活,但需要开发者学习新的查询语言和概念。

总结

如果需要高性能的服务间通信,特别是在微服务架构中,gRPC 是更合适的选择。

如果你开发的是前端重、需要大量定制数据的应用,GraphQL 提供了更大的灵活性和效率。

3.1. gRPC

gRPC

介绍: gRPC 是一个高性能、开源和通用的 RPC 框架,由 Google 主导开发,基于 HTTP/2 协议。gRPC 允许定义函数,可以远程调用这些函数。它专为低延迟和高吞吐量的通信而设计。

优点

  • 支持多种编程语言。

  • 基于 HTTP/2,支持双向流。

  • 使用 Protocol Buffers 作为接口定义语言,性能优于 JSON。

使用方法: 定义 .proto 文件来指定服务接口,然后使用 gRPC 工具生成服务端和客户端代码。

生成代码后,在服务器端和客户端实现和调用这些服务。

// message.proto
syntax = "proto3";

service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

3.2. GraphQL

GraphQL | A query language for your API

介绍: GraphQL 是由 Facebook 开发的一个数据查询和操作语言,它提供了一种更高效、强大和灵活的替代方案来替代传统的 REST API。GraphQL 允许客户端精确指定它们需要哪些数据,服务器然后返回这些请求的数据,不多也不少。

优点

  • 允许客户端请求所需的确切数据,减少数据传输。

  • 能够通过单一请求聚合多个数据源的数据。

  • 更容易适应前端需求的变化,无需每次需求变更都更新 API。

使用方法: 在服务器端,你需要设置一个 GraphQL 服务,定义 schema(数据模型)和 resolvers(如何返回数据)。

const { ApolloServer, gql } = require('apollo-server');

const typeDefs = gql`
  type Query {
    hello: String
  }
`;

const resolvers = {
  Query: {
    hello: () => 'Hello, world!',
  },
};

const server = new ApolloServer({ typeDefs, resolvers });
server.listen().then(({ url }) => {
  console.log(`🚀 Server ready at ${url}`);
});

在客户端,你可以使用像 Apollo Client 这样的库来查询数据。

import { ApolloClient, InMemoryCache, gql } from '@apollo/client';

const client = new ApolloClient({
  uri: 'http://localhost:4000',
  cache: new InMemoryCache()
});

client.query({
  query: gql`
    query GetHello {
      hello
    }
  `
}).then(result => console.log(result.data));

4. 跨域通信解决方案

跨域通信是前后端开发中常见的一个挑战,主要是因为浏览器的同源策略限制。同源策略是一种安全措施,阻止一个域的文档或脚本与另一个域的资源进行交互。这意味着在默认情况下,来自不同源的 Web 页面不能自由地获取或交互数据。

然而,有几种技术可以绕过这些限制:

  1. JSONP(JSON with Padding)

  2. CORS(Cross-Origin Resource Sharing)

  3. 代理服务器

区别

优点

缺点

JSONP

JSONP 是一种跨域通信的老旧解决方案,允许跨域请求数据通过动态 <script> 标签加载,适用于仅支持 GET 请求的简单应用场景。

  • 兼容性好:几乎所有的浏览器都支持 <script> 标签,无需复杂配置。

  • 简单实用:实现相对简单,不需要服务器做复杂配置。

  • 只支持 GET 请求:不能发送 POST、PUT、DELETE 等类型的 HTTP 请求。

  • 安全风险:由于 JSONP 是通过执行回调函数来处理返回的数据,存在 XSS(跨站脚本攻击)的风险。

  • 缺少错误处理:如果请求失败,JSONP 不会返回任何错误信息。

CORS

CORS 是一种安全的跨域访问机制,允许服务器指定哪些域可以访问资源,通过在服务器上设置特定的 HTTP 响应头来授权浏览器跨域访问资源。

  • 安全性高:通过 HTTP 头部控制访问,可以细粒度地指定哪些类型的请求是允许的。

  • 支持所有类型的 HTTP 请求:包括 GET、POST、PUT、DELETE 等。

  • 灵活和强大:支持发送 cookies 和其他验证信息。

  • 浏览器和服务器都需要支持:老旧的浏览器可能不支持 CORS。

  • 需要服务器配置:需要在服务器端进行配置,对服务器有一定的配置要求。

代理服务器

代理服务器作为客户端请求与服务器响应之间的中介,通过接收并转发请求来绕过浏览器的同源策略限制。

  • 完全绕过同源策略:客户端代码只与代理服务器交互,因此不受同源策略限制。

  • 可以实现复杂的跨域请求:代理服务器可以处理任何类型的请求,包括带有身份验证或使用不同方法的请求。

  • 增加服务器负担:所有请求都需要通过代理服务器转发,这可能增加服务器的负担和响应时间。

  • 需要维护额外的服务器组件:代理服务器的配置和维护需要额外的工作。

总结

如果需要简单的 GET 请求且对安全性要求不高,可以使用 JSONP。

对于需要高安全性且需要处理复杂请求的现代 Web 应用,推荐使用 CORS。

如果跨域请求非常复杂(前端同时访问多个后端服务或API)或需要绕过某些限制,或者开发中遇到 CORS 配置问题时,可以考虑使用代理服务器统一管理这些服务的接入点。

4.1. JSONP

4.1.1. 介绍

JSONP(JSON with Padding)是一种技术手段,用于解决跨域数据访问的限制。它不是一种官方的协议标准,而是开发者之间约定俗成的一种方法。JSONP 工作原理基于 web 页面可以从不同域名下加载脚本文件(如 <script> 标签引用的 JavaScript 文件)这一特性。

工作原理:

  1. 客户端:在客户端(通常是浏览器),开发者定义一个回调函数,然后在请求 URL 中指定这个回调函数的名称。

  2. 服务端:服务器接收到请求后,会将数据封装在一个函数调用里。这个函数就是客户端请求中指定的回调函数。

  3. 完成交互:当响应到达浏览器时,浏览器执行这个函数调用,由于函数已经定义在客户端,所以可以直接使用响应中的数据。

使用方式:

JSONP 主要用于 GET 请求,并且通常用于请求静态文件或动态生成的脚本,其步骤如下:

  1. 客户端设置

  • 定义一个回调函数,用于处理即将从服务器接收的数据。

  • 动态创建 <script> 标签,其 src 属性设置为 API 请求的 URL,并在 URL 中指定回调函数的名称(通常是通过查询字符串 callbackjsonp 参数)。

  1. 服务器设置

  • 服务器识别到 callback 参数,并将响应数据封装在一个函数调用中,该函数名为 callback 参数的值。

  • 生成的响应类似于:callbackName({json数据})

4.1.2. 使用

假设你想从一个提供 JSONP 支持的 API 获取数据。

4.1.2.1. 客户端代码
<script>
// 定义回调函数
function handleResponse(data) {
  console.log('Received data:', data);
}

// 动态创建<script>标签
var script = document.createElement('script');
script.src = 'https://example.com/data?callback=handleResponse'; 
document.head.appendChild(script);
</script>
  1. 定义回调函数

  1. 在创建 JSONP 请求之前,你需要定义一个全局回调函数(下例中的handleResponse),该函数将处理从服务器接收到的数据。服务器将返回一个函数调用,其参数是你需要的数据。

  2. 务必确保这个函数可以全局访问,因为返回的脚本将试图执行它。

function handleResponse(data) {
    console.log('Received data:', data);
}
  1. 动态创建<script>标签:JSONP 的工作原理是通过 <script> 标签加载跨域的脚本。你需要动态地创建一个 <script> 标签,并将其 src 属性设置为目标 URL。

var script = document.createElement('script');
script.src = 'https://example.com/data.json?callback=handleResponse'; // 注意修改 URL 和回调函数名
document.head.appendChild(script);
  1. 设置回调函数名称:在请求的 URL 中,你需要指定回调函数的名称。这通常通过 URL 的查询参数完成,如 callback=handleResponse

  2. 处理错误和超时:JSONP 不支持原生的错误处理。如果请求失败(例如,由于网络问题或服务器问题),<script> 标签不会返回任何错误信息。你可以通过设置超时来处理这种情况。

var timeout = setTimeout(function() {
    console.error('JSONP request timed out.');
    document.head.removeChild(script);
}, 10000); // 设置超时为10秒

function handleResponse(data) {
    clearTimeout(timeout); // 清除超时
    console.log('Received data:', data);
}
  1. 清理:一旦你的回调函数被执行,你可能想从 DOM 中移除 <script> 标签,尤其是如果你进行了多次 JSONP 请求的话。

function handleResponse(data) {
    document.head.removeChild(script); // 移除 script 元素
    console.log('Received data:', data);
}
4.1.2.2. 服务端代码

在服务器端接收到 JSONP 请求时,需要根据请求中指定的回调函数名称,将数据封装在函数调用中返回给客户端。假设使用 Node.js:

// Node.js Express 示例
app.get('/api/data', function(req, res) {
    var data = { message: 'Hello, JSONP!' };
    var callback = req.query.callback; // 假设回调函数的参数名为 callback
    res.type('text/javascript');
    res.send(callback + '(' + JSON.stringify(data) + ')');
});

4.2. CORS

Cross-Origin Resource Sharing (CORS) - HTTP | MDN

4.2.1. 介绍

CORS(Cross-Origin Resource Sharing)是一种机制,它使得浏览器可以请求一个不同源(域名、协议、端口号)服务器上的资源。CORS 是通过一系列 HTTP 响应头来实现的,它们定义了在当前网页的哪些外部资源可以被加载和执行。这些措施能够帮助网站保护用户免受恶意内容的侵害,同时也为合法的跨源请求提供了方便。

工作原理

当一个网页尝试进行跨域请求时(例如,通过 XMLHttpRequestfetch),浏览器会首先发送一个预检请求(pre-flight request)到目标服务器,这个请求使用 OPTIONS 方法。预检请求的目的是询问目标服务器是否愿意接受来自这个源的请求。服务器通过响应预检请求并发送适当的 CORS 响应头来表明是否接受这些请求。

如果服务器同意接受,浏览器会发送实际的请求。如果服务器拒绝接受,浏览器将阻止请求,并在控制台中生成一个错误。

重要的 CORS 响应头:

  • Access-Control-Allow-Origin: 指定了哪些域名可以访问资源。例如,Access-Control-Allow-Origin: * 允许所有域名的脚本访问该资源。

  • Access-Control-Allow-Methods: 指定了哪些 HTTP 方法可用于访问资源。例如,Access-Control-Allow-Methods: GET, POST, PUT

  • Access-Control-Allow-Headers: 在实际请求中,允许自定义的头部字段列表。

  • Access-Control-Allow-Credentials: 表示是否允许发送 cookies。只有当这个值是 true 时,浏览器才会发送 cookies。例如,Access-Control-Allow-Credentials: true

4.2.2. 使用

4.2.2.1. 服务器端配置

简单示例:Node.js 使用 Express

const express = require('express');
const cors = require('cors');

const app = express();

// 允许所有域名访问
app.use(cors());

// 允许特定域名访问
app.use(cors({
    origin: 'https://example.com'
}));

// 设置特定的多个域名访问
app.use(cors({
    origin: ['https://example.com', 'https://api.example.com']
}));

// 设置复杂 CORS 策略
app.use(cors({
    origin: 'https://example.com',
    methods: ['GET', 'POST'],
    allowedHeaders: ['Content-Type', 'Authorization'],
    credentials: true
}));

app.get('/data', (req, res) => {
    res.json({ message: 'This is a CORS-enabled route.' });
});

app.listen(3000, () => console.log('Server running on port 3000'));
4.2.2.2. 客户端使用

在客户端,你不需要做任何特别的配置来使用 CORS。浏览器会自动处理 CORS 握手过程。例如,使用 fetch 发起跨域请求:

fetch('https://api.example.com/data')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Failed to fetch data:', error));

4.2.3. 前端关注

在使用 CORS(Cross-Origin Resource Sharing)时,前端程序员通常不需要进行特别的配置来启用或使用 CORS。CORS 的实现主要是服务器端的责任,服务器必须发送适当的 CORS 响应头来允许或限制来自不同源的请求。

然而,前端程序员应该了解几个重要方面,以确保前端应用能够正确地与启用了 CORS 的服务器交互:

4.2.3.1. 了解 CORS 基本概念

前端开发者应该了解 CORS 的工作原理,包括预检请求(pre-flight requests)何时发生,哪些 HTTP 头参与 CORS,以及如何处理可能出现的 CORS 错误

4.2.3.2. 发送带凭证的请求

如果需要在请求中包含凭证,如 Cookies 或认证相关的 HTTP 头(例如 Authorization 头),前端开发者需要在 AJAX 请求中设置 withCredentials 属性为 true。默认情况下,例如使用 fetchXMLHttpRequest,不会发送 Cookies 或 HTTP 认证信息。

使用 XMLHttpRequest:

var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', true);
xhr.withCredentials = true;  // 确保跨域请求时发送Cookies
xhr.send();

使用 Fetch API:

fetch('https://api.example.com/data', {
  method: 'GET',
  credentials: 'include'  // 或 'same-origin',根据需求选择
})
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Error:', error));
4.2.3.3. 处理 CORS 错误

前端代码应当准备好处理由 CORS 策略引起的错误。通常这些错误会在浏览器控制台中显示,但是为了增强用户体验,前端程序应提供适当的错误处理逻辑。

4.2.3.4. 测试跨域请求

在开发过程中,确保跨域请求能够正确工作是非常重要的。测试不仅包括成功场景,也应包括处理来自服务器的各种 CORS 错误的情况。

4.2.3.5. 配合服务器端设置

虽然主要配置是在服务器端完成的,但前端开发者应与后端开发者紧密合作,确保服务器正确响应前端应用的跨域请求需求。这包括确定哪些域、方法和头部应该被允许,以及是否需要支持凭证。

4.3. 代理服务器

4.3.1. 介绍

在前后端通信中,使用代理服务器是一种常见的跨域解决方案。代理服务器充当客户端和远程服务器之间的中间人,它接收客户端的请求,然后转发这些请求到实际的服务器上,最后再将服务器的响应返回给客户端。这种方式有效地绕过了浏览器的同源策略限制。

使用代理服务器的优势

  1. 安全性:代理服务器可以提供附加的安全层,比如进行SSL终端或过滤恶意请求。

  2. 绕过同源限制:对于前端应用来说,所有请求都发送到同源的代理服务器,由代理服务器转发到其他域的服务器,从而绕过浏览器的同源策略。

  3. 缓存能力:代理服务器还可以缓存经常请求的资源,减少对远程服务器的请求,提高响应速度。

使用代理服务器的缺点

  1. 增加复杂性:需要设置和维护额外的代理服务器,这可能增加系统的复杂性和维护成本。

  2. 可能的性能影响:所有请求都需要通过代理服务器,可能会引入额外的网络延迟。

  3. 资源消耗:维护代理服务器需要服务器资源,对于高流量的应用,代理服务器可能成为性能瓶颈。

4.3.2. 技术实现

4.3.2.1. Nginx 作为代理服务器

Nginx 是一种流行的选择,用于配置作为反向代理服务器,处理跨域请求。下面是一个基本的 Nginx 配置示例,展示如何将前端请求代理到另一个域的 API 服务器。

server {
  listen 80;
  server_name myfrontend.com;

  location /api/ {
    # 转发请求到实际的API服务器
    proxy_pass http://api.backendserver.com;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
  }
}

在此配置中,所有到 /api/ 的请求都会被转发到 http://api.backendserver.com,并且保留原始请求信息。

4.3.2.2. Node.js 创建代理服务器

使用Node.js和Express,你可以创建一个简单的代理服务器,使用像 http-proxy-middleware 这样的中间件来转发请求。

这个Node.js服务器监听本地的3000端口,并将所有发送到 /api 的请求转发到配置的目标服务器。

const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');

const app = express();

app.use('/api', createProxyMiddleware({ 
  target: 'http://api.backendserver.com', 
  changeOrigin: true 
}));

app.listen(3000, () => {
  console.log('Proxy server is running on http://localhost:3000');
});

5. 后记 · 总结

以下介绍了关于前后端通信的多种技术方案,希望对你有所帮助。往往在实际应用中,选择适当的通信技术方案通常取决于项目的需求、现有技术栈、以及团队的熟悉程度。