Remarque: Gongular récemment mis à jour, et si vous recherchez la version précédente, il est marqué comme V.1.0
Gongular est un cadre de serveur HTTP pour développer facilement des API. C'est comme Gin Gonic, mais il présente une injection de dépendance de type angulaire (ou de ressort) et une meilleure manipulation des entrées. La plupart du temps, l'entrée de l'utilisateur doit être transformée en données structurées, puis elle doit être validée. Il faut trop de temps et est un travail répétitif, Gongular vise à réduire cette complexité en fournissant une cartographie d'intervention de demande avec la validation basée sur les balises.
Remarque: Gongular est un cadre d'opinion et il repose fortement sur la réflexion pour réaliser ces fonctionnalités. Bien qu'il existe des tests pour s'assurer que cela fonctionne parfaitement, je suis ouvert aux contributions et aux opinions sur la façon de l'améliorer.
Gongular vise à être aussi simple que possible tout en offrant une flexibilité. L'exemple ci-dessous est suffisant pour répondre à l'utilisateur avec son 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" ) Tous les gestionnaires HTTP en gongular sont des structures avec une fonction Handle(c *gongular.Context) error ou en d'autres termes, interface RequestHandler , implémentée. Les objets de gestionnaire de demandes sont flexibles. Ils peuvent avoir divers champs, où certains champs avec des noms spécifiques sont spéciaux. Par exemple, si vous souhaitez lier les paramètres de chemin, votre objet de gestionnaire doit avoir un champ nommé Param qui est une structure plate. Vous pouvez également avoir un champ Query qui mappe également aux paramètres de requête. Le champ Body vous permet de mapper à JSON Body, et Form Field vous permet de vous lier aux soumissions de formulaire avec des fichiers.
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
}Nous utilisons Julienschmidt / httprouter pour multiplexer les demandes et faire la liaison paramétrique aux demandes. Ainsi, le format: variableName, * Somepath est pris en charge dans les chemins. Notez que vous pouvez utiliser la balise Struct valide pour valider les paramètres.
type PathParamHandler struct {
Param struct {
Username string
}
}
func ( p * PathParamHandler ) Handle ( c * Context ) error {
c . SetBody ( p . Param . Username )
return nil
} Le paramètre de requête est très similaire aux paramètres de chemin, la seule différence que le nom de champ doit être Query et il doit également être une structure plate sans paramètres intérieurs ni tableaux. Les paramètres de requête sont sensibles à la casse et utilisent le nom exact de la propriété struct par défaut. Vous pouvez utiliser la balise q struct pour spécifier la touche de paramètre
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
}Les corps de demande JSON peuvent être analysés similaires aux paramètres de requête, mais le corps JSON peut être une structure arbitraire.
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
} Veuillez noter que Body et Form ne peuvent pas être à la fois présents dans le même gestionnaire, car le gongulaire confondre ce qu'il faut faire avec le corps de la demande.
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 {}) Pour les fichiers téléchargés, nous utilisons une structure spéciale pour les maintenir dans la valeur du formulaire de la structure de demande. UploadedFile détient le multipart.File et le multipart.Header , vous pouvez faire tout ce que vous voulez avec eux.
type UploadedFile struct {
File multipart. File
Header * multipart. FileHeader
}Vous pouvez l'utiliser dans le gestionnaire comme ce qui suit:
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 {})Les itinéraires peuvent avoir plusieurs gestionnaires, appelés middleware, qui pourraient être utiles pour regrouper les demandes et effectuer des travaux préliminaires avant certains itinéraires. Par exemple, le regroupement et le routage suivant sont valides:
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
*/ Nous utilisons Asaskevich / Govalidator comme cadre de validation. Si l'entrée fournie ne passe pas l'étape de validation, http.statusbadrequest (400) est renvoyé l'utilisateur avec la cause. La validation peut être utilisée dans les entrées de requête, de param, de corps ou de forme. Un exemple peut être vu comme suit:
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 une demande avec un champ de nom d'utilisateur non valide est définie, elle renvoie un ParseError .
L'une des choses qui rendent gongulé à partir d'autres cadres est qu'il fournit une injection de valeur sûre pour acheminer les gestionnaires. Il peut être utilisé pour stocker des connexions de base de données, ou un autre utilitaire externe que vous voulez que cela soit avilable dans votre gestionnaire, mais ne voulez pas le rendre mondial, ou tout simplement l'obtenir d'une autre fonction globale qui pourrait polluer l'espace. Des dépendances fournies sont fournies en tant que gestionnaires d'acheminement et ils sont privés à routeur fourni, rien n'est mondial.
Gongular permet une injection très basique: vous fournissez une valeur à Gongular.Neget, et il vous fournit à votre gestionnaire si vous le souhaitez dans votre fonction de gestionnaire. Ce n'est pas comme une injection de guice ou de ressort, elle ne résout pas les dépendances des injections, elle fournit simplement la valeur, afin que vous n'utilisiez pas les valeurs globales, et cela facilite le test, car vous pouvez simplement tester votre fonction de gestionnaire en se moquant des interfaces que vous aimez.
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 {})L'injection de base fonctionne très bien, mais si vous souhaitez fournir le même type de valeur plus d'une fois, vous devez utiliser l'injection clé pour que le gongulaire puisse différer.
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 {}) Parfois, fournir des valeurs telles quelles peuvent ne pas vous être suffisantes. Vous pouvez choisir de cingler la base de données, de créer une transaction, d'obtenir une valeur à partir d'un pool, et celles-ci nécessitent l'implémentation d'une logique personnalisée. Gongular vous permet d'écrire uneFonction CustomProvideFunction qui vous permet de fournir votre valeur préférée avec toute logique que vous aimez.
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 {}) Les fonctions Provide par défaut vous permettent d'injecter uniquement des implémentations. L'injection d'interfaces ne fonctionnera pas. Pendant l'injection, l'injecteur recherchera un type fourni et échouera. Par exemple, le code suivant ne fonctionnera pas:
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 {}) Cela entraînera une erreur d'injecteur. Si vous souhaitez injecter des interfaces, vous devez utiliser ProvideUnsafe . ProvideUnsafe est une injection stricte de clé / valeur. Vous ne pouvez pas fournir plusieurs valeurs pour la même clé.
Exemple d'utilisation:
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{}) : définit le corps de réponse à sérialiser.context.Status(int) : définit l'état de la réponse s'il n'est pas défini auparavantcontext.MustStatus(int) : remplace l'état précédemment écritcontext.Request() : renvoie la demande HTTP brute sous-jacentecontext.Header(string,string) : définit un en-tête de réponse donné.context.Finalize() : utilisé pour écrire la réponse au client, normalement ne doit pas être utilisé autrement que dans Panichandler puisque Gongular s'occupe de la réponse.context.Logger() : renvoie l'enregistreur du contexte. Le rappel d'itinéraire, réglé à l'échelle mondiale pour le moteur, vous permet d'obtenir les statistiques de la demande terminée. Il contient des informations courantes, y compris les journaux de demande et les gestionnaires appariés, combien de temps il a pris dans chaque gestionnaire, le temps total, la taille totale de la réponse écrite et le code d'état final, qui peut vous être utile pour l'envoyer à un autre service de surveillance, ou tout simplement un élastique pour l'analyse des journaux.
type RouteStat struct {
Request * http. Request
Handlers [] HandlerStat
MatchedPath string
TotalDuration time. Duration
ResponseSize int
ResponseCode int
Logs * bytes. Buffer
} Dans le cas où vous renvoyez une erreur de votre fonction, ou une autre erreur se produit, ce qui rend la demande insatisfaisante, gongular.Engine appelle la fonction du gestionnaire d'erreur, dans laquelle par défaut le gestionnaire suivant:
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 prend également en charge les connexions WebSocket. La fonction de gestionnaire est similaire à l'interface de gestionnaire de routes régulière, mais elle permet également la terminaison de connexion si vous le souhaitez avec le gestionnaire Before .
type WebsocketHandler interface {
Before ( c * Context ) (http. Header , error )
Handle ( conn * websocket. Conn )
}Tout d'abord, la fonction de manche ne renvoie pas d'erreur, car il s'agit d'une exécution continue. L'utilisateur est responsable de toute l'interaction WebSocket. Deuxièmement, avant un filtre appliqué juste avant la mise à niveau de la demande à WebSocket. Il peut être utile pour filtrer la demande et renvoyer une erreur n'ouvrirait pas de WebSocket mais la fermera avec une erreur. Le http.header est destiné à répondre avec un http.header qui permet de définir un cookie. Peut être omis si ce n'est pas souhaité.
La bonne chose à propos de WebSockethandler est qu'elle prend également en charge les demandes de param et de requête, afin que toute la liaison et la validation puissent être effectuées avant la demande, et vous pouvez l'utiliser dans votre gestionnaire.
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 ()
}