路由方法 #

一、HTTP方法概述 #

HTTP方法定义了对资源的操作类型。Echo支持所有标准HTTP方法。

1.1 常用HTTP方法 #

方法 用途 是否幂等 是否安全
GET 获取资源
POST 创建资源
PUT 更新资源(完整)
PATCH 更新资源(部分)
DELETE 删除资源
HEAD 获取响应头
OPTIONS 获取支持的方法

1.2 幂等性与安全性 #

text
┌─────────────────────────────────────────────────────────┐
│                  HTTP方法分类                            │
├─────────────────────────────────────────────────────────┤
│                                                         │
│   安全方法:GET、HEAD、OPTIONS                           │
│   ├── 多次调用不会修改资源                               │
│   └── 可以被缓存                                        │
│                                                         │
│   幂等方法:GET、HEAD、OPTIONS、PUT、DELETE              │
│   ├── 多次调用结果相同                                   │
│   └── 可以安全重试                                      │
│                                                         │
│   非幂等方法:POST、PATCH                                │
│   ├── 每次调用可能产生不同结果                           │
│   └── 重试需要谨慎                                      │
│                                                         │
└─────────────────────────────────────────────────────────┘

二、GET方法 #

2.1 基本用法 #

GET用于获取资源,参数通过URL传递。

go
e.GET("/users", func(c echo.Context) error {
    users := []User{
        {ID: 1, Name: "张三"},
        {ID: 2, Name: "李四"},
    }
    return c.JSON(http.StatusOK, users)
})

2.2 获取单个资源 #

go
e.GET("/users/:id", func(c echo.Context) error {
    id := c.Param("id")
    user := User{ID: id, Name: "张三"}
    return c.JSON(http.StatusOK, user)
})

2.3 查询参数 #

go
e.GET("/users", func(c echo.Context) error {
    page := c.QueryParam("page")
    size := c.QueryParam("size")
    keyword := c.QueryParam("keyword")
    
    return c.JSON(http.StatusOK, map[string]string{
        "page":    page,
        "size":    size,
        "keyword": keyword,
    })
})

测试:

bash
curl "http://localhost:8080/users?page=1&size=10&keyword=张"

2.4 默认值处理 #

go
e.GET("/users", func(c echo.Context) error {
    page := c.QueryParam("page")
    if page == "" {
        page = "1"
    }
    
    size := c.QueryParam("size")
    if size == "" {
        size = "10"
    }
    
    return c.JSON(http.StatusOK, map[string]string{
        "page": page,
        "size": size,
    })
})

2.5 绑定查询参数 #

go
type QueryParams struct {
    Page    int    `query:"page"`
    Size    int    `query:"size"`
    Keyword string `query:"keyword"`
}

e.GET("/users", func(c echo.Context) error {
    q := new(QueryParams)
    if err := c.Bind(q); err != nil {
        return err
    }
    
    return c.JSON(http.StatusOK, q)
})

三、POST方法 #

3.1 创建资源 #

POST用于创建新资源,数据通过请求体传递。

go
type User struct {
    Name  string `json:"name"`
    Email string `json:"email"`
}

e.POST("/users", func(c echo.Context) error {
    u := new(User)
    if err := c.Bind(u); err != nil {
        return echo.NewHTTPError(http.StatusBadRequest, err.Error())
    }
    
    u.ID = generateID()
    
    return c.JSON(http.StatusCreated, u)
})

测试:

bash
curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"name":"张三","email":"zhangsan@example.com"}' \
  http://localhost:8080/users

3.2 表单数据 #

go
e.POST("/login", func(c echo.Context) error {
    username := c.FormValue("username")
    password := c.FormValue("password")
    
    return c.JSON(http.StatusOK, map[string]string{
        "username": username,
        "status":   "success",
    })
})

测试:

bash
curl -X POST \
  -d "username=admin&password=123456" \
  http://localhost:8080/login

3.3 文件上传 #

go
e.POST("/upload", func(c echo.Context) error {
    file, err := c.FormFile("file")
    if err != nil {
        return err
    }
    
    src, err := file.Open()
    if err != nil {
        return err
    }
    defer src.Close()
    
    dst, err := os.Create(file.Filename)
    if err != nil {
        return err
    }
    defer dst.Close()
    
    if _, err = io.Copy(dst, src); err != nil {
        return err
    }
    
    return c.JSON(http.StatusOK, map[string]string{
        "filename": file.Filename,
        "size":     fmt.Sprintf("%d", file.Size),
    })
})

四、PUT方法 #

4.1 完整更新 #

PUT用于完整更新资源,需要提供所有字段。

go
e.PUT("/users/:id", func(c echo.Context) error {
    id := c.Param("id")
    
    u := new(User)
    if err := c.Bind(u); err != nil {
        return err
    }
    
    u.ID = id
    
    return c.JSON(http.StatusOK, u)
})

测试:

bash
curl -X PUT \
  -H "Content-Type: application/json" \
  -d '{"name":"李四","email":"lisi@example.com"}' \
  http://localhost:8080/users/1

4.2 PUT vs POST #

特性 POST PUT
用途 创建资源 更新资源
幂等性
URL 资源集合 具体资源
响应状态码 201 Created 200 OK

五、PATCH方法 #

5.1 部分更新 #

PATCH用于部分更新资源,只需提供修改的字段。

go
type UserUpdate struct {
    Name  *string `json:"name,omitempty"`
    Email *string `json:"email,omitempty"`
}

e.PATCH("/users/:id", func(c echo.Context) error {
    id := c.Param("id")
    
    u := new(UserUpdate)
    if err := c.Bind(u); err != nil {
        return err
    }
    
    existingUser := getUserByID(id)
    
    if u.Name != nil {
        existingUser.Name = *u.Name
    }
    if u.Email != nil {
        existingUser.Email = *u.Email
    }
    
    return c.JSON(http.StatusOK, existingUser)
})

测试:

bash
curl -X PATCH \
  -H "Content-Type: application/json" \
  -d '{"name":"王五"}' \
  http://localhost:8080/users/1

5.2 PUT vs PATCH #

特性 PUT PATCH
更新方式 完整替换 部分更新
需要字段 全部字段 仅修改字段
幂等性

六、DELETE方法 #

6.1 删除资源 #

go
e.DELETE("/users/:id", func(c echo.Context) error {
    id := c.Param("id")
    
    if err := deleteUser(id); err != nil {
        return echo.NewHTTPError(http.StatusNotFound, "用户不存在")
    }
    
    return c.NoContent(http.StatusNoContent)
})

测试:

bash
curl -X DELETE http://localhost:8080/users/1

6.2 响应状态码 #

状态码 说明
204 No Content 删除成功,无返回内容
200 OK 删除成功,返回信息
404 Not Found 资源不存在

七、HEAD方法 #

7.1 获取响应头 #

HEAD与GET类似,但只返回响应头,不返回响应体。

go
e.HEAD("/users/:id", func(c echo.Context) error {
    id := c.Param("id")
    
    c.Response().Header().Set("Content-Type", "application/json")
    c.Response().Header().Set("X-Resource-Id", id)
    
    return c.NoContent(http.StatusOK)
})

测试:

bash
curl -I http://localhost:8080/users/1

7.2 用途 #

  • 检查资源是否存在
  • 获取资源元信息
  • 检查资源是否修改

八、OPTIONS方法 #

8.1 获取支持的方法 #

go
e.OPTIONS("/users", func(c echo.Context) error {
    c.Response().Header().Set("Allow", "GET, POST, OPTIONS")
    return c.NoContent(http.StatusOK)
})

8.2 CORS预检 #

Echo内置CORS中间件会自动处理OPTIONS请求:

go
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
    AllowOrigins: []string{"*"},
    AllowMethods: []string{echo.GET, echo.POST, echo.PUT, echo.DELETE},
}))

九、Any和Match #

9.1 Any方法 #

注册所有HTTP方法:

go
e.Any("/api", func(c echo.Context) error {
    method := c.Request().Method
    return c.JSON(http.StatusOK, map[string]string{
        "method": method,
    })
})

9.2 Match方法 #

注册指定方法:

go
e.Match([]string{http.MethodGet, http.MethodPost}, "/api", func(c echo.Context) error {
    return c.String(http.StatusOK, "GET or POST")
})

十、RESTful API设计 #

10.1 RESTful规范 #

go
func setupRoutes(e *echo.Echo) {
    e.GET("/users", listUsers)
    e.GET("/users/:id", getUser)
    e.POST("/users", createUser)
    e.PUT("/users/:id", updateUser)
    e.PATCH("/users/:id", patchUser)
    e.DELETE("/users/:id", deleteUser)
    
    e.GET("/users/:id/posts", listUserPosts)
    e.GET("/users/:id/posts/:postId", getUserPost)
    e.POST("/users/:id/posts", createUserPost)
}

10.2 状态码规范 #

操作 成功状态码 失败状态码
GET 200 OK 404 Not Found
POST 201 Created 400 Bad Request
PUT 200 OK 400, 404
PATCH 200 OK 400, 404
DELETE 204 No Content 404 Not Found

10.3 完整RESTful示例 #

go
package main

import (
    "net/http"
    "sync"
    "github.com/labstack/echo/v4"
)

type User struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

var (
    users = make(map[int]*User)
    mu    sync.RWMutex
    nextID = 1
)

func main() {
    e := echo.New()
    
    e.GET("/users", listUsers)
    e.GET("/users/:id", getUser)
    e.POST("/users", createUser)
    e.PUT("/users/:id", updateUser)
    e.PATCH("/users/:id", patchUser)
    e.DELETE("/users/:id", deleteUser)
    
    e.Start(":8080")
}

func listUsers(c echo.Context) error {
    mu.RLock()
    defer mu.RUnlock()
    
    result := make([]*User, 0, len(users))
    for _, u := range users {
        result = append(result, u)
    }
    
    return c.JSON(http.StatusOK, result)
}

func getUser(c echo.Context) error {
    id := c.Param("id")
    
    mu.RLock()
    defer mu.RUnlock()
    
    var userID int
    fmt.Sscanf(id, "%d", &userID)
    
    user, ok := users[userID]
    if !ok {
        return echo.NewHTTPError(http.StatusNotFound, "用户不存在")
    }
    
    return c.JSON(http.StatusOK, user)
}

func createUser(c echo.Context) error {
    u := new(User)
    if err := c.Bind(u); err != nil {
        return echo.NewHTTPError(http.StatusBadRequest, err.Error())
    }
    
    mu.Lock()
    defer mu.Unlock()
    
    u.ID = nextID
    nextID++
    users[u.ID] = u
    
    return c.JSON(http.StatusCreated, u)
}

func updateUser(c echo.Context) error {
    id := c.Param("id")
    
    var userID int
    fmt.Sscanf(id, "%d", &userID)
    
    u := new(User)
    if err := c.Bind(u); err != nil {
        return echo.NewHTTPError(http.StatusBadRequest, err.Error())
    }
    
    mu.Lock()
    defer mu.Unlock()
    
    if _, ok := users[userID]; !ok {
        return echo.NewHTTPError(http.StatusNotFound, "用户不存在")
    }
    
    u.ID = userID
    users[userID] = u
    
    return c.JSON(http.StatusOK, u)
}

func patchUser(c echo.Context) error {
    id := c.Param("id")
    
    var userID int
    fmt.Sscanf(id, "%d", &userID)
    
    patch := make(map[string]interface{})
    if err := c.Bind(&patch); err != nil {
        return echo.NewHTTPError(http.StatusBadRequest, err.Error())
    }
    
    mu.Lock()
    defer mu.Unlock()
    
    user, ok := users[userID]
    if !ok {
        return echo.NewHTTPError(http.StatusNotFound, "用户不存在")
    }
    
    if name, ok := patch["name"].(string); ok {
        user.Name = name
    }
    if email, ok := patch["email"].(string); ok {
        user.Email = email
    }
    
    return c.JSON(http.StatusOK, user)
}

func deleteUser(c echo.Context) error {
    id := c.Param("id")
    
    var userID int
    fmt.Sscanf(id, "%d", &userID)
    
    mu.Lock()
    defer mu.Unlock()
    
    if _, ok := users[userID]; !ok {
        return echo.NewHTTPError(http.StatusNotFound, "用户不存在")
    }
    
    delete(users, userID)
    
    return c.NoContent(http.StatusNoContent)
}

十一、总结 #

路由方法要点:

方法 用途 示例
GET 获取资源 e.GET("/users", listUsers)
POST 创建资源 e.POST("/users", createUser)
PUT 完整更新 e.PUT("/users/:id", updateUser)
PATCH 部分更新 e.PATCH("/users/:id", patchUser)
DELETE 删除资源 e.DELETE("/users/:id", deleteUser)
HEAD 获取头信息 e.HEAD("/users/:id", headUser)
OPTIONS 获取方法列表 e.OPTIONS("/users", optionsUser)

准备好学习路由参数了吗?让我们进入下一章!

最后更新:2026-03-28