Catatan: Gongular baru -baru ini diperbarui, dan jika Anda mencari versi sebelumnya, itu ditandai sebagai v.1.0
Gongular adalah kerangka kerja server HTTP untuk mengembangkan API dengan mudah. Ini seperti gin gonik, tetapi fitur injeksi ketergantungan seperti sudut (atau seperti musim semi) dan penanganan input yang lebih baik. Sebagian besar waktu, input pengguna harus diubah menjadi data terstruktur maka harus divalidasi. Dibutuhkan terlalu banyak waktu dan merupakan pekerjaan yang berulang, Gongular bertujuan untuk mengurangi kompleksitas itu dengan memberikan pemetaan input permintaan dengan validasi berbasis tag.
Catatan: Gongular adalah kerangka kerja yang bertentangan dan sangat bergantung pada refleksi untuk mencapai fungsi ini. Meskipun ada tes untuk memastikannya bekerja dengan sempurna, saya terbuka untuk kontribusi dan pendapat tentang cara membuatnya lebih baik.
Gongular bertujuan untuk menjadi sederhana sebanyak mungkin sambil memberikan fleksibilitas. Contoh di bawah ini cukup untuk membalas pengguna dengan IP -nya.
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" ) Semua penangan http di gongular adalah struct dengan Handle(c *gongular.Context) error atau dengan kata lain antarmuka RequestHandler , diimplementasikan. Permintaan objek penangan fleksibel. Mereka dapat memiliki berbagai bidang, di mana beberapa bidang dengan nama spesifik adalah khusus. Misalnya, jika Anda ingin mengikat parameter jalur, objek pawang Anda harus memiliki bidang bernama Param yang merupakan struct datar. Anda juga dapat memiliki bidang Query yang juga memetakan parameter kueri. Bidang Body memungkinkan Anda memetakan ke JSON Body, dan Form Field memungkinkan Anda mengikat ke dalam pengiriman formulir dengan file.
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
}Kami menggunakan julienschmidt/httprouter untuk permintaan multipleks dan melakukan pengikatan parametrik ke permintaan. Jadi format: variablename, *Somepath didukung di jalur. Perhatikan bahwa, Anda dapat menggunakan tag struct yang valid untuk memvalidasi parameter.
type PathParamHandler struct {
Param struct {
Username string
}
}
func ( p * PathParamHandler ) Handle ( c * Context ) error {
c . SetBody ( p . Param . Username )
return nil
} Parameter kueri sangat mirip dengan parameter jalur, satu -satunya perbedaan nama bidang harus Query dan juga harus menjadi struct datar tanpa parameter atau array dalam. Param kueri peka case dan gunakan nama yang tepat dari properti struct secara default. Anda dapat menggunakan tag q struct untuk menentukan kunci parameter
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
}Badan permintaan JSON dapat diuraikan mirip dengan parameter kueri, tetapi tubuh JSON bisa menjadi struct yang sewenang -wenang.
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
} Harap dicatat bahwa Body dan Form tidak dapat hadir di pawang yang sama, karena gongular akan membingungkan apa yang harus dilakukan dengan badan permintaan.
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 {}) Untuk file yang diunggah, kami menggunakan struct khusus untuk menahannya dalam nilai formulir dari struct permintaan. UploadedFile memegang multipart.File dan multipart.Header , Anda dapat melakukan apa pun yang Anda inginkan dengan mereka.
type UploadedFile struct {
File multipart. File
Header * multipart. FileHeader
}Anda dapat menggunakannya di pawang seperti berikut:
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 {})Rute dapat memiliki beberapa penangan, yang disebut middleware, yang mungkin berguna dalam mengelompokkan permintaan dan melakukan pekerjaan awal sebelum beberapa rute. Misalnya, pengelompokan dan perutean berikut ini valid:
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
*/ Kami menggunakan Asaskevich/Govalidator sebagai kerangka kerja validasi. Jika input yang disediakan tidak melewati langkah validasi, http.statusBadRequest (400) dikembalikan pengguna dengan penyebabnya. Validasi dapat digunakan dalam input kueri, param, bodi atau bentuk. Contoh dapat dilihat sebagai berikut:
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
} Jika permintaan dengan bidang nama pengguna yang tidak valid ditetapkan, ia mengembalikan ParseError .
Salah satu hal yang membuat gongular dari kerangka kerja lain adalah memberikan injeksi nilai yang aman untuk merutekan penangan. Ini dapat digunakan untuk menyimpan koneksi basis data, atau beberapa utilitas eksternal lainnya yang Anda inginkan yang tersedia di pawang Anda, tetapi tidak ingin membuatnya global, atau hanya mendapatkannya dari beberapa fungsi global lain yang mungkin mencemari ruang. Ketergantungan yang disediakan disediakan apa adanya untuk merutekan penangan dan mereka pribadi untuk router yang disediakan, tidak ada yang global.
Gongular memungkinkan suntikan yang sangat mendasar: Anda memberikan nilai kepada gongular.Engine, dan memberikan Anda kepada pawang Anda jika Anda menginginkannya dalam fungsi penangan Anda. Ini tidak seperti guice atau musim semi seperti injeksi, itu tidak menyelesaikan ketergantungan dari suntikan, itu hanya memberikan nilainya, sehingga Anda tidak menggunakan nilai global, dan itu membuat pengujian lebih mudah, karena Anda dapat menguji fungsi penangan Anda dengan mengejek antarmuka yang Anda sukai.
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 {})Injeksi dasar bekerja dengan baik, tetapi jika Anda ingin memasok jenis nilai yang sama lebih dari sekali, Anda harus menggunakan injeksi kunci sehingga gongular dapat berbeda.
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 {}) Terkadang, memberikan nilai sebagaimana adanya tidak cukup untuk Anda. Anda dapat memilih untuk melakukan ping database, membuat transaksi, mendapatkan nilai dari kumpulan, dan ini memerlukan penerapan logika khusus. Gongular memungkinkan Anda untuk menulis fungsi CustomProvideFunction yang memungkinkan Anda memberikan nilai pilihan Anda dengan logika apa pun yang Anda sukai.
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 {}) Fungsi Provide default memungkinkan Anda untuk menyuntikkan implementasi saja. Injeksi antarmuka tidak akan berhasil. Selama injeksi, injektor akan mencari jenis yang disediakan dan gagal. Misalnya kode berikut tidak akan berfungsi:
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 {}) Ini akan menyebabkan kesalahan injektor. Jika Anda ingin menyuntikkan antarmuka, Anda harus menggunakan ProvideUnsafe . ProvideUnsafe adalah injeksi kunci/nilai yang ketat. Anda tidak dapat memberikan beberapa nilai untuk kunci yang sama.
Contoh Penggunaan:
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{}) : Mengatur badan respons yang akan diserialisasi.context.Status(int) : Menetapkan status respons jika sebelumnya tidak disetelcontext.MustStatus(int) : menimpa status tertulis sebelumnyacontext.Request() : Mengembalikan permintaan HTTP mentah yang mendasaricontext.Header(string,string) : Mengatur header respons yang diberikan.context.Finalize() : Digunakan untuk menulis respons terhadap klien, biasanya tidak boleh digunakan selain di Panichandler karena Gongular menangani respons.context.Logger() : Mengembalikan logger konteks. Panggilan balik rute, yang ditetapkan secara global untuk mesin, memungkinkan Anda untuk mendapatkan statistik untuk permintaan yang telah selesai. Ini berisi info umum, termasuk log permintaan dan penangan yang cocok, berapa banyak waktu yang dibutuhkan di setiap penangan, total waktu, ukuran total respons yang ditulis dan kode status akhir, yang dapat berguna bagi Anda untuk mengirimkannya ke layanan pemantauan lain, atau hanya beberapa penelitian elastics untuk analisis log.
type RouteStat struct {
Request * http. Request
Handlers [] HandlerStat
MatchedPath string
TotalDuration time. Duration
ResponseSize int
ResponseCode int
Logs * bytes. Buffer
} Jika Anda mengembalikan kesalahan dari fungsi Anda, atau kesalahan lain terjadi yang membuat permintaan tidak memuaskan, gongular.Engine memanggil fungsi penangan kesalahan, di mana default ke pawang berikut:
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 mendukung koneksi WebSocket juga. Fungsi penangan mirip dengan antarmuka rute rute reguler, tetapi juga memungkinkan penghentian koneksi jika Anda mau dengan pawang Before .
type WebsocketHandler interface {
Before ( c * Context ) (http. Header , error )
Handle ( conn * websocket. Conn )
}Pertama -tama, fungsi pegangan tidak mengembalikan kesalahan, karena ini merupakan eksekusi yang berkelanjutan. Pengguna bertanggung jawab atas semua interaksi WebSocket. Kedua, sebelumnya adalah filter yang diterapkan tepat sebelum meningkatkan permintaan ke WebSocket. Ini dapat berguna untuk memfilter permintaan dan mengembalikan kesalahan tidak akan membuka websocket tetapi tutup dengan kesalahan. HTTP.Header adalah untuk menjawab dengan http.header yang memungkinkan pengaturan cookie. Dapat dihilangkan jika tidak diinginkan.
Hal yang menyenangkan tentang WebSockethandler adalah bahwa ia mendukung permintaan Param dan kueri juga, sehingga semua pengikatan dan validasi dapat dilakukan sebelum permintaan, dan Anda dapat menggunakannya di pawang Anda.
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 ()
}