Nota: Gongular actualizado recientemente, y si está buscando la versión anterior, está etiquetada como V.1.0
Gongular es un marco de servidor HTTP para desarrollar API fácilmente. Es como Gin Gonic, pero presenta una inyección de dependencia de forma angular (o resorte) y un mejor manejo de entrada. La mayoría de las veces, la entrada del usuario debe transformarse en datos estructurados, entonces debe validarse. Tarda demasiado tiempo y es un trabajo repetitivo, Gongular tiene como objetivo reducir esa complejidad al proporcionar el mapeo de entrada de solicitud con validación basada en etiquetas.
Nota: Gongular es un marco obstinado y se basa en gran medida en la reflexión para lograr estas funcionalidades. Si bien hay pruebas para garantizar que funcione sin problemas, estoy abierto a contribuciones y opiniones sobre cómo mejorarlo.
Gongular tiene como objetivo ser lo más simple posible al tiempo que proporciona flexibilidad. El siguiente ejemplo es suficiente para responder al usuario con su 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 los controladores HTTP en gongular son estructuras con función Handle(c *gongular.Context) error o, en otras palabras, la interfaz RequestHandler , implementada. Los objetos del controlador de solicitud son flexibles. Pueden tener varios campos, donde algunos de los campos con nombres específicos son especiales. Por ejemplo, si desea unir los parámetros de ruta, su objeto de controlador debe tener un campo llamado Param , que es una estructura plana. También puede tener un campo Query que también se mapea para consultar los parámetros. Body Field le permite asignar a JSON Body, y Form Field le permite vincular a los envíos de formularios con archivos.
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 multiplex y hacemos enlace paramétrico a las solicitudes. Entonces, el formato: VariAblename, *SomePath es compatible con las rutas. Tenga en cuenta que puede usar una etiqueta de estructura válida para validar los parámetros.
type PathParamHandler struct {
Param struct {
Username string
}
}
func ( p * PathParamHandler ) Handle ( c * Context ) error {
c . SetBody ( p . Param . Username )
return nil
} El parámetro de consulta es muy similar a los parámetros de ruta, la única diferencia que el nombre del campo debe ser Query y también debe ser una estructura plana sin parámetros o matrices internos. Los parámetros de consulta son sensibles a mayúsculas y usan el nombre exacto de la propiedad Struct de forma predeterminada. Puede usar la etiqueta q struct para especificar la tecla de 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
}Los cuerpos de solicitud de JSON se pueden analizar de manera similar a los parámetros de consulta, pero el cuerpo JSON puede ser una estructura arbitraria.
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
} Tenga en cuenta que Body y Form no pueden estar presentes en el mismo controlador, ya que el gongular confundiría qué hacer con el cuerpo de solicitud.
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 los archivos cargados, utilizamos una estructura especial para mantenerlos en el valor de formulario de la estructura de solicitud. UploadedFile contiene el multipart.File y el multipart.Header .
type UploadedFile struct {
File multipart. File
Header * multipart. FileHeader
}Puedes usarlo en el controlador como el siguiente:
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 {})Las rutas pueden tener múltiples manejadores, llamados middleware, que podrían ser útiles para agrupar las solicitudes y hacer un trabajo preliminar antes de algunas rutas. Por ejemplo, la siguiente agrupación y enrutamiento es válido:
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 marco de validación. Si la entrada suministrada no pasa el paso de validación, http.statusbadrequest (400) se devuelve al usuario con la causa. La validación se puede usar en entradas de consulta, parámetro, cuerpo o tipo de formulario. Un ejemplo se puede ver de la siguiente manera:
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
} Si se establece una solicitud con un campo de nombre de usuario no válido, devuelve un ParseError .
Una de las cosas que hace gongular desde otros marcos es que proporciona inyección de valor seguro a los manejadores de ruta. Se puede utilizar para almacenar conexiones de bases de datos, o alguna otra utilidad externa que desea que sea ágil en su manejador, pero no quiere hacerlo global, o simplemente obtener de alguna otra función global que pueda contaminar el espacio. Las dependencias suministradas se proporcionan como AS a los manejadores de ruta y son privados a enrutadores suministrados, nada es global.
Gongular permite una inyección muy básica: proporciona un valor a Gongular.Engine, y le proporciona a su controlador si lo desea en su función de controlador. No es como una inyección de Guice o Spring, no resuelve las dependencias de las inyecciones, solo proporciona el valor, por lo que no usa valores globales y facilita las pruebas, ya que puede probar su función de controlador burlándose de las interfaces que le gusta.
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 {})La inyección básica funciona muy bien, pero si desea suministrar el mismo tipo de valor más de una vez, debe usar la inyección clave para que gongular pueda 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 {}) A veces, proporcionar valores como es podría no ser suficiente para usted. Puede optar por hacer ping a la base de datos, crear una transacción, obtener un valor de un grupo y esto requiere implementar una lógica personalizada. Gongular le permite escribir un CustomProvideFunction que le permite proporcionar su valor preferido con cualquier lógica que desee.
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 {}) Las funciones Provide predeterminadas le permiten inyectar implementaciones solamente. La inyección de interfaces no funcionará. Durante la inyección, el inyector buscará un tipo proporcionado y fallará. Por ejemplo, el siguiente código no 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 {}) Esto causará un error del inyector. Si desea inyectar interfaces, debe usar ProvideUnsafe . ProvideUnsafe es una inyección estricta de clave/valor. No puede proporcionar múltiples valores para la misma clave.
Ejemplo 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{}) : establece el cuerpo de respuesta para ser serializado.context.Status(int) : establece el estado de la respuesta si no se establece previamentecontext.MustStatus(int) : anula el estado escrito anteriormentecontext.Request() : Devuelve la solicitud HTTP sin procesar subyacentecontext.Header(string,string) : establece un encabezado de respuesta dado.context.Finalize() : Se utiliza para escribir la respuesta al cliente, normalmente no debe usarse aparte de Panichandler ya que gongular se encarga de la respuesta.context.Logger() : devuelve el registrador del contexto. La devolución de llamada de la ruta, establecida a nivel mundial para el motor, le permite obtener las estadísticas para la solicitud completa. Contiene información común, incluidos los registros de solicitudes y los manejadores coincidentes, cuánto tiempo tomó en cada manejador, el tiempo total, el tamaño de respuesta total escrito y el código de estado final, que puede ser útil para que lo envíe a otro servicio de monitoreo, o simplemente algunos ElasticseSearch para el análisis de registros.
type RouteStat struct {
Request * http. Request
Handlers [] HandlerStat
MatchedPath string
TotalDuration time. Duration
ResponseSize int
ResponseCode int
Logs * bytes. Buffer
} En caso de que devuelva un error de su función, u otro error se produce que hace que la solicitud sea insatisfactoria, gongular.Engine Elemento llama a la función del controlador de errores, en la que el valor predeterminado al siguiente controlador:
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 también admite las conexiones WebSocket. La función del controlador es similar a la interfaz de controlador de ruta regular, pero también permite la terminación de la conexión si lo desea con el controlador Before .
type WebsocketHandler interface {
Before ( c * Context ) (http. Header , error )
Handle ( conn * websocket. Conn )
}En primer lugar, la función del manejo no devuelve un error, ya que es una ejecución continua. El usuario es responsable de toda la interacción WebSocket. En segundo lugar, antes se aplica un filtro justo antes de actualizar la solicitud a WebSocket. Puede ser útil para filtrar la solicitud y devolver un error no abriría un WebSocket, sino que lo cerraría con un error. El http.header es para responder con un http.header que permite configurar una cookie. Se puede omitir si no se desea.
Lo bueno de WebSocketHandler es que también admite solicitudes de parámetro y consulta, de modo que se pueda hacer todo el enlace y la validación antes de la solicitud, y puede usarlo en su controlador.
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 ()
}