description |
---|
краткое описание elton |
Мои первые шаги в разработке backend начались с nodejs, а первым использованным мной фреймворком был express. Позже я познакомился с другими фреймворками, но наиболее знакомым мне стал koa. При использовании golang для backend-разработки, я сравнивал gin, echo и iris, и все они имеют похожие возможности (поддерживают middleware, имеют похожие методы обработки). Однако в процессе разработки я предпочитаю подход koa: при неудаче выбрасываю ошибку, а при успехе присваиваю ответные данные ctx.body, что делает процесс простым и понятным.
При создании нового фреймворка, первым делом я учитываю свои потребности. Из трех тысяч источников воды я беру лишь один кувшин, так и новый фреймворк должен удовлетворять моим потребностям. В частности, мне нужно, чтобы все успешные и неудачные ответы обрабатывались фреймворком, а не отдельными middleware или функциями маршрутизации. Почему я так думаю? В реальной разработке способности разработчиков могут различаться, и я хочу иметь возможность легко добавлять единое обработание ответов, чтобы облегчить создание отчетов. Конкретные моменты, которые должен реализовать фреймворк, включают:- Обработка запросов через middleware должна происходить от внешнего к внутреннему, а ответы — от внутреннего к внешнему.
Теперь рассмотрим простые примеры успешной обработки запроса и ошибки:
package main
import (
"errors"
"github.com/vicanso/elton"
"github.com/vicanso/elton/middleware"
)
func main() {
e := elton.New()
e.Use(middleware.NewDefaultResponder())
e.Use(middleware.NewDefaultError())
e.GET("/", func(c *elton.Context) (err error) {
c.Body = &struct {
Message string `json:"message,omitempty"`
}{
Message: "Hello world!",
}
return
})
Как показано в коде, процесс очень прост, и данные ответа напрямую присваиваются к Body(interface{}), с помощью middleware Responder структуры и другие данные могут быть преобразованы в JSON-ответ (или с помощью пользовательских middleware можно реализовать более разнообразные типы ответов). В случае ошибки обработки, достаточно вернуть error, middleware error будет преобразовывать error в соответствующий HTTP-ответ. Эти два типа middleware будут подробно рассмотрены позже.
elton
обрабатываются после выполнения всех middleware и функций маршрутизации. Обычно ответы формируются фреймворком, который в конце концов записывает данные из BodyBuffer
в http.ResponseWriter
. Все middleware и функции обработки не должны напрямую записывать данные в http.ResponseWriter
.Для успешных ответов, чтобы облегчить разработку, elton
предоставляет ctx.Body
, который позволяет устанавливать различные типы данных ответа (тип interface{}). С помощью middleware ответа данные преобразуются в соответствующий Buffer (например, json.Marshal), также поддерживается напрямую записывать данные в ResponseWriter, но это не рекомендуется.В случае ошибок, все они напрямую возвращаются как error
, с помощью пользовательского middleware обработки ошибок, в зависимости от сценария использования, ошибки преобразуются в соответствующие типы данных (например, JSON). Из-за единой обработки ошибок, можно очень легко собирать и анализировать различные ошибки с помощью пользовательских middleware обработки ошибок. Это позволяет собирать и анализировать неуправляемые ошибки (например, из-за ненормативного кодирования или неизвестных ошибок), что облегчает последующую оптимизацию и корректировку соответствующих процессов.
После того как HTTP-ответы обрабатываются единым образом, данные ответа делятся на три части: код состояния (int), заголовки ответа (http.Header), тело ответа (*bytes.Buffer). Это позволяет легко реализовать следующие функции:- Определять, следует ли сжимать данные на основе типа контента в заголовке ответа и размера тела ответа, а также выбирать подходящий алгоритм сжатия на основе заголовка Accept-Encoding
ETag
на основе тела ответа и обрабатывать 304
Cache-Control
, и кэшировать GET и HEAD ответы в памяти или базе данных, что позволяет реализовать функцию кэширования URLПочему elton
не рекомендует использовать прямую запись данных в ResponseWriter
?Рассмотрим следующую ситуацию: добавление промежуточного программного обеспечения для сжатия с использованием gzip
, которое требует сжатия ответных данных. Если использовать прямое написание данных, то можно обернуть только один объект ResponseWriter
. Используя пользовательский объект Writer
, который сжимает данные при их получении и передает их исходному объекту ResponseWriter
, можно реализовать сжатие данных. Однако такой подход не позволяет реализовать персонализированное сжатие данных, например, выбирать разные методы сжатия в зависимости от типа ответных данных или их длины. При рассмотрении обработки кода 304
, необходимо рассчитать ETag
для текущего ответа и проверить, был ли он обновлен. Для выполнения этой обработки сначала нужно преобразовать ответные данные в байты, а затем выполнить расчет. Если данные сразу записать в ResponseWriter
, то этот промежуточный процесс не будет возможен.
Handler func(*Context) error
, и их можно добавить в глобальный список промежуточных обработчиков с помощью метода Use
, или добавить отдельно к одному маршруту или группе маршрутов. Обработка промежуточных обработчиков также очень проста: если возникает ошибка, возвращается Error
(дальнейшие обработчики не выполняются). Если текущий обработчик завершил обработку, вызов Context.Next()
не требуется. Если необходимо перейти к следующему обработчику, вызов Context.Next()
выполняет эту задачу. Ниже приведено описание наиболее часто используемых промежуточных обработчиков.```gopackage main
import ( "bytes" "log" "time"
"github.com/vicanso/elton"
)
func main() { e := elton.New()
// логгер
e.Use(func(c *elton.Context) (err error) {
err = c.Next()
rt := c.GetHeader("X-Response-Time")
log.Printf("%s %s - %s\n", c.Request.Method, c.Request.RequestURI, rt)
return
})
// x-response-time
e.Use(func(c *elton.Context) (err error) {
start := time.Now()
err = c.Next()
c.SetHeader("X-Response-Time", time.Since(start).String())
return
})
e.GET("/", func(c *elton.Context) (err error) {
c.BodyBuffer = bytes.NewBufferString("Hello, World!")
return
})
err := e.ListenAndServe(":3000")
if err != nil {
panic(err)
}
}
### Промежуточный обработчик responder
HTTP-ответы обычно делятся на три части: HTTP-код ответа, HTTP-заголовки и HTTP-тело ответа. Первая и вторая части имеют унифицированный формат, но HTTP-тело ответа может отличаться для разных приложений. В elton ответные данные из `BodyBuffer` выводятся как HTTP-тело ответа. В реальных приложениях некоторые используют JSON, другие XML или другие форматы ответов. Поэтому в elton существует свойство `Body(interface{})`, которое позволяет присваивать ответные данные этому полю, а затем промежуточные обработчики преобразуют его в соответствующий `BodyBuffer` и устанавливают `Content-Type`.
В реальных приложениях HTTP-интерфейсы обычно используют JSON, поэтому `elton-responder` предоставляет обработку преобразования `Body` в соответствующий `BodyBuffer` (JSON). Основная обработка осуществляется следующим образом:
```go
// NewResponder создает откликающийся объект
func NewResponder(config ResponderConfig) elton.Handler {
skipper := config.Skipper
if skipper == nil {
skipper = elton.DefaultSkipper
}
marshal := config.Marshal
// Если не определен marshal
if marshal == nil {
marshal = json.Marshal
}
contentType := config.ContentType
if contentType == "" {
contentType = elton.MIMEApplicationJSON
}
}
return func(c *elton.Context) (err error) {
if skipper(c) {
return c.Next()
}
err = c.Next()
if err != nil {
return
}
// Если BodyBuffer уже установлен, значит ответ уже сформирован, пропускаем
if c.BodyBuffer != nil {
return
}
}
``````go
if c.StatusCode == 0 && c.Body == nil {
// Если статус-код и тело ответа пустые, значит это недопустимый ответ
err = ErrInvalidResponse
return
}
// Если тело ответа является reader, пропускаем
if c.IsReaderBody() {
return
}
hadContentType := false
// Проверяем, установлен ли заголовок Content-Type
if c.GetHeader(elton.HeaderContentType) != "" {
hadContentType = true
}
var body []byte
if c.Body != nil {
switch data := c.Body.(type) {
case string:
if !hadContentType {
c.SetHeader(elton.HeaderContentType, elton.MIMETextPlain)
}
body = []byte(data)
case []byte:
if !hadContentType {
c.SetHeader(elton.HeaderContentType, elton.MIMEBinary)
}
body = data
default:
// Используем marshal для преобразования (по умолчанию json.Marshal)
buf, e := marshal(data)
if e != nil {
he := hes.NewWithErrorStatusCode(e, http.StatusInternalServerError)
he.Category = ErrResponderCategory
he.Exception = true
err = he
return
}
if !hadContentType {
c.SetHeader(elton.HeaderContentType, contentType)
}
body = buf
}
}
statusCode := c.StatusCode
if statusCode == 0 {
statusCode = http.StatusOK
}
if len(body) != 0 {
c.BodyBuffer = bytes.NewBuffer(body)
}
c.StatusCode = statusCode
return nil
}
Код выполняет следующие шаги:
Проверяет, нужно ли пропустить middleware, основываясь на условиях: наличие ошибки, наличие BodyBuffer
(что указывает на завершение обработки ответа) или тело ответа является reader (выходные данные в виде потока).
Если тело ответа имеет тип string
, то string
преобразуется в bytes
. Если заголовок Content-Type
не установлен, то он устанавливается как text/plain; charset=utf-8
.
Если тело ответа имеет тип []byte
, и заголовок Content-Type
не установлен, то он устанавливается как application/octet-stream
.
```4. Для других типов используется marshal
(по умолчанию `json.Marshal`) для преобразования в `[]byte`. Если заголовок `Content-Type` не установлен, то он устанавливается как `application/json; charset=utf-8`.
С помощью этого middleware разработчики могут легко возвращать различные структуры данных и карты данных в виде json
, без необходимости отдельной обработки преобразования данных. Если приложение требует возврата данных в формате xml
или других форматах, можно настроить marshal
и Content-Type
соответственно.
По умолчанию в elton
простая обработка ошибок включает только вывод err.Error()
и использование простого статус-кода StatusInternalServerError
, что не удовлетворяет потребностям различных пользовательских сценариев обработки ошибок. Поэтому рекомендуется создавать собственные middleware для обработки ошибок, которые генерируют соответствующие данные ответа на основе пользовательских объектов ошибок. Например, elton-error
генерирует соответствующие статус-коды, типы ответов и данные ответа (json) на основе объектов ошибок типа hes.Error:```go
// NewError создает обработчик ошибок
func NewError(config ErrorConfig) elton.Handler {
skipper := config.Skipper
if skipper == nil {
skipper = elton.DefaultSkipper
}
return func(c *elton.Context) error {
if skipper(c) {
return c.Next()
}
err := c.Next()
// Если ошибки нет, возвращаем nil
if err == nil {
return nil
}
he, ok := err.(*hes.Error)
if !ok {
he = hes.Wrap(err)
// Если ошибка не является hes.Error, считаем ее ошибкой 500
he.StatusCode = http.StatusInternalServerError
he.Exception = true
he.Category = ErrErrorCategory
}
c.StatusCode = he.StatusCode
if config.ResponseType == "json" ||
strings.Contains(c.GetRequestHeader("Accept"), "application/json") {
buf := he.ToJSON()
c.BodyBuffer = bytes.NewBuffer(buf)
c.SetHeader(elton.HeaderContentType, elton.MIMEApplicationJSON)
} else {
c.BodyBuffer = bytes.NewBufferString(he.Error())
c.SetHeader(elton.HeaderContentType, elton.MIMETextPlain)
}
}
}
}
}
Elton предоставляет более простой и удобный опыт разработки веб-приложений, код реализации которого очень прост. Большинство функций реализуются с помощью различных middleware. Для получения дополнительной информации о middleware и документации обратитесь к списку middleware.
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )