引言
REST(Representational State Transfer)是一种架构风格,旨在通过简单的方式描述网络应用的行为。RESTful API 是一种基于 HTTP 协议的接口设计方式,它强调了资源的概念,通过标准的 HTTP 方法(GET、POST、PUT、DELETE 等)来对这些资源进行操作。
1. RESTful API 的基本概念
1.1 资源
在 RESTful API 中,“资源”是指可以被唯一标识并通过网络访问的任何事物。资源可以是用户、文章、订单等。每个资源都有一个唯一的 URI(Uniform Resource Identifier)来表示。
1.2 URI
URI 用于唯一标识一个资源。例如,/users/{userId} 表示一个用户的资源。其中 {userId} 是一个路径参数,代表用户 ID 的值。
1.3 HTTP 方法
- GET:获取资源。
- POST:创建资源。
- PUT:更新资源。
- DELETE:删除资源。
- PATCH:部分更新资源。
- HEAD:获取资源头部信息。
- OPTIONS:获取资源支持的方法。
2. 设计 RESTful API 的原则
设计 RESTful API 时,应当遵循以下原则:
- 无状态:每次请求都包含所有必要的信息,服务器不应该存储任何上下文。
- 统一接口:通过一致的 URL 结构和 HTTP 方法来操作资源。
- 客户端-服务器架构:客户端和服务端分离,便于各自独立发展。
- 缓存:API 应支持缓存机制,减少不必要的服务器请求。
- 分层系统:API 可以通过中间层(如代理服务器)来增强性能。
- 按需编码:客户端可以发送或接收不同格式的数据(如 JSON、XML 等)。
3. 定义 RESTful API
3.1 用户资源示例
假设我们要为一个博客应用程序设计一个 RESTful API,该 API 需要支持用户管理功能。
3.1.1 用户资源的 URI
- /users:获取所有用户列表或创建新用户。
- /users/{userId}:获取、更新或删除特定用户。
3.1.2 用户资源的 HTTP 方法
- GET /users:获取所有用户。
- POST /users:创建新用户。
- GET /users/{userId}:获取指定用户的详细信息。
- PUT /users/{userId}:更新指定用户的信息。
- DELETE /users/{userId}:删除指定用户。
- PATCH /users/{userId}:部分更新指定用户的信息。
4. 完整代码示例
结合前面的内容,下面是一个完整的 RESTful API 示例,展示了如何使用 net/http 包来构建一个支持 CRUD 操作的用户资源 API,并使用中间件来记录日志。
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"strconv"
"time"
"github.com/gorilla/mux"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
var users = map[int]User{
1: {ID: 1, Name: "Alice", Email: "alice@example.com"},
2: {ID: 2, Name: "Bob", Email: "bob@example.com"},
}
func (u User) String() string {
return fmt.Sprintf("ID: %d, Name: %s, Email: %s", u.ID, u.Name, u.Email)
}
func handleGetAllUsers(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(users)
}
func handleCreateUser(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "Failed to read request body", http.StatusBadRequest)
return
}
var newUser User
if err := json.Unmarshal(body, &newUser); err != nil {
http.Error(w, "Failed to parse request body", http.StatusBadRequest)
return
}
maxId := 0
for id := range users {
if id > maxId {
maxId = id
}
}
newUser.ID = maxId + 1
users[newUser.ID] = newUser
json.NewEncoder(w).Encode(newUser)
}
func handleGetUser(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
userId, err := strconv.Atoi(vars["userId"])
if err != nil {
http.Error(w, "Invalid user ID", http.StatusBadRequest)
return
}
user, ok := users[userId]
if !ok {
http.Error(w, "User not found", http.StatusNotFound)
return
}
json.NewEncoder(w).Encode(user)
}
func handleUpdateUser(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
userId, err := strconv.Atoi(vars["userId"])
if err != nil {
http.Error(w, "Invalid user ID", http.StatusBadRequest)
return
}
body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "Failed to read request body", http.StatusBadRequest)
return
}
var updatedUser User
if err := json.Unmarshal(body, &updatedUser); err != nil {
http.Error(w, "Failed to parse request body", http.StatusBadRequest)
return
}
if user, ok := users[userId]; ok {
user.Name = updatedUser.Name
user.Email = updatedUser.Email
users[userId] = user
json.NewEncoder(w).Encode(user)
return
}
http.Error(w, "User not found", http.StatusNotFound)
}
func handleDeleteUser(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
userId, err := strconv.Atoi(vars["userId"])
if err != nil {
http.Error(w, "Invalid user ID", http.StatusBadRequest)
return
}
if _, ok := users[userId]; ok {
delete(users, userId)
w.WriteHeader(http.StatusNoContent)
return
}
http.Error(w, "User not found", http.StatusNotFound)
}
func loggerMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
log.Printf("%s %s %s", r.Method, r.URL.Path, time.Since(start))
})
}
func main() {
router := mux.NewRouter()
router.HandleFunc("/users", handleGetAllUsers).Methods("GET")
router.HandleFunc("/users", handleCreateUser).Methods("POST")
router.HandleFunc("/users/{userId}", handleGetUser).Methods("GET")
router.HandleFunc("/users/{userId}", handleUpdateUser).Methods("PUT")
router.HandleFunc("/users/{userId}", handleDeleteUser).Methods("DELETE")
handler := loggerMiddleware(router)
log.Println("Starting server on :8080...")
http.ListenAndServe(":8080", handler)
}