注意: Gongular最近更新了,如果您正在寻找以前的版本,则标记为v.1.0
Gongular是用于轻松开发API的HTTP服务器框架。它就像杜松子酒一样,但具有类似角的(或春季)依赖注入和更好的输入处理。在大多数情况下,必须将用户输入转换为结构化数据,然后必须对其进行验证。这需要太多时间,并且是一项重复的工作,Gongular旨在通过提供基于标签的验证的请求输入映射来降低这种复杂性。
注意:牙龈是一个自明的框架,它在很大程度上依赖于反思来实现这些功能。尽管有测试可以确保其完美无缺,但我对如何使其变得更好的贡献和意见持开放态度。
Gongular的目标是在提供灵活性的同时尽可能简单。以下示例足以用其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" )Gongular中的所有HTTP处理程序都是带有Handle(c *gongular.Context) error函数的结构,或者换句话说, RequestHandler接口,已实现。请求处理程序对象灵活。他们可以拥有各种字段,其中一些具有特定名称的字段很特别。例如,如果要绑定路径参数,则处理程序对象必须具有名为Param的字段,即平坦结构。另外,您可以拥有一个Query字段,该字段还可以映射到查询参数。 Body Field允许您映射到JSON主体,并Form字段使您可以用文件绑定到表单提交中。
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, *路径中支持某些路径。请注意,您可以使用有效的结构标签来验证参数。
type PathParamHandler struct {
Param struct {
Username string
}
}
func ( p * PathParamHandler ) Handle ( c * Context ) error {
c . SetBody ( p . Param . Username )
return nil
}查询参数与路径参数非常相似,该字段名称应该是Query唯一区别,它也应该是没有内部参数或数组的平坦结构。查询参数是案例敏感的,默认情况下使用结构属性的确切名称。您可以使用q结构标签指定参数密钥
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 {})对于上传的文件,我们使用特殊的结构将其保存在请求结构的形式值中。 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允许非常基本的注射:您为Gongular.Engine提供了价值,如果您希望在处理程序功能中,它为您提供了处理程序。它不像是guice或春季那样注射,它不能解决注射的依赖性,它只是提供值,因此您不使用全局值,并且可以使测试更加容易,因为您可以通过模拟自己喜欢的接口来测试处理程序功能。
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 {})有时,提供值可能不足以提供的值。您可以选择ping数据库,创建交易,从池中获取值,这些都需要实现自定义逻辑。 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中使用,因为Gongular会照顾响应。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用于使用允许设置cookie的http.header回答。如果不需要,可以省略。
Websockethandler的好处在于它也支持参数和查询请求,因此可以在请求之前完成所有绑定和验证,并且您可以在处理程序中使用它。
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 ()
}