golang轻量快速基于net/context的HTTP路由插件chi的使用

Golang轻量快速基于net/context的HTTP路由插件chi的使用

chi是一个轻量级、符合Go语言习惯且可组合的路由器,用于构建Go HTTP服务。它特别适合帮助您编写大型REST API服务,随着项目增长和变化而保持可维护性。

特性

  • 轻量级 - 核心路由器代码约1000行
  • 快速 - 性能优异
  • 100%兼容net/http - 可以使用任何与net/http兼容的中间件
  • 模块化/可组合API设计 - 支持中间件、路由组和子路由器挂载
  • 上下文控制 - 基于新的context包,提供值链、取消和超时
  • 健壮 - 已在Pressly、Cloudflare、Heroku、99Designs等公司生产环境使用

安装

go get -u github.com/go-chi/chi/v5

基本示例

package main

import (
	"net/http"

	"github.com/go-chi/chi/v5"
	"github.com/go-chi/chi/v5/middleware"
)

func main() {
	r := chi.NewRouter()
	r.Use(middleware.Logger)
	r.Get("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("welcome"))
	})
	http.ListenAndServe(":3000", r)
}

REST API完整示例

import (
	"context"
	"github.com/go-chi/chi/v5"
	"github.com/go-chi/chi/v5/middleware"
)

func main() {
	r := chi.NewRouter()

	// 基础中间件栈
	r.Use(middleware.RequestID)
	r.Use(middleware.RealIP)
	r.Use(middleware.Logger)
	r.Use(middleware.Recoverer)
	r.Use(middleware.Timeout(60 * time.Second))

	r.Get("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("hi"))
	})

	// 文章资源的路由
	r.Route("/articles", func(r chi.Router) {
		r.With(paginate).Get("/", listArticles)                          // GET /articles
		r.With(paginate).Get("/{month}-{day}-{year}", listArticlesByDate) // GET /articles/01-16-2017

		r.Post("/", createArticle)                                       // POST /articles
		r.Get("/search", searchArticles)                                 // GET /articles/search

		// 正则表达式URL参数
		r.Get("/{articleSlug:[a-z-]+}", getArticleBySlug)               // GET /articles/home-is-toronto

		// 子路由
		r.Route("/{articleID}", func(r chi.Router) {
			r.Use(ArticleCtx)
			r.Get("/", getArticle)                                      // GET /articles/123
			r.Put("/", updateArticle)                                   // PUT /articles/123
			r.Delete("/", deleteArticle)                                // DELETE /articles/123
		})
	})

	// 挂载管理员子路由
	r.Mount("/admin", adminRouter())

	http.ListenAndServe(":3333", r)
}

func ArticleCtx(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		articleID := chi.URLParam(r, "articleID")
		article, err := dbGetArticle(articleID)
		if err != nil {
			http.Error(w, http.StatusText(404), 404)
			return
		}
		ctx := context.WithValue(r.Context(), "article", article)
		next.ServeHTTP(w, r.WithContext(ctx))
	})
}

func getArticle(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	article, ok := ctx.Value("article").(*Article)
	if !ok {
		http.Error(w, http.StatusText(422), 422)
		return
	}
	w.Write([]byte(fmt.Sprintf("title:%s", article.Title)))
}

// 管理员路由
func adminRouter() http.Handler {
	r := chi.NewRouter()
	r.Use(AdminOnly)
	r.Get("/", adminIndex)
	r.Get("/accounts", adminListAccounts)
	return r
}

func AdminOnly(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		ctx := r.Context()
		perm, ok := ctx.Value("acl.permission").(YourPermissionType)
		if !ok || !perm.IsAdmin() {
			http.Error(w, http.StatusText(403), 403)
			return
		}
		next.ServeHTTP(w, r)
	})
}

中间件示例

// HTTP中间件设置请求上下文的值
func MyMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// 从请求上下文中创建新上下文,并设置"user"键的值为"123"
		ctx := context.WithValue(r.Context(), "user", "123")

		// 调用链中的下一个处理器,传递响应写入器和带有新上下文值的请求对象
		next.ServeHTTP(w, r.WithContext(ctx))
	})
}

// HTTP处理器访问请求上下文中的数据
func MyRequestHandler(w http.ResponseWriter, r *http.Request) {
	// 从请求上下文中读取"user"键的值
	user := r.Context().Value("user").(string)

	// 响应客户端
	w.Write([]byte(fmt.Sprintf("hi %s", user)))
}

// HTTP处理器访问URL路由参数
func MyRequestHandler(w http.ResponseWriter, r *http.Request) {
	// 从匹配的路由模式中获取URL参数"userID"
	userID := chi.URLParam(r, "userID")

	// 从请求上下文中获取"key"
	ctx := r.Context()
	key := ctx.Value("key").(string)

	// 响应客户端
	w.Write([]byte(fmt.Sprintf("hi %v, %v", userID, key)))
}

核心中间件

chi提供了一个可选的中件间包,包含一系列标准的net/http中间件。

中间件 描述
AllowContentEncoding 强制请求Content-Encoding头白名单
BasicAuth 基本HTTP认证
Compress 对接受压缩响应的客户端进行Gzip压缩
Logger 记录每个请求的开始和结束以及处理时间
RealIP 设置http.Request的RemoteAddr为X-Real-IP或X-Forwarded-For
Recoverer 优雅地吸收panic并打印堆栈跟踪
RequestID 向每个请求的上下文注入请求ID
Timeout 当达到超时截止时间时向请求上下文发出信号

性能

chi基于一种Patricia Radix trie实现,性能优异。在Go 1.15.5 Linux AMD 3950x上的基准测试结果:

BenchmarkChi_Param          3075895        384 ns/op      400 B/op      2 allocs/op
BenchmarkChi_Param5         2116603        566 ns/op      400 B/op      2 allocs/op
BenchmarkChi_Param20         964117       1227 ns/op      400 B/op      2 allocs/op
BenchmarkChi_GithubStatic   3045488        395 ns/op      400 B/op      2 allocs/op
BenchmarkChi_GithubParam    2204115        540 ns/op      400 B/op      2 allocs/op

chi是一个专注于简单优雅设计的路由器,特别适合构建大型REST API服务。它的小巧核心和模块化设计使其成为Go HTTP服务开发的理想选择。


更多关于golang轻量快速基于net/context的HTTP路由插件chi的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang轻量快速基于net/context的HTTP路由插件chi的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Golang轻量快速HTTP路由插件chi使用指南

chi是一个轻量级、高性能的Go语言HTTP路由器,基于标准库net/http构建,支持context中间件链。它非常适合构建RESTful API服务。

主要特点

  • 极轻量级(约1000行代码)
  • 100%兼容net/http
  • 支持中间件链
  • 基于context的路由参数
  • 无外部依赖
  • 优秀的性能

基本使用

安装

go get -u github.com/go-chi/chi/v5

简单示例

package main

import (
	"net/http"
	"github.com/go-chi/chi/v5"
	"github.com/go-chi/chi/v5/middleware"
)

func main() {
	r := chi.NewRouter()
	
	// 使用内置中间件
	r.Use(middleware.Logger)
	r.Use(middleware.Recoverer)
	
	// 基本路由
	r.Get("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("Hello World!"))
	})
	
	// 启动服务器
	http.ListenAndServe(":3000", r)
}

路由功能

路径参数

r.Get("/users/{userID}", func(w http.ResponseWriter, r *http.Request) {
	userID := chi.URLParam(r, "userID")
	w.Write([]byte(fmt.Sprintf("User ID: %s", userID)))
})

正则匹配

r.Get("/articles/{id:[0-9]+}", func(w http.ResponseWriter, r *http.Request) {
	id := chi.URLParam(r, "id")
	w.Write([]byte(fmt.Sprintf("Article ID: %s", id)))
})

路由分组

r.Route("/users", func(r chi.Router) {
	r.Get("/", listUsers)          // GET /users
	r.Post("/", createUser)        // POST /users
	
	r.Route("/{userID}", func(r chi.Router) {
		r.Get("/", getUser)       // GET /users/123
		r.Put("/", updateUser)    // PUT /users/123
		r.Delete("/", deleteUser) // DELETE /users/123
	})
})

中间件使用

内置中间件

chi提供了一些常用中间件:

r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Use(middleware.Timeout(60 * time.Second))

自定义中间件

func AuthMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		token := r.Header.Get("Authorization")
		if token != "valid-token" {
			http.Error(w, "Unauthorized", http.StatusUnauthorized)
			return
		}
		next.ServeHTTP(w, r)
	})
}

// 使用中间件
r.With(AuthMiddleware).Get("/admin", adminHandler)

文件服务

// 静态文件服务
r.Handle("/static/*", http.StripPrefix("/static/", http.FileServer(http.Dir("./static"))))

// 单文件服务
r.Get("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {
	http.ServeFile(w, r, "./static/favicon.ico")
})

RESTful API示例

package main

import (
	"encoding/json"
	"net/http"
	"github.com/go-chi/chi/v5"
	"github.com/go-chi/chi/v5/middleware"
)

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

var users = map[string]User{
	"1": {ID: "1", Name: "Alice"},
	"2": {ID: "2", Name: "Bob"},
}

func main() {
	r := chi.NewRouter()
	r.Use(middleware.Logger)
	r.Use(middleware.Recoverer)
	
	r.Route("/api/users", func(r chi.Router) {
		r.Get("/", listUsers)
		r.Post("/", createUser)
		
		r.Route("/{userID}", func(r chi.Router) {
			r.Get("/", getUser)
			r.Put("/", updateUser)
			r.Delete("/", deleteUser)
		})
	})
	
	http.ListenAndServe(":3000", r)
}

func listUsers(w http.ResponseWriter, r *http.Request) {
	json.NewEncoder(w).Encode(users)
}

func createUser(w http.ResponseWriter, r *http.Request) {
	var user User
	if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	users[user.ID] = user
	w.WriteHeader(http.StatusCreated)
	json.NewEncoder(w).Encode(user)
}

func getUser(w http.ResponseWriter, r *http.Request) {
	userID := chi.URLParam(r, "userID")
	user, ok := users[userID]
	if !ok {
		http.Error(w, "User not found", http.StatusNotFound)
		return
	}
	json.NewEncoder(w).Encode(user)
}

func updateUser(w http.ResponseWriter, r *http.Request) {
	userID := chi.URLParam(r, "userID")
	var user User
	if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	users[userID] = user
	json.NewEncoder(w).Encode(user)
}

func deleteUser(w http.ResponseWriter, r *http.Request) {
	userID := chi.URLParam(r, "userID")
	delete(users, userID)
	w.WriteHeader(http.StatusNoContent)
}

性能优化技巧

  1. 避免在热路径中使用正则表达式
  2. 合理使用中间件,避免不必要的处理
  3. 对于静态路由优先使用Get()/Post()等直接方法
  4. 使用r.Mount()挂载子路由器

chi是一个简单但功能强大的路由器,特别适合构建高性能的API服务。它的设计哲学是保持简单和可组合性,同时提供足够的灵活性来处理复杂路由需求。

回到顶部