Nota: Gongular recentemente atualizado e, se você estiver procurando a versão anterior, ele é marcado como v.1.0
O gongular é uma estrutura de servidor HTTP para desenvolver APIs facilmente. É como o Gin Gonic, mas apresenta injeção de dependência do tipo angular (ou como mola) e melhor manuseio de insumos. Na maioria das vezes, a entrada do usuário deve ser transformada em dados estruturados e deve ser validada. Leva muito tempo e é um trabalho repetitivo, o Gongular tem como objetivo reduzir essa complexidade, fornecendo mapeamento de entrada de solicitação com validação baseada em tags.
Nota: O gongular é uma estrutura opinativa e depende fortemente da reflexão para alcançar essas funcionalidades. Embora existam testes para garantir que funcione perfeitamente, estou aberto a contribuições e opiniões sobre como torná -lo melhor.
O gongular visa ser simples o máximo possível, proporcionando flexibilidade. O exemplo abaixo é suficiente para responder ao usuário com seu 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" ) Todos os manipuladores HTTP no gongular são estruturas com a função Handle(c *gongular.Context) error ou, em outras palavras, a interface RequestHandler , implementada. Os objetos de manipulador de solicitação são flexíveis. Eles podem ter vários campos, onde alguns dos campos com nomes específicos são especiais. Por exemplo, se você deseja vincular os parâmetros do caminho, seu objeto de manipulador deve ter um campo de nome nomeado Param que é uma estrutura plana. Além disso, você pode ter um campo Query que também mapeia os parâmetros de consulta. O campo Body permite mapear para o corpo JSON e o campo Form permite que você se liga aos envios de formulário com arquivos.
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
}Utilizamos Julienschmidt/httprouter para solicitações multiplex e fazemos uma ligação paramétrica às solicitações. Portanto, o formato: VariableName, *algum caminho é suportado em caminhos. Observe que você pode usar a tag struct válida para validar parâmetros.
type PathParamHandler struct {
Param struct {
Username string
}
}
func ( p * PathParamHandler ) Handle ( c * Context ) error {
c . SetBody ( p . Param . Username )
return nil
} O parâmetro de consulta é muito semelhante aos parâmetros do caminho, a única diferença que o nome do campo deve ser Query e também deve ser uma estrutura plana, sem parâmetros ou matrizes internos. Os parâmetros de consulta são sensíveis ao caso e usam o nome exato da propriedade STRUT por padrão. Você pode usar a tag q da estrutura para especificar a chave do parâmetro
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
}Os órgãos de solicitação JSON podem ser analisados semelhantes aos parâmetros de consulta, mas o corpo JSON pode ser uma estrutura arbitrária.
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
} Observe que Body e Form não podem estar presentes no mesmo manipulador, pois o gongular confundiria o que fazer com o corpo de solicitação.
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 {}) Para arquivos enviados, usamos uma estrutura especial para mantê -los no valor de formulário da estrutura da solicitação. UploadedFile mantém o multipart.File e o multipart.Header , você pode fazer o que quiser com eles.
type UploadedFile struct {
File multipart. File
Header * multipart. FileHeader
}Você pode usá -lo no manipulador como o seguinte:
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 {})As rotas podem ter vários manipuladores, chamados middleware, que podem ser úteis para agrupar as solicitações e fazer trabalhos preliminares antes de algumas rotas. Por exemplo, o seguinte agrupamento e roteamento são válidos:
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
*/ Utilizamos asaskevich/GoValidator como uma estrutura de validação. Se a entrada fornecida não passar na etapa de validação, http.statusbadrequest (400) será devolvido o usuário com a causa. A validação pode ser usada em entradas de consulta, parâmetro, corpo ou tipo de formulário. Um exemplo pode ser visto da seguinte maneira:
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
} Se uma solicitação com um campo de nome de usuário não válido estiver definido, ele retornará um ParseError .
Uma das coisas que faz do gongular de outras estruturas é que ele fornece injeção de valor seguro para rotear os manipuladores. Ele pode ser usado para armazenar conexões de banco de dados, ou algum outro utilitário externo que você deseja que isso seja avilizado em seu manipulador, mas não deseja torná -lo global ou apenas obtê -lo de outra função global que possa poluir o espaço. As dependências fornecidas são fornecidas como é para os manipuladores de rotear e são privados para o roteador fornecido, nada é global.
O gongular permite injeção muito básica: você fornece um valor ao gongular.engine e isso fornece ao seu manipulador se desejar na sua função manipuladora. Não é como uma injeção de guice ou mola, não resolve dependências das injeções, apenas fornece o valor, para que você não use valores globais e facilite o teste, pois você pode testar sua função de manipulador zombando das interfaces que você gosta.
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 {})A injeção básica funciona muito bem, mas se você deseja fornecer o mesmo tipo de valor mais de uma vez, deve usar a injeção de chave para que o gongular possa diferir.
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 {}) Às vezes, fornecer valores como é pode não ser suficiente para você. Você pode optar por ping no banco de dados, criar uma transação, obter um valor de um pool e isso requer implementar uma lógica personalizada. O Gongular permite que você escreva uma CustomProvideFunction que permite fornecer seu valor preferido com qualquer lógica que desejar.
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 {}) O padrão Provide funções que permitem injetar implementações apenas. A injeção de interfaces não funcionará. Durante a injeção, o injetor procurará um tipo fornecido e falhará. Por exemplo, o código a seguir não funcionará:
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 {}) Isso causará um erro de injetor. Se você deseja injetar interfaces, você deve usar ProvideUnsafe . ProvideUnsafe é uma injeção rigorosa de chave/valor. Você não pode fornecer vários valores para a mesma chave.
Exemplo de uso:
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{}) : define o corpo de resposta a ser serializado.context.Status(int) : define o status da resposta se não for definido anteriormentecontext.MustStatus(int) : substitui o status anteriormente escritocontext.Request() : Retorna a solicitação HTTP bruta subjacentecontext.Header(string,string) : define um determinado cabeçalho de resposta.context.Finalize() : Usado para escrever a resposta ao cliente, normalmente não deve ser usado além de Panichandler, pois o gongular cuida da resposta.context.Logger() : Retorna o registro do contexto. O retorno de chamada da rota, definido globalmente para o mecanismo, permite obter as estatísticas para a solicitação preenchida. Ele contém informações comuns, incluindo os registros de solicitação e os manipuladores correspondentes, quanto tempo levou em cada manipulador, o tempo total, o tamanho total da resposta escrito e o código de status final, que pode ser útil para você enviá -lo para outro serviço de monitoramento ou apenas alguma pesquisa de elastical para análise de logs.
type RouteStat struct {
Request * http. Request
Handlers [] HandlerStat
MatchedPath string
TotalDuration time. Duration
ResponseSize int
ResponseCode int
Logs * bytes. Buffer
} Caso você retorne um erro da sua função, ou ocorra outro erro, o que torna a solicitação insatisfeita, gongular.Engine . O motor chama a função do manipulador de erros, na qual os padrões do manipulador a seguir:
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 ()
} O gongular também suporta conexões da WebSocket. A função manipuladora é semelhante à interface de manipulador de rota regular, mas também permite a terminação da conexão, se desejar com o manipulador Before .
type WebsocketHandler interface {
Before ( c * Context ) (http. Header , error )
Handle ( conn * websocket. Conn )
}Primeiro de tudo, a função Handle não retorna um erro, pois é uma execução contínua. O usuário é responsável por toda a interação do WebSocket. Em segundo lugar, antes é um filtro aplicado imediatamente antes de atualizar a solicitação para o WebSocket. Pode ser útil para filtrar a solicitação e retornar um erro não abriria um WebSocket, mas fechará com um erro. O http.Header é para responder com um http.header que permite definir um cookie. Pode ser omitido se não for desejado.
O bom do WebSocketHandler é que ele também suporta solicitações de param e consulta, para que toda a ligação e validação possam ser feitas antes da solicitação e você pode usá -lo no seu manipulador.
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 ()
}