1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/mirrors-elton

Клонировать/Скачать
introduction.md 20 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 04.06.2025 23:59 7f2703a
description
краткое описание elton

Введение

Мои первые шаги в разработке backend начались с nodejs, а первым использованным мной фреймворком был express. Позже я познакомился с другими фреймворками, но наиболее знакомым мне стал koa. При использовании golang для backend-разработки, я сравнивал gin, echo и iris, и все они имеют похожие возможности (поддерживают middleware, имеют похожие методы обработки). Однако в процессе разработки я предпочитаю подход koa: при неудаче выбрасываю ошибку, а при успехе присваиваю ответные данные ctx.body, что делает процесс простым и понятным.

Обзор

При создании нового фреймворка, первым делом я учитываю свои потребности. Из трех тысяч источников воды я беру лишь один кувшин, так и новый фреймворк должен удовлетворять моим потребностям. В частности, мне нужно, чтобы все успешные и неудачные ответы обрабатывались фреймворком, а не отдельными middleware или функциями маршрутизации. Почему я так думаю? В реальной разработке способности разработчиков могут различаться, и я хочу иметь возможность легко добавлять единое обработание ответов, чтобы облегчить создание отчетов. Конкретные моменты, которые должен реализовать фреймворк, включают:- Обработка запросов через middleware должна происходить от внешнего к внутреннему, а ответы — от внутреннего к внешнему.

  • Все обработчики должны иметь одинаковые параметры и типы, каждый обработчик может быть предшествующим middleware для других обработчиков.
  • При успешной обработке запроса, данные присваиваются Body(interface{}), а middleware сериализует его в соответствующие байты (например, json, xml и т.д.).
  • При неудачной обработке запроса, возвращается ошибка, а middleware преобразует её в соответствующие байты (golang-ошибка является interface, которую можно настроить).elton, используя реализацию Koa, позволяет легко добавлять различные middleware, и их выполнение также похоже на Koa. Это показано на следующем "луковом" рисунке, где выполнение начинается с внешнего слоя и переходит к внутреннему, а затем возвращается обратно к внешнему (или возвращается к внешнему слою, не дойдя до внутреннего).

Теперь рассмотрим простые примеры успешной обработки запроса и ошибки:

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 будут подробно рассмотрены позже.

Единый HTTP-ответHTTP-ответы (успешные и ошибочные) в 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, то этот промежуточный процесс не будет возможен.

Промежуточные обработчикиПромежуточные обработчики в elton являются настоящим сокровищем. Функция обработчика имеет тип Handler func(*Context) error, и их можно добавить в глобальный список промежуточных обработчиков с помощью метода Use, или добавить отдельно к одному маршруту или группе маршрутов. Обработка промежуточных обработчиков также очень проста: если возникает ошибка, возвращается Error (дальнейшие обработчики не выполняются). Если текущий обработчик завершил обработку, вызов Context.Next() не требуется. Если необходимо перейти к следующему обработчику, вызов Context.Next() выполняет эту задачу. Ниже приведено описание наиболее часто используемых промежуточных обработчиков.```go

package 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
}

Код выполняет следующие шаги:

  1. Проверяет, нужно ли пропустить middleware, основываясь на условиях: наличие ошибки, наличие BodyBuffer (что указывает на завершение обработки ответа) или тело ответа является reader (выходные данные в виде потока).

  2. Если тело ответа имеет тип string, то string преобразуется в bytes. Если заголовок Content-Type не установлен, то он устанавливается как text/plain; charset=utf-8.

  3. Если тело ответа имеет тип []byte, и заголовок Content-Type не установлен, то он устанавливается как application/octet-stream. ```4. Для других типов используется marshal (по умолчанию `json.Marshal`) для преобразования в `[]byte`. Если заголовок `Content-Type` не установлен, то он устанавливается как `application/json; charset=utf-8`.

С помощью этого middleware разработчики могут легко возвращать различные структуры данных и карты данных в виде json, без необходимости отдельной обработки преобразования данных. Если приложение требует возврата данных в формате xml или других форматах, можно настроить marshal и Content-Type соответственно.

Обработчик ошибок middleware

По умолчанию в 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 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/mirrors-elton.git
git@api.gitlife.ru:oschina-mirror/mirrors-elton.git
oschina-mirror
mirrors-elton
mirrors-elton
master