使用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是使用内置的多路复用器创建的。我的问题是:

  1. 我的思路方向正确吗?
  2. 有没有更好的实现方式?
  3. httprouter 是否更快或更好?

任何反馈都表示感谢。


更多关于使用Golang创建包含1000个端点的REST CRUD API的实战教程也可以访问 https://www.itying.com/category-94-b0.html

9 回复

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.github.io 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 会在数据库/默认文件中查找端点/路径,然后获取查询/站点。如果数据库中没有对应的端点,那就意味着没有这个端点。

在上面的示例中,替换 categoryid 这些参数(你可以在 gorilla mux 文档中找到一些解释和示例)。

GitHub

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())
}

回答你的问题:

  1. 你的方向正确,但需要更结构化的路由处理
  2. 使用httproutergorilla/mux比标准库多路复用器更适合大量端点
  3. httprouter确实更快,它使用基数树路由,时间复杂度O(n),而标准库是O(n²)

这个实现支持:

  • 动态资源端点(如/users/products
  • 每个资源的CRUD操作
  • 并发安全的存储
  • 正确的HTTP状态码
  • JSON请求/响应处理

要扩展到1000个端点,建议:

  1. 添加数据库持久化层
  2. 实现请求限流
  3. 添加监控和日志
  4. 考虑使用gRPC提高性能
回到顶部