前端十年:从0到资深开发者的10堂必修课

第7篇:网络篇——HTTP/HTTPS、RESTful与GraphQL实战

前端应用与后端服务的通信是现代 Web 开发的基石。无论是请求数据、提交表单,还是实时同步,都离不开网络协议与接口设计。理解 HTTP 协议的核心、掌握数据交互的方式、并熟悉 RESTful 与 GraphQL 两种主流的接口风格,将帮助你构建更可靠、更高效的前端应用。


一、HTTP 协议基础

HTTP(HyperText Transfer Protocol)是 Web 上客户端与服务器之间通信的基石。虽然我们日常开发中很少直接操作原始 HTTP,但理解它的工作原理对于调试、性能优化和安全防护至关重要。

1. 请求/响应结构、常见方法、状态码

请求结构

一个典型的 HTTP 请求包含:

  • 请求行:方法、URL、协议版本(如 GET /api/users HTTP/1.1
  • 请求头:键值对,包含客户端环境、认证信息、内容类型等(如 Host: example.comAccept: application/json
  • 空行:分隔头部和主体
  • 请求主体:可选,通常在 POST/PUT 请求中包含数据
响应结构

响应包含:

  • 状态行:协议版本、状态码、状态文本(如 HTTP/1.1 200 OK
  • 响应头:类似请求头,包含服务器信息、内容类型、缓存策略等
  • 空行
  • 响应主体:返回的数据(如 HTML、JSON)
常见 HTTP 方法
方法 描述 是否幂等 是否有请求体
GET 获取资源
POST 创建资源或提交处理
PUT 完整更新资源(替换)
PATCH 部分更新资源
DELETE 删除资源 通常无
HEAD 类似 GET,但只返回响应头
OPTIONS 获取服务器支持的 HTTP 方法
常见状态码(应熟记)
  • 1xx 信息性
  • 2xx 成功
    • 200 OK:请求成功
    • 201 Created:资源创建成功(常用于 POST)
    • 204 No Content:成功但无返回内容
  • 3xx 重定向
    • 301 Moved Permanently:永久重定向
    • 302 Found:临时重定向
    • 304 Not Modified:缓存未修改(配合协商缓存)
  • 4xx 客户端错误
    • 400 Bad Request:请求格式错误
    • 401 Unauthorized:未认证
    • 403 Forbidden:禁止访问(已认证但无权限)
    • 404 Not Found:资源不存在
    • 429 Too Many Requests:请求过频繁
  • 5xx 服务器错误
    • 500 Internal Server Error:服务器内部错误
    • 502 Bad Gateway:网关错误
    • 503 Service Unavailable:服务不可用

2. HTTPS 加密过程与证书

HTTPS = HTTP + SSL/TLS,通过在 HTTP 和 TCP 之间加入一层加密协议,保证数据传输的机密性、完整性和身份验证。

TLS 握手过程(简化版)
  1. 客户端 Hello:客户端发送支持的 TLS 版本、加密套件列表、随机数等。
  2. 服务器 Hello:服务器选择加密套件、发送自己的数字证书(包含公钥)、生成另一个随机数。
  3. 证书验证:客户端验证证书的合法性(是否由受信任 CA 签发、域名匹配、是否过期等)。
  4. 密钥交换:客户端生成一个预主密钥,用服务器的公钥加密后发送。服务器用私钥解密得到预主密钥。
  5. 生成会话密钥:双方使用两个随机数和预主密钥计算出对称加密的会话密钥。
  6. Finished 消息:双方发送加密的“Finished”消息,握手完成,之后通信使用会话密钥对称加密。
数字证书与 CA
  • 数字证书由受信任的证书颁发机构(CA)签发,包含:域名、公钥、颁发者、有效期、签名等。
  • 浏览器内置了根 CA 证书,用于验证服务器证书的签名链。
前端开发者需要了解什么?
  • 混合内容(Mixed Content):HTTPS 页面中加载 HTTP 资源会被浏览器阻止或警告。
  • 如何配置本地开发环境使用 HTTPS(如使用 mkcert 生成自签名证书,或利用 Vite/Webpack 的 devServer HTTPS 选项)。

二、数据交互方式

在前端,我们通过 JavaScript 提供的 API 与服务器通信。从传统的 XMLHttpRequest 到现代的 Fetch,再到功能更丰富的 Axios,了解它们的差异和适用场景能让我们写出更健壮的代码。

1. XMLHttpRequest 与 Fetch API

XMLHttpRequest(XHR)

XHR 是早期 AJAX 的核心,虽然现在有更现代的替代,但在兼容旧浏览器或处理上传进度等场景仍有使用。

const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/users', true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onreadystatechange = function() {
  if (xhr.readyState === 4) { // 请求完成
    if (xhr.status >= 200 && xhr.status < 300) {
      console.log(JSON.parse(xhr.responseText));
    } else {
      console.error('请求失败', xhr.status);
    }
  }
};
xhr.send();

// 上传进度
xhr.upload.onprogress = (e) => {
  if (e.lengthComputable) {
    const percent = (e.loaded / e.total) * 100;
    console.log(`上传进度:${percent}%`);
  }
};
Fetch API

Fetch 是 ES6 引入的基于 Promise 的现代替代方案,语法简洁,支持流式处理。

// GET 请求
fetch('/api/users')
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return response.json();
  })
  .then(data => console.log(data))
  .catch(error => console.error('Fetch 错误:', error));

// POST 请求
fetch('/api/users', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: 'Alice' })
});

Fetch 的注意事项

  • 只有网络错误或无法连接时才会 reject,HTTP 错误状态(如 404、500)不会 reject,需要手动检查 response.ok
  • 默认不携带 cookie,需要设置 credentials: 'include'
  • 不支持请求超时控制(可借助 AbortController 实现)。
  • 没有内置上传进度监听(可通过 ReadableStream 自行实现,较复杂)。

2. Axios 封装与拦截器

Axios 是一个基于 Promise 的 HTTP 客户端,同时支持浏览器和 Node.js,提供了比 Fetch 更丰富的功能:请求/响应拦截、取消请求、自动转换 JSON、客户端防御 XSRF 等。

安装

npm install axios

基本用法

import axios from 'axios';

// GET
axios.get('/api/users')
  .then(response => console.log(response.data))
  .catch(error => console.error(error));

// POST
axios.post('/api/users', { name: 'Alice' }, {
  headers: { 'Authorization': 'Bearer token' }
});
拦截器

拦截器可以在请求发送前或响应返回后统一处理,非常适合添加 token、处理错误、日志记录等。

// 请求拦截器
axios.interceptors.request.use(
  config => {
    // 在发送请求前做些什么(如添加 token)
    const token = localStorage.getItem('token');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  error => Promise.reject(error)
);

// 响应拦截器
axios.interceptors.response.use(
  response => response,
  error => {
    // 对响应错误做点什么(如统一处理 401 未认证)
    if (error.response && error.response.status === 401) {
      // 跳转到登录页
      window.location.href = '/login';
    }
    return Promise.reject(error);
  }
);
创建实例与封装

通常我们会为不同后端服务创建独立的 Axios 实例,并统一配置 baseURL、超时等。

// request.js
import axios from 'axios';

const request = axios.create({
  baseURL: process.env.VUE_APP_API_BASE || '/api',
  timeout: 10000,
});

// 请求拦截器添加 token
request.interceptors.request.use(...);
// 响应拦截器统一处理错误
request.interceptors.response.use(...);

export default request;

// 使用
import request from './request';
request.get('/users');

Axios 还支持取消请求(CancelTokenAbortController)、并发请求(axios.all)、文件上传进度(onUploadProgress)等。


三、RESTful 与 GraphQL

接口设计风格直接影响前后端协作效率和前端代码的复杂度。RESTful 是目前最普遍的 API 设计风格,而 GraphQL 作为新兴的查询语言,提供了更灵活的数据获取方式。

1. REST 设计最佳实践

REST(Representational State Transfer)是一种基于资源的架构风格,核心原则包括:

  • 使用名词表示资源:URL 应该是名词复数形式,如 /users/orders/123
  • 利用 HTTP 方法表达操作:GET 获取、POST 创建、PUT 整体更新、PATCH 部分更新、DELETE 删除。
  • 无状态:每个请求都应包含所有必要信息,服务器不保存客户端状态(通过 token 等方式传递认证信息)。
  • 使用 HTTP 状态码表示结果
  • 资源关联:通过嵌套表示关系,如 /users/123/orders 表示用户的订单。

最佳实践示例

操作 请求方法 URL 状态码
获取所有用户 GET /api/users 200
获取单个用户 GET /api/users/{id} 200/404
创建用户 POST /api/users 201
更新用户 PUT /api/users/{id} 200/204
部分更新用户 PATCH /api/users/{id} 200
删除用户 DELETE /api/users/{id} 204

分页、过滤、排序:通过查询参数实现,如 GET /api/users?page=2&limit=20&sort=name&role=admin

版本管理:可以在 URL 中包含版本号,如 /api/v1/users,或通过 Accept 头(如 Accept: application/vnd.myapp.v1+json)。

HATEOAS(可选):响应中包含相关操作的链接,使 API 自描述。

2. GraphQL 基本查询与变更

GraphQL 由 Facebook 开发,允许客户端精确指定需要的数据,避免了 REST 中的过度获取或获取不足。它通过一个端点处理所有请求,并使用强类型 Schema 定义数据模型。

核心概念
  • Query:查询数据(相当于 GET)
  • Mutation:修改数据(相当于 POST/PUT/DELETE)
  • Subscription:实时订阅(WebSocket)
  • Schema:定义类型和字段,例如:
    type User {
      id: ID!
      name: String!
      email: String!
      posts: [Post!]!
    }
    type Post {
      id: ID!
      title: String!
      content: String!
    }
    type Query {
      user(id: ID!): User
      users: [User!]!
    }
    type Mutation {
      createUser(name: String!, email: String!): User!
    }
    
前端使用(以 Apollo Client 为例)

安装

npm install @apollo/client graphql

初始化客户端

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

const client = new ApolloClient({
  uri: 'https://api.example.com/graphql',
  cache: new InMemoryCache(),
});

执行查询

client.query({
  query: gql`
    query GetUser($id: ID!) {
      user(id: $id) {
        name
        email
        posts {
          title
        }
      }
    }
  `,
  variables: { id: '123' },
}).then(result => console.log(result.data));

在 React 中使用(Hooks)

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

const GET_USER = gql`
  query GetUser($id: ID!) {
    user(id: $id) {
      name
      email
    }
  }
`;

function User({ userId }) {
  const { loading, error, data } = useQuery(GET_USER, {
    variables: { id: userId },
  });

  if (loading) return <p>加载中...</p>;
  if (error) return <p>错误: {error.message}</p>;

  return <div>{data.user.name} - {data.user.email}</div>;
}

变更(Mutation)

const CREATE_USER = gql`
  mutation CreateUser($name: String!, $email: String!) {
    createUser(name: $name, email: $email) {
      id
      name
      email
    }
  }
`;

function AddUser() {
  const [createUser, { data, loading, error }] = useMutation(CREATE_USER);

  const handleSubmit = (name, email) => {
    createUser({ variables: { name, email } });
  };
  // ...
}

优点

  • 客户端按需获取,减少网络传输。
  • 强类型 Schema,自文档化。
  • 单一端点,便于聚合多个数据源。

缺点

  • 学习曲线较陡。
  • 服务端实现复杂度增加。
  • 缓存机制需要额外处理。
REST vs GraphQL 选型
场景 REST GraphQL
简单、资源导向的 API 非常适合,清晰直观 可能过度设计
客户端需求多变,数据聚合复杂 可能需要多次请求或自定义端点 一次查询获取所有需要的数据
实时订阅 WebSocket 单独实现 原生支持 Subscription
缓存策略 利用 HTTP 缓存 需要客户端缓存(如 Apollo)
团队熟悉度 广泛熟悉 需要学习

总结

本篇深入探讨了前端网络交互的核心知识:

  • HTTP 基础:掌握了请求/响应结构、常见方法和状态码,理解了 HTTPS 加密过程与证书的作用。
  • 数据交互方式:对比了 XMLHttpRequest、Fetch 和 Axios,学会了使用拦截器统一处理请求,并封装可复用的请求模块。
  • RESTful 与 GraphQL:了解了 REST 设计的最佳实践,以及 GraphQL 的基本查询与变更,能够根据项目需求选择合适的接口风格。

网络通信是前后端协作的桥梁。掌握这些内容后,你能更从容地处理接口对接、错误处理和性能优化。下一篇我们将进入 性能篇,从加载、渲染到运行时全方位优化前端性能,敬请期待!


思考题

  1. 为什么说 HTTPS 不仅是为了加密,还保证了身份验证和完整性?
  2. 在使用 Fetch 时,如何实现请求超时控制?
  3. 如果后端 REST API 返回的数据结构与前端需要的不完全匹配,你有几种处理方式?
  4. GraphQL 如何避免 N+1 查询问题?在服务端有哪些优化策略?

欢迎在评论区分享你的见解和疑问,一起讨论进步!

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐