9.1.1 RESTful架构风格
RESTful 架构风格介绍
REST(Representational State Transfer)是一种用于设计网络应用的架构风格,主要基于 HTTP 协议。其核心思想是通过一组无状态的操作来访问和操纵资源,这些操作通常与 HTTP 方法直接对应。
REST 的关键原则包括:
-
资源导向:
- 每个对象或服务均被视为一个资源,使用 URI 进行唯一标识。
-
无状态性:
- 每个请求应包含服务器处理该请求所需的所有信息,服务器不会在请求之间保存任何客户端上下文。
-
统一接口:
- 标准化的 CRUD 操作为:GET(读取)、POST(创建)、PUT(更新)、DELETE(删除)。
-
分层系统:
- 允许在客户端和服务器之间使用中间层来提升安全性、可扩展性等。
-
可缓存性:
- 响应应该明确指示是否可以缓存,以提高性能。
使用场景
RESTful 架构适用于以下场景:
- Web 应用后端:提供 API 接口供前端调用。
- 移动应用通信:提供轻量级 API 接口给移动设备。
- 微服务架构:REST API 可作为微服务之间的通信协议。
- 物联网设备交互:轻量级传输协议适合带宽受限的设备。
RESTful请求处理流程图
下面是一个 RESTful 请求处理的典型流程图:
+-----------------+
| Client |
+-------+---------+
|
| HTTP Request (GET/POST/PUT/DELETE)
v
+-------+---------+
| REST API |
+-------+---------+
|
| Perform operation on resource
v
+-------+---------+
| Database |
+-----------------+
|
| Fetch/Update/Delete resource
v
+-----------------+
| REST API |
+-----------------+
|
| HTTP Response (Data/Status Code)
v
+-----------------+
| Client |
+-----------------+
9.1.2 路由与URL设计
在构建 RESTful API 时,路由和 URL 设计是非常重要的部分。良好的路由和 URL 设计可以使 API 更加直观、易用,并且符合 REST 的资源导向原则。
设计原则
-
资源导向:
- 使用名词表示资源,并采用复数形式。例如:
/users
,/products
。
- 使用名词表示资源,并采用复数形式。例如:
-
HTTP 方法语义化:
- 使用 HTTP 动词来表示操作:GET(读取)、POST(创建)、PUT/PATCH(更新)、DELETE(删除)。
-
层次关系:
- URL 可以表达资源之间的层次关系。例如:
/users/{userId}/orders
表示某个用户的订单集合。
- URL 可以表达资源之间的层次关系。例如:
-
过滤和分页:
- 通过查询参数实现,如:
/products?category=electronics&limit=10&page=2
。
- 通过查询参数实现,如:
-
状态码使用:
- 合理使用 HTTP 状态码来表示操作结果,例如:200 OK, 201 Created, 400 Bad Request, 404 Not Found, 500 Internal Server Error 等。
使用场景
- 用户管理系统:提供对用户数据的增删查改。
- 电子商务平台:管理产品、订单、客户等资源。
- 社交网络应用:处理用户、帖子、评论、好友关系等数据。
net/http 代码示例
以下是一个使用 Golang 原生 net/http
包实现简单 RESTful API 的示例:
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"strconv"
)
// User represents a user structure
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Age int `json:"age"`
}
var users = map[int]User{
}
var nextID = 1
func getUsers(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(users)
}
func getUserByID(w http.ResponseWriter, r *http.Request) {
idStr := r.URL.Path[len("/users/"):]
id, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, "Invalid ID", http.StatusBadRequest)
return
}
user, exists := users[id]
if !exists {
http.Error(w, "User not found", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
}
func createUser(w http.ResponseWriter, r *http.Request) {
var newUser User
err := json.NewDecoder(r.Body).Decode(&newUser)
if err != nil {
http.Error(w, "Invalid input", http.StatusBadRequest)
return
}
newUser.ID = nextID
nextID++
users[newUser.ID] = newUser
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(newUser)
}
func updateUser(w http.ResponseWriter, r *http.Request) {
idStr := r.URL.Path[len