ПРИМЕЧАНИЕ: гонгулярный недавно обновлен, и если вы ищете предыдущую версию, она помечена как v.1.0
Gongular - это HTTP Server Framework для легкости разработки API. Это похоже на джин-Gonic, но он имеет углоподобную (или пружинную) инъекцию зависимости и лучшую обработку ввода. В большинстве случаев пользовательский ввод должен быть преобразован в структурированные данные, а затем он должен быть подтвержден. Это занимает слишком много времени и является повторяющейся работой, гонгулярная цель-уменьшить эту сложность, предоставляя отображение ввода запроса с помощью проверки на основе тегов.
Примечание: гонгулярный - это самоуверенная структура, и она в значительной степени зависит от размышлений для достижения этих функций. Хотя есть тесты, чтобы убедиться, что он работает безупречно, я открыт для вкладов и мнений о том, как сделать его лучше.
Гонгулярные цели - быть простым, насколько это возможно, обеспечивая гибкость. Приведенный ниже пример достаточно, чтобы ответить пользователю своим IP.
type WelcomeMessage struct {}
func ( w * WelcomeMessage ) Handle ( c * gongular. Context ) error {
c . SetBody ( c . Request (). RemoteAddr )
}
g := gongular . NewEngine ()
g . GET ( "/" , & WelcomeMessage {})
g . ListenAndServe ( ":8000" ) Все обработчики HTTP в гонгуляре представляют собой структуру с функцией Handle(c *gongular.Context) error или, другими словами, реализовано интерфейс RequestHandler . Объекты обработки запросов гибкие. Они могут иметь различные поля, где некоторые из областей с конкретными именами являются особенными. Например, если вы хотите связать параметры пути, ваш объект обработчика должен иметь поле с названием Param , который является плоской структурой. Также вы можете иметь поле Query , которое также отображает параметры запроса. Поле Body позволяет вам сопоставить корпус JSON, а Form Field позволяет привязать к формам с файлами.
type MyHandler struct {
Param struct {
UserID int
}
Query struct {
Name string
Age int
Level float64
}
Body struct {
Comment string
Choices [] string
Address struct {
City string
Country string
Hello string
}
}
}
func ( m * MyHandler ) Handle ( c * gongular. Context ) error {
c . SetBody ( "Wow so much params" )
return nil
}Мы используем Julienschmidt/HttProuter для мультиплексных запросов и выполняем параметрическое привязку с запросами. Таким образом, формат: variablename, *SomePath поддерживается на пути. Обратите внимание, что вы можете использовать допустимый тег struct для проверки параметров.
type PathParamHandler struct {
Param struct {
Username string
}
}
func ( p * PathParamHandler ) Handle ( c * Context ) error {
c . SetBody ( p . Param . Username )
return nil
} Параметр запроса очень похож на параметры пути, единственное отличие, которое имя поля должно быть Query , и это также должно быть плоской структурой без внутренних параметров или массивов. Парамы запросов чувствительны к случаю и по умолчанию используют точное имя свойства struct. Вы можете использовать тег q struct, чтобы указать клавишу параметра
type QueryParamHandler struct {
Query struct {
Username string `q:"username"`
Age int
}
}
func ( p * QueryParamHandler ) Handle ( c * Context ) error {
println ( p . Param . Age )
c . SetBody ( p . Param . Username )
return nil
}Запросные тела JSON могут быть проанализированы аналогично параметрам запроса, но тело JSON может быть произвольной структурой.
type BodyParamHandler struct {
Body struct {
Username string
Age int
Preferences [] string
Comments [] struct {
OwnerID int
Message string
}
}
}
func ( p * BodyParamHandler ) Handle ( c * Context ) error {
println ( p . Body . Age )
c . SetBody ( p . Body . Preferences + len ( c . Body . Comments ))
return nil
} Обратите внимание, что Body и Form не могут присутствовать в одном и том же обработке, поскольку гонгулярный путает, что делать с образованием запроса.
type formHandler struct {
Form struct {
Age int
Name string
Favorite string
Fraction float64
}
}
func ( q * formHandler ) Handle ( c * Context ) error {
c . SetBody ( fmt . Sprintf ( "%d:%s:%s:%.2f" ,
q . Form . Age , q . Form . Name , q . Form . Favorite , q . Form . Fraction ))
return nil
}
e . GetRouter (). POST ( "/submit" , & formHandler {}) Для загруженных файлов мы используем специальную структуру, чтобы удерживать их в значении формы stuctrad. UploadedFile удерживает multipart.File и multipart.Header , вы можете сделать с ними все, что захотите.
type UploadedFile struct {
File multipart. File
Header * multipart. FileHeader
}Вы можете использовать его в обработке, как следующее:
type formUploadTest struct {
Form struct {
SomeFile * UploadedFile
RegularValue int
}
}
func ( f * formUploadTest ) Handle ( c * Context ) error {
s := sha256 . New ()
io . Copy ( s , f . Form . SomeFile . File )
resp := fmt . Sprintf ( "%x:%d" , s . Sum ( nil ), f . Form . RegularValue )
c . SetBody ( resp )
return nil
}
e . GetRouter (). POST ( "/upload" , & formUploadTest {})Маршруты могут иметь несколько обработчиков, называемых промежуточным программным обеспечением, которое может быть полезно для группирования запросов и выполнения предварительной работы перед некоторыми маршрутами. Например, следующая группировка и маршрутизация действительны:
type simpleHandler struct {}
func ( s * simpleHandler ) Handle ( c * Context ) error {
c . SetBody ( "hi" )
return nil
}
// The middle ware that will fail if you supply 5 as a user ID
type middlewareFailIfUserId5 struct {
Param struct {
UserID int
}
}
func ( m * middlewareFailIfUserId5 ) Handle ( c * Context ) error {
if m . Param . UserID == 5 {
c . Status ( http . StatusTeapot )
c . SetBody ( "Sorry" )
c . StopChain ()
}
return nil
}
r := e . GetRouter ()
g := r . Group ( "/api/user/:UserID" , & middlewareFailIfUserId5 {})
g . GET ( "/name" , & simpleHandler {})
g . GET ( "/wow" , & simpleHandler {})
/*
The example responses:
/api/user/5/name -> Sorry
/api/user/4/name -> hi
/api/user/1/wow -> hi
*/ Мы используем Asaskevich/Govalidator в качестве структуры проверки. Если предоставленный вход не проходит шаг проверки, HTTP.StatusBadrequest (400) возвращается пользователю с причиной. Валидация может использоваться в входах запроса, параметра, тела или формы. Пример можно увидеть следующим образом:
type QueryParamHandler struct {
Query struct {
Username string `valid:"alpha"`
Age int
}
}
func ( p * QueryParamHandler ) Handle ( c * Context ) error {
println ( p . Param . Age )
c . SetBody ( p . Param . Username )
return nil
} Если задан запрос с не допустимым поле пользователя, он возвращает ParseError .
Одна из вещей, которая делает гонгулярные из других рамок, заключается в том, что он обеспечивает впрыскивание безопасной стоимости для обработчиков маршрутов. Его можно использовать для хранения подключений к базе данных или какой -либо другой внешней утилиты, которую вы хотите, чтобы это было доступно в вашем обработчике, но не хотите делать его глобальным или просто получить его от какой -либо другой глобальной функции, которая может загрязнять пространство. Поставляемые зависимости предоставляются как есть, чтобы получить обработчики маршрутов, и они являются частными для поставленного маршрутизатора, нет ничего глобального.
Gongular обеспечивает очень базовую инъекцию: вы предоставляете ценность для гонгулярного. Engine, и он предоставляет вам ваш обработчик, если вы хотите его в функции обработчика. Это не похоже на инъекцию, подобную пружине, он не разрешает зависимости инъекций, он просто обеспечивает значение, так что вы не используете глобальные значения, и это облегчает тестирование, поскольку вы можете просто проверить функцию обработчика, издеваясь от интерфейсов, которые вам нравятся.
type myHandler struct {
Param struct {
UserID uint
}
Database * sql. DB
}
func ( i * myHandler ) Handle ( c * Context ) error {
c . SetBody ( fmt . Sprintf ( "%p:%d" , i . Database , i . Param . UserID ))
return nil
}
db := new (sql. DB )
e . Provide ( db )
e . GetRouter (). GET ( "/my/db/interaction/:UserID" , & myHandler {})Основная инъекция отлично работает, но если вы хотите предоставить одинаковую стоимость более одного раза, вам нужно использовать ключ к ключевой инъекции, чтобы гонгулярная была отличаться.
type injectKey struct {
Val1 int `inject:"val1"`
Val2 int `inject:"val2"`
}
func ( i * injectKey ) Handle ( c * Context ) error {
c . SetBody ( i . Val1 * i . Val2 )
return nil
}
e . ProvideWithKey ( "val1" , 71 )
e . ProvideWithKey ( "val2" , 97 )
e . GetRouter (). GET ( "/" , & injectKey {}) Иногда предоставление ценностей, как это может быть недостаточно для вас. Вы можете выбрать пинг базы данных, создать транзакцию, получить значение из пула, и они требуют реализации пользовательской логики. Gongular позволяет написать CustomProvideFunction , которая позволяет вам предоставлять предпочтительную ценность с любой логикой, которая вам нравится.
type injectCustom struct {
DB * sql. DB
}
func ( i * injectCustom ) Handle ( c * Context ) error {
c . SetBody ( fmt . Sprintf ( "%p" , i . DB ))
return nil
}
e := newEngineTest ()
var d * sql. DB
e . CustomProvide ( & sql. DB {}, func ( c * Context ) ( interface {}, error ) {
d = new (sql. DB )
return d , nil
})
e . GetRouter (). GET ( "/" , & injectCustom {}) Функции Provide умолчанию позволяют вводить только реализации. Инъекция интерфейсов не будет работать. Во время инъекции инжектор будет искать предоставленный тип и сбой. Например, следующий код не будет работать:
type injectKey struct {
DB MySQLInterface `inject:"db"`
}
func ( i * injectKey ) Handle ( c * Context ) error {
c . SetBody ( "yay" )
return nil
}
e . ProvideWithKey ( "db" , & sql. DB {})
e . GetRouter (). GET ( "/" , & injectKey {}) Это приведет к ошибке инжектора. Если вы хотите вводить интерфейсы, вы должны использовать ProvideUnsafe . ProvideUnsafe - это строгая впрыска ключей/значения. Вы не можете предоставить несколько значений для одного и того же ключа.
Пример использования:
type injectKey struct {
DB MySQLInterface `inject:"db"`
}
func ( i * injectKey ) Handle ( c * Context ) error {
c . SetBody ( "yay" )
return nil
}
e . ProvideUnsafe ( "db" , initializeDB ())
// This would cause a panic
// e.ProvideUnsafe("db", &sql.DB{})
e . GetRouter (). GET ( "/" , & injectKey {})context.SetBody(interface{}) : устанавливает тело ответа на сериализованную.context.Status(int) : устанавливает статус ответа, если не установлен ранееcontext.MustStatus(int) : переопределяет ранее написанный статусcontext.Request() : возвращает базовый http -запрос.context.Header(string,string) : устанавливает заданный заголовок ответа.context.Finalize() : используется для написания ответа на клиента, обычно не следует использовать, кроме как в Panichandler, поскольку гонгулярные заботятся о ответе.context.Logger() : возвращает регистратор контекста. Обратный вызов маршрута, установленная глобально для двигателя, позволяет получить статистику для выполненного запроса. Он содержит общую информацию, включая журналы запросов и подходящие обработчики, сколько времени занимало в каждом обработчике, общее время, общий размер ответа, написанный и окончательный код состояния, который может быть полезен для отправки ее в другую службу мониторинга, или просто некоторый Elasticsearch для анализа журнала.
type RouteStat struct {
Request * http. Request
Handlers [] HandlerStat
MatchedPath string
TotalDuration time. Duration
ResponseSize int
ResponseCode int
Logs * bytes. Buffer
} В случае, если вы возвращаете ошибку из вашей функции, или возникает другая ошибка, которая делает запрос неудовлетворительным, gongular.Engine вызывает функцию обработчика ошибок, в которой по умолчанию следующий обработчик:
var defaultErrorHandler = func ( err error , c * Context ) {
c . logger . Println ( "An error has occurred:" , err )
switch err := err .( type ) {
case InjectionError :
c . MustStatus ( http . StatusInternalServerError )
c . logger . Println ( "Could not inject the requested field" , err )
case ValidationError :
c . MustStatus ( http . StatusBadRequest )
c . SetBody ( map [ string ] interface {}{ "ValidationError" : err })
case ParseError :
c . MustStatus ( http . StatusBadRequest )
c . SetBody ( map [ string ] interface {}{ "ParseError" : err })
default :
c . SetBody ( err . Error ())
c . MustStatus ( http . StatusInternalServerError )
}
c . StopChain ()
} Gongular также поддерживает соединения WebSocket. Функция обработчика аналогична регулярному интерфейсу обработчика маршрута, но также позволяет завершить подключение, если вы хотите с помощью Before .
type WebsocketHandler interface {
Before ( c * Context ) (http. Header , error )
Handle ( conn * websocket. Conn )
}Прежде всего, функция обработки не возвращает ошибку, поскольку это непрерывное выполнение. Пользователь несет ответственность за все взаимодействие WebSocket. Во -вторых, прежде чем он применяется непосредственно перед обновлением запроса на WebSocket. Это может быть полезно для фильтрации запроса, и возврат ошибки не откроет WebSocket, но закройте его ошибкой. HTTP.Header для ответа с помощью HTTP.Header, который позволяет установить файл cookie. Может быть пропущен, если не желательно.
Хорошая вещь о WebSockethandler заключается в том, что он также поддерживает запросы PARAM и запроса, так что все привязки и проверка можно сделать до запроса, и вы можете использовать его в своем обработчике.
type wsTest struct {
Param struct {
UserID int
}
Query struct {
Track bool
Username string
}
}
func ( w * wsTest ) Before ( c * Context ) (http. Header , error ) {
return nil , nil
}
func ( w * wsTest ) Handle ( conn * websocket. Conn ) {
_ , msg , err := conn . ReadMessage ()
if err != nil {
conn . Close ()
}
toSend := fmt . Sprintf ( "%s:%d:%s:%t" , msg , w . Param . UserID , w . Query . Username , w . Query . Track )
conn . WriteMessage ( websocket . TextMessage , [] byte ( toSend ))
conn . Close ()
}