使用Golang创建包含1000个端点的REST CRUD API
使用Golang创建包含1000个端点的REST CRUD API 我之前问过这个问题。得到的回答非常有帮助。基于这些回答和我遇到的挑战,我创建了一个API原型。无论我是否走在正确的轨道上,或者是否需要从头开始,我都非常感谢任何反馈。这个原型只完成一个简单的任务:基于CRUD操作提供一个简单的字符串。
这是在线链接:尝试 #1 动态端点
package main
import (
"encoding/json"
"net/http"
"strings"
)
func main() {
http.HandleFunc("/", handler)
http.Handle("/favicon.ico", http.NotFoundHandler())
http.ListenAndServe(":9999", nil)
}
func handler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET,HEAD,OPTIONS,POST,PUT,CREATE,DELETE")
w.Header().Set("Access-Control-Allow-Headers", "*")
w.Header().Set("Content-Type", "application/json")
switch r.Method {
case "DELETE":
Delete(w, r)
case "POST":
Create(w, r)
case "PUT":
Update(w, r)
default: //GET
Select(w, r)
}
}
func Select(w http.ResponseWriter, r *http.Request) {
route, val := getpath(r)
data := map[string]string{"method": "GET", "route": route, "value": val}
json.NewEncoder(w).Encode(data)
}
func Update(w http.ResponseWriter, r *http.Request) {
route, val := getpath(r)
data := map[string]string{"method": "PUT", "route": route, "value": val}
json.NewEncoder(w).Encode(data)
}
func Create(w http.ResponseWriter, r *http.Request) {
route, val := getpath(r)
data := map[string]string{"method": "POST", "route": route, "value": val}
json.NewEncoder(w).Encode(data)
}
func Delete(w http.ResponseWriter, r *http.Request) {
route, val := getpath(r)
data := map[string]string{"method": "DELETE", "route": route, "value": val}
json.NewEncoder(w).Encode(data)
}
func getpath(r *http.Request) (string, string) {
path := strings.Split(r.URL.String(), "/")
switch len(path) {
case 3:
return path[1], path[2]
case 2:
return path[1], ""
default:
return "", ""
}
}
这个API是使用内置的多路复用器创建的。我的问题是:
- 我的思路方向正确吗?
- 有没有更好的实现方式?
- httprouter 是否更快或更好?
任何反馈都表示感谢。
更多关于使用Golang创建包含1000个端点的REST CRUD API的实战教程也可以访问 https://www.itying.com/category-94-b0.html
geosoft1:
将路由中的参数替换为表示其他路由的内容。
那么具体该如何操作?我该如何替换 /articles/?
更多关于使用Golang创建包含1000个端点的REST CRUD API的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
geosoft1:
你可以在 gorilla mux 的文档中找到一些解释和示例。
是的,我花了几天时间研究这个问题,但我完全不明白该如何使用参数来进行路由。
geosoft1:
一种选择可能是使用 gorilla 工具包。
我尝试过,但无法使端点动态化。有什么建议如何使用 Gorilla Mux 来实现这一点吗?
是的,我猜完全动态可能不行(不过这个想法挺有意思的 :thinking:),但在某些限制下,你可以使用能接受一些可变参数的固定路由来尝试,虽然不那么符合惯用法,但……
Sibert:
有没有更好的方法?
一个选择可以是使用 gorilla 工具包 🤷♂️
Gorilla, the golang web toolkit
Gorilla 是 Go 编程语言的 Web 工具包
是的,但也可以像这样(来自 gorilla mux 文档)
s.HandleFunc("/articles/{category}/{id:[0-9]+
用表示其他路由的内容替换路由中的参数。我使用过几次这种技术。缺点是路由并非真正动态,但在参数限制内具有一定的动态性。
注:就 gorilla 工具包而言,这种技术是惯用的。
(不过这个想法挺有意思 🤔)但在某些限制下,你可以尝试使用固定的路由。
像这样吗?
https://api2.go4webdev.org/test/ https://api2.go4webdev.org/whateveryouwant
我注意到 Nginx 处理动态端点的方式和我正在考虑的方式几乎一样。
Nginx 会在数据库/默认文件中查找端点/路径,然后获取查询/站点。如果数据库中没有对应的端点,那就意味着没有这个端点。
在上面的示例中,替换 category 或 id 这些参数(你可以在 gorilla mux 文档中找到一些解释和示例)。
GitHub - gorilla/mux: 一个强大的 HTTP 路由器和 URL 匹配器,用于构建 Go…
一个强大的 HTTP 路由器和 URL 匹配器,用于构建 Go Web 服务器 🦍 - GitHub - gorilla/mux: 一个强大的 HTTP 路由器和 URL 匹配器,用于构建 Go Web 服务器 🦍
你的原型展示了正确的思路,但需要改进以支持1000个端点。以下是基于你代码的增强版本,使用httprouter实现动态路由和内存存储:
package main
import (
"encoding/json"
"net/http"
"sync"
"github.com/julienschmidt/httprouter"
)
type Resource struct {
Value string `json:"value"`
}
var (
resources = make(map[string]Resource)
mu sync.RWMutex
)
func main() {
router := httprouter.New()
// 动态端点处理
router.GET("/:resource", GetResource)
router.GET("/:resource/:id", GetResourceByID)
router.POST("/:resource", CreateResource)
router.PUT("/:resource/:id", UpdateResource)
router.DELETE("/:resource/:id", DeleteResource)
router.OPTIONS("/*path", HandleCORS)
http.ListenAndServe(":9999", router)
}
func HandleCORS(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "*")
w.WriteHeader(http.StatusOK)
}
func GetResource(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
mu.RLock()
defer mu.RUnlock()
resourceName := ps.ByName("resource")
response := make(map[string]Resource)
for key, resource := range resources {
if strings.HasPrefix(key, resourceName+"/") {
response[key] = resource
}
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
func GetResourceByID(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
mu.RLock()
defer mu.RUnlock()
key := ps.ByName("resource") + "/" + ps.ByName("id")
if resource, exists := resources[key]; exists {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resource)
return
}
http.Error(w, "Resource not found", http.StatusNotFound)
}
func CreateResource(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
var resource Resource
if err := json.NewDecoder(r.Body).Decode(&resource); err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
return
}
mu.Lock()
key := ps.ByName("resource") + "/" + generateID()
resources[key] = resource
mu.Unlock()
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(map[string]string{"id": key, "value": resource.Value})
}
func UpdateResource(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
var resource Resource
if err := json.NewDecoder(r.Body).Decode(&resource); err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
return
}
key := ps.ByName("resource") + "/" + ps.ByName("id")
mu.Lock()
defer mu.Unlock()
if _, exists := resources[key]; !exists {
http.Error(w, "Resource not found", http.StatusNotFound)
return
}
resources[key] = resource
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resource)
}
func DeleteResource(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
key := ps.ByName("resource") + "/" + ps.ByName("id")
mu.Lock()
defer mu.Unlock()
if _, exists := resources[key]; !exists {
http.Error(w, "Resource not found", http.StatusNotFound)
return
}
delete(resources, key)
w.WriteHeader(http.StatusNoContent)
}
func generateID() string {
return fmt.Sprintf("%d", time.Now().UnixNano())
}
回答你的问题:
- 你的方向正确,但需要更结构化的路由处理
- 使用
httprouter或gorilla/mux比标准库多路复用器更适合大量端点 httprouter确实更快,它使用基数树路由,时间复杂度O(n),而标准库是O(n²)
这个实现支持:
- 动态资源端点(如
/users、/products) - 每个资源的CRUD操作
- 并发安全的存储
- 正确的HTTP状态码
- JSON请求/响应处理
要扩展到1000个端点,建议:
- 添加数据库持久化层
- 实现请求限流
- 添加监控和日志
- 考虑使用gRPC提高性能

