注意: 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 ()
}