[+FIT2081]: 莫那什大学课程, FIT2081 mobile application development

[+ORM]: 对象关系映射(Object-Relational Mapping),是一种将对象模型与关系数据库进行映射的技术。

[+ViewModel]: 在开发中,ViewModel 是一种设计模式,用于将视图(UI)与数据模型分离。它通常用于MVVM(Model-View-ViewModel) 架构中,使得视图可以通过数据绑定自动更新。

[+axios]: Axios 是一个基于 Promise 的 HTTP 客户端,用于浏览器和 Node.js。

[+Vue.js]: Vue.js 是一个用于构建用户界面的渐进式 JavaScript 框架。

[+Django]: Django 是一个高级 Python Web 框架,鼓励快速开发和简洁、实用的设计。

Info

这篇笔记来自我第一次做 Django + Vue.js 项目。那之前,我对 UI 的理解主要来自 FIT2081[+FIT2081] 里的 Kotlin Jetpack Compose:ViewModel[+ViewModel] 和 ORM[+ORM] 让状态好像离界面很近。Web app 给我的第一课正好相反:前端和后端并不直接共享状态,它们必须通过 HTTP 这条边界协商。

这篇文章真正有用的地方,不是说明 HTTP 存在,而是记录我第一次意识到:Web UI 和后端之间并不是像一个小型移动应用那样“自然共享状态”。它们之间隔着一个显式边界:请求、响应、状态码、header、body、序列化,以及经常会遇到的 CORS。这里保留 Vue.js 和 Django 的例子,是因为当时正是在这个具体项目里,这条边界第一次变得清楚。

理解 HTTP

HTTP 是 Web 上移动数据的基础协议。它采用客户端-服务器模型:客户端,通常是浏览器,发送请求;服务器返回响应。一个页面随后会由文本、样式、图像、脚本和其他资源组装出来。HTTP 可以承载复杂的数据,但协议本身主要规定消息如何传输,而不是数据具体意味着什么。因此,一条 HTTP 消息的结构反而相对简单。

Warning

这里主要讨论最常见的 REST-style request/response。HTTP 也可以承载 GraphQL、gRPC-web、streaming response、server-sent events 等交互方式。范围先收窄,是因为 REST API 是我当时最需要理解的第一条边界。

HTTP 请求结构

正如上文所示,HTTP 请求从客户端开始,因此我们先解释客户端的请求结构。

HTTP 请求是客户端向服务器发送的消息,请求服务器执行某个操作或返回某些数据。它通常包含以下部分:

Note
  1. 请求行: 包括 HTTP 方法(例如,GET、POST、PUT、DELETE)、请求目标(通常是 URL)和 HTTP 版本。 其中:
  • HTTP 方法:指示请求的类型,例如 GET(获取资源)、POST(提交数据)、PUT(更新资源)或 DELETE(删除资源)。
  • 请求目标:通常是服务器上的资源路径,例如在一个后端运行的进程中,有一个url /api/users/,专门用于获取用户列表。
  • HTTP 版本:例如 HTTP/1.1 或 HTTP/2。
  1. 标头: 这些是键值对,提供有关请求的附加信息,例如内容类型、身份验证令牌和缓存指令。
  2. 正文: 这是一个可选部分,包含发送到服务器的数据,通常与 POST 和 PUT 等方法一起使用,例如提交表单数据或上传文件。这一部分往往需要涉及 json 等结构化数据格式的转换。

HTTP 响应结构

一个 HTTP 响应由以下部分组成:

  1. 状态行: 包括 HTTP 版本、状态代码(例如,200、404、500)和状态消息。
  2. 标头: 与请求标头类似,这些标头提供有关响应的信息。
  3. 正文: 包含从服务器发回的数据,可以是 HTML、JSON、XML 或其他格式,但通常是 JSON 格式。

常见的 HTTP 状态代码

  • 200 OK: 请求已成功。
  • 201 Created: 请求已完成,并导致创建了一个或多个新资源。
  • 400 Bad Request: 由于明显的客户端错误,服务器无法或不会处理该请求。
  • 404 Not Found: 服务器找不到请求的资源。
  • 500 Internal Server Error: 服务器遇到了不知道如何处理的情况。

使用 Django 设置后端

Django 是一个 Python Web 框架。在这篇笔记里,后端只需要一个很小的 API endpoint:返回用户列表。

视图和 URL 映射负责隔离边界的后端一侧:

Tip

以下代码使用了 Python 的装饰器(decorator)功能,这是一种在函数或方法调用前后添加额外功能的方式。装饰器可以让代码更简洁、更易读。 详细可以阅读 Python 装饰器。简单来说,装饰器通过把其装饰的函数传入另外一个函数,并返回一个新的函数来实现功能的扩展。这是一种常见的高阶函数+高阶函数用法。

views.py

from django.http import JsonResponse # 从 Django 的 HTTP 模块导入 JsonResponse
from rest_framework.decorators import api_view # 从 Django REST Framework 导入 api_view 装饰器
@api_view(['GET'])
def user_list(request):
"""
一个以 JSON 格式返回用户列表的视图。
"""
# 此处只是一个示例数据,实际应用中可数据库查询获取json,这在许多数据库中都很容易实现。
users = [
{'id': 1, 'name': 'Alice'},
{'id': 2, 'name': 'Bob'},
]
return JsonResponse(users, safe=False)

urls.py

from django.urls import path
from .views import user_list
# 这是一个 URL 配置,将 API 端点映射到视图函数,用于与前端进行通信。
urlpatterns = [
path('api/users/', user_list, name='user-list'),
]

使用 Vue.js 设置前端

Vue.js 用来构建前端界面。前端这一侧用 axios[+axios] 向 Django endpoint 发起请求。

这个组件负责获取 JSON payload 并渲染用户列表:

UserList.vue

<template>
<div>
<h1>用户列表</h1>
<ul>
<li v-for="user in users" :key="user.id">{{ user.name }}</li>
</ul>
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
users: [],
};
},
mounted() {
this.fetchUsers();
},
methods: {
// 处理网络通信使用异步函数,如果你不了解具体原理,那么你只需要知道这么做是为了保证程序的执行顺序和结果正确
async fetchUsers() {
try {
// await 关键字用于等待异步操作完成,只能用在声明为 async 的函数中
const response = await axios.get('/api/users/');
this.users = response.data;
} catch (error) {
console.error('获取用户时出错:', error);
}
},
},
};
</script>

在此示例中,fetchUsers 方法向 后端暴露的/api/users/ 端点发送 GET 请求。当接收到数据时,它会存储在 users 数组中并显示在模板中。

Note

在实际中,前端和后端进程往往运行在不同的端口上,因此需要配置 CORS(跨源资源共享)来允许跨域请求。Django 可以通过安装 django-cors-headers 包来轻松处理这一点。

结论

这个项目让我开始认真看见一条以前被我低估的边界。

HTTP 通信不是前后端之间的魔法桥梁,而是一组显式契约:方法、状态码、数据格式、跨域规则、认证、错误处理和性能约束。一个 /api/users/ 示例已经足够看出问题的形状,但真实系统是否可靠,取决于这些契约设计得有多清楚。

现在回头看,这更像是我 API design 线索里的早期笔记。REST 是一种风格,GraphQL 和 gRPC 是另外的选择,而后来的 AI Gateway 实践其实也是同一个问题的变体:如何把边界定义得足够清楚,让不同 client、tool 和 service 不需要彼此猜测也能协作。

进一步阅读