О веб-фреймворке
Gin — это легкий веб-фреймворк для go.Легкий означает, что он предоставляет только основные функции, которые должны быть в веб-фреймворке. Я думаю, что лучший способ взглянуть на исходный код — это поставить перед собой цели.
- Как веб-фреймворк gin реализует основные функции, которыми должен обладать веб-фреймворк?
- Есть ли что-нибудь, что стоит узнать о реализации кода?
Общий поток обработки запроса
Как найти вход
Чтобы узнать общий поток обработки запроса, просто найдите запись веб-фреймворка. Давайте сначала взглянем на самую простую демонстрацию в документации gin. Метод Run очень красив.Нажмите, чтобы увидеть ключ http.ListenAndServe, который означает, что структура Engine реализует интерфейс ServeHTTP. Запись представляет собой интерфейс ServeHTTP, реализованный Engine.
//我是最简单的demo
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
c.Redirect(http.StatusMovedPermanently, "https://github.com/gin-gonic/gin")
})
r.Run() // listen and serve on 0.0.0.0:8080
}
//我是Run方法
func (engine *Engine) Run(addr ...string) (err error) {
defer func() { debugPrintError(err) }()
address := resolveAddress(addr)
debugPrint("Listening and serving HTTP on %s\n", address)
err = http.ListenAndServe(address, engine)
return
}
ServeHTTP
Общий процесс так же прост, как комментарии. Здесь стоит отметить, что,Объект контекста Контекст извлекается из пула объектов, а не генерируется каждый раз, что повышает эффективность.. Как видите, реальный основной поток обработки находится в методе handleHTTPRequest.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// 从上下文对象池中获取一个上下文对象
c := engine.pool.Get().(*Context)
// 初始化上下文对象,因为从对象池取出来的数据,有脏数据,故要初始化。
c.writermem.reset(w)
c.Request = req
c.reset()
//处理web请求
engine.handleHTTPRequest(c)
//将Context对象扔回对象池了
engine.pool.Put(c)
}
handleHTTPRequest
В следующем коде пропущено много кода, который не имеет ничего общего с основной логикой.Основная логика очень проста: больше метода запроса и обработки поиска URI запроса.функции, а потом звоните. Почему обработчики, а не обработчики, которые мы написали? Потому что сюда включена функция обработки среднего слоя.
func (engine *Engine) handleHTTPRequest(context *Context) {
httpMethod := context.Request.Method
var path string
var unescape bool
// 省略......
// tree是个数组,里面保存着对应的请求方式的,URI与处理函数的树。
// 之所以用数组是因为,在个数少的时候,数组查询比字典要快
t := engine.trees
for i, tl := 0, len(t); i < tl; i++ {
if t[i].method == httpMethod {
root := t[i].root
// 找到路由对应的处理函数们
handlers, params, tsr := root.getValue(path, context.Params, unescape)
// 调用处理函数们
if handlers != nil {
context.handlers = handlers
context.Params = params
context.Next()
context.writermem.WriteHeaderNow()
return
}
// 省略......
break
}
}
// 省略......
}
Место, где стоит учиться
обработка маршрута
критические потребности
Если оставить в стороне структуру gin, каковы ключевые требования для обработки маршрутизации? Я лично думаю, что есть два момента
- Эффективный поиск функций обработчика, соответствующих URI
- Гибкие комбинации маршрутизации
Обращение с джином
основная идея
- Каждому маршруту соответствует независимый массив функций обработки
- Промежуточное ПО и функция обработчика одинаковы
- Используйте дерево для обеспечения эффективного поиска массива функций обработки, соответствующего URI.
интересное место
RouterGroup обрабатывает маршруты
Гибкая компоновка маршрутов достигается за счет применения отдельного массива функций-обработчиков к каждому URI. Операция объединения маршрутов абстрагирует структуру RouterGroup для работы с ней. Его основные функции:
- Свяжите маршрут с соответствующей функцией обработчика
- Обеспечивает функцию групп маршрутизации, которая реализуется за счет способа связывания префиксов
- Обеспечивает функцию свободного комбинирования промежуточного ПО: 1. Общее промежуточное ПО 2. Промежуточное ПО группы маршрутизации 3. Промежуточное ПО функции обработки
И группы маршрутов, и обработчики могут добавлять промежуточное ПО, которое намного более гибко, чем обычное промежуточное ПО DJango.
Работа с промежуточным ПО
Промежуточное программное обеспечение необходимо обрабатывать при запросе, а также может потребоваться обработка при его возврате. Как показано ниже (картинка из django).
Проблема в том, что промежуточное ПО gin — это функция обработки, как реализовать обработку при возврате. Если вы внимательно посмотрите, вызов на приведенном выше рисунке — это LIFO.Да, ответ на каждую ошибку: использование функции LIFO стека вызовов функций для умелого завершения операции постобработки, выполняемой промежуточным программным обеспечением в пользовательской функции обработки. Метод обработки Django заключается в определении класса, определении метода обработки перед обработкой запроса и определении метода обработки после обработки запроса. Способ джина более гибкий, но способ джанго понятнее.//调用处理函数数组
func (c *Context) Next() {
c.index++
s := int8(len(c.handlers))
for ; c.index < s; c.index++ {
c.handlers[c.index](c)
}
}
// 中间件例子
func Logger() gin.HandlerFunc {
return func(c *gin.Context) {
t := time.Now()
// before request
c.Set("example", "12345")
c.Next()
// 返回后的处理
latency := time.Since(t)
log.Print("latency: ", latency)
status := c.Writer.Status()
log.Println("status: ", status)
}
}
func main() {
r := gin.New()
r.Use(Logger())
r.GET("/test", func(c *gin.Context) {
example := c.MustGet("example").(string)
// it would print: "12345"
log.Println("example", example)
})
// Listen and serve on 0.0.0.0:8080
r.Run(":8081")
}
Обработка содержимого запроса и обработка возвращенного содержимого
необходимость
- Получить параметры в пути
- Получить параметры запроса
- получить содержимое запроса
- вернуть обработанный результат
Идеи реализации Gin framework
Помимо предоставления согласованного метода обработки путем наложения собственного слоя, если вы недовольны официальной реализацией, вы можете заменить его или даже добавить слой обработки кеша (на самом деле это не обязательно, потому что обрабатывается только один раз при обычном использовании).
- Если официальная http-библиотека может предоставить его, только один слой официальной http-библиотеки упакован для предоставления интерфейса с согласованным интерфейсом.
- Если официальная http-библиотека не может предоставить, вы можете реализовать ее самостоятельно.
ключевая структура
type Context struct {
writermem responseWriter
Request *http.Request
// 传递接口,使用各个处理函数,更加灵活,降低耦合
Writer ResponseWriter
Params Params // 路径当中的参数
handlers HandlersChain // 处理函数数组
index int8 // 目前在运行着第几个处理函数
engine *Engine
Keys map[string]interface{} // 各个中间件添加的key value
Errors errorMsgs
Accepted []string
}
Очки, которые стоит изучить
Поиск в массиве выполняется быстрее, чем поиск в словаре для небольших чисел.
Из комментариев к структуре Context выше вы можете узнать, что Params на самом деле является массивом. По сути можно сказать, что это соответствие значений ключей, почему не словарь, а массив? В реальных сценариях количество параметров для получения параметров пути не очень велико, а при использовании словаря производительность не такая высокая, как у массива. Поскольку словарю нужно найти соответствующее значение, общий процесс таков: хешировать ключ -> найти положение соответствующего смещения с помощью алгоритма (существует несколько алгоритмов, вы можете проверить это, если вам интересно) -> взять значение . После набора процессов массив был пройден с небольшим количеством.
router.GET("user/:name/*action", func(c *gin.Context) {
name := c.Param("name")
action := c.Param("action")
message := name + " is222 " + action
c.String(http.StatusOK, message)
})
func (ps Params) Get(name string) (string, bool) {
for _, entry := range ps {
if entry.Key == name {
return entry.Value, true
}
}
return "", false
}
Различные сценарии обрабатываются через интерфейс
получить содержимое запроса
Сценарии, с которыми сталкивается требование получить содержимое запроса. Для статического языка, такого как go, если вы хотите обработать содержимое запроса, вам необходимо десериализовать содержимое в структуру, однако содержимое запроса поступает в различных формах, таких как JSON, XML, ProtoBuf и т. д. Таким образом, здесь можно резюмировать следующие нефункциональные требования.
- Разный контент требует разных механизмов десериализации
- Разрешить пользователям реализовывать собственный механизм десериализации
Общим моментом является то, что контент обрабатывается, разница в том, что контент обрабатывается по-разному, что легко представить.полиморфизмЭта концепция, различные виды ищут точки соприкосновения. Ядром полиморфизма является интерфейс, который в настоящее время необходимо абстрагировать.
type Binding interface {
Name() string
Bind(*http.Request, interface{}) error
}
вернуть обработанный контент
Содержимое запроса варьируется, а возвращаемый контент одинаков. Например: вернуть JSON, вернуть XML, вернуть HTML, вернуть 302 и т. д. Здесь можно резюмировать следующие нефункциональные требования.
- Различные типы возвращаемого контента требуют разных механизмов сериализации.
- Позволяет пользователям реализовать собственный механизм сериализации
В соответствии с вышеизложенным, интерфейс здесь также абстрагируется.
type Render interface {
Render(http.ResponseWriter) error
WriteContentType(w http.ResponseWriter)
}
После того, как интерфейс определен, нужно подумать о том, как его использовать.
Подумайте, как изящно использовать эти интерфейсы.
Для получения содержимого запроса в привязке модели существуют следующие сценарии.
- Сбой привязки обрабатывается пользователем или платформой?
- Должен ли пользователь заботиться о содержании запроса и выбирать другую подшивку
Ответ, данный в структуре gin для этих сценариев, заключается в предоставлении различных методов для удовлетворения вышеуказанных требований. Ключевым моментом здесь является то, каков сценарий использования.
// 自动更加请求头选择不同的绑定器对象进行处理
func (c *Context) Bind(obj interface{}) error {
b := binding.Default(c.Request.Method, c.ContentType())
return c.MustBindWith(obj, b)
}
// 绑定失败后,框架会进行统一的处理
func (c *Context) MustBindWith(obj interface{}, b binding.Binding) (err error) {
if err = c.ShouldBindWith(obj, b); err != nil {
c.AbortWithError(400, err).SetType(ErrorTypeBind)
}
return
}
// 用户可以自行选择绑定器,自行对出错处理。自行选择绑定器,这也意味着用户可以自己实现绑定器。
// 例如:嫌弃默认的json处理是用官方的json处理包,嫌弃它慢,可以自己实现Binding接口
func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error {
return b.Bind(c.Request, obj)
}
Обработка несоответствий в построении реализованных конструкций
Вернуть обработанный контент, а реализованные параметры построения класса несовместимы. Например: для обработки текста и для обработки json. Оружием, принесенным в жертву перед лицом такого рода сцен, является: еще один уровень инкапсуляции, который используется для создания соответствующего объекта обработки.
//对于String的处理
type String struct {
Format string
Data []interface{}
}
//对于String处理封装多的一层
func (c *Context) String(code int, format string, values ...interface{}) {
c.Render(code, render.String{Format: format, Data: values})
}
//对于json的处理
JSON struct {
Data interface{}
}
//对于json的处理封装多的一层
func (c *Context) JSON(code int, obj interface{}) {
c.Render(code, render.JSON{Data: obj})
}
//核心的一致的处理
func (c *Context) Render(code int, r render.Render) {
c.Status(code)
if !bodyAllowedForStatus(code) {
r.WriteContentType(c.Writer)
c.Writer.WriteHeaderNow()
return
}
if err := r.Render(c.Writer); err != nil {
panic(err)
}
}
Суммировать
Этот процесс просмотра кода заключается в том, чтобы смотреть его шаг за шагом на примере официального документа после того, как есть цель. Затем медленно оцените, как этот фреймворк обрабатывает некоторые распространенные сценарии веб-фреймворков. Объем кода в этом фреймворке очень мал, и он очень элегантно написан, что очень достойно внимания.