Gin ialah rangka kerja web HTTP yang ditulis dalam Go (Golang). Ia menampilkan API seperti Martini, tetapi dengan prestasi sehingga 40 kali lebih pantas daripada Martini. Jika anda memerlukan prestasi hebat, dapatkan Gin.
Tapak web rasmi Gin memperkenalkan dirinya sebagai rangka kerja web dengan "prestasi tinggi" dan "produktiviti yang baik". Ia juga menyebut dua perpustakaan lain. Yang pertama ialah Martini, yang juga merupakan rangka kerja web dan mempunyai nama minuman keras. Gin mengatakan ia menggunakan APInya, tetapi 40 kali lebih pantas. Menggunakan httprouter ialah sebab penting mengapa ia boleh menjadi 40 kali lebih pantas daripada Martini.
Antara "Ciri" di tapak web rasmi, lapan ciri utama disenaraikan dan kami akan melihat secara beransur-ansur pelaksanaan ciri ini kemudian.
Mari kita lihat contoh terkecil yang diberikan dalam dokumentasi rasmi.
package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 }
Jalankan contoh ini, kemudian gunakan penyemak imbas untuk melawati http://localhost:8080/ping, dan anda akan mendapat "pong".
Contoh ini sangat mudah. Ia boleh dibahagikan kepada tiga langkah sahaja:
Daripada kaedah GET dalam contoh kecil di atas, kita dapat melihat bahawa dalam Gin, kaedah pemprosesan kaedah HTTP perlu didaftarkan menggunakan fungsi yang sepadan dengan nama yang sama.
Terdapat sembilan kaedah HTTP, dan empat yang paling biasa digunakan ialah GET, POST, PUT, dan DELETE, yang sepadan dengan empat fungsi pertanyaan, memasukkan, mengemas kini dan memadam masing-masing. Perlu diingat bahawa Gin juga menyediakan antara muka Sebarang, yang boleh mengikat secara langsung semua kaedah pemprosesan kaedah HTTP kepada satu alamat.
Hasil yang dikembalikan biasanya mengandungi dua atau tiga bahagian. Kod dan mesej sentiasa ada, dan data biasanya digunakan untuk mewakili data tambahan. Jika tiada data tambahan untuk dikembalikan, ia boleh ditinggalkan. Dalam contoh, 200 ialah nilai medan kod dan "pong" ialah nilai medan mesej.
Dalam contoh di atas, gin.Default() telah digunakan untuk mencipta Enjin. Walau bagaimanapun, fungsi ini adalah pembalut untuk Baharu. Malah, Enjin dicipta melalui antara muka Baharu.
package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 }
Cukup lihat secara ringkas proses penciptaan buat masa ini, dan jangan fokus pada makna pelbagai pembolehubah ahli dalam struktur Enjin. Ia boleh dilihat bahawa selain mencipta dan memulakan pembolehubah enjin jenis Enjin, New juga menetapkan engine.pool.New kepada fungsi tanpa nama yang memanggil engine.allocateContext(). Fungsi fungsi ini akan dibincangkan kemudian.
Terdapat struktur tertanam RouterGroup dalam Enjin. Antara muka yang berkaitan dengan kaedah HTTP Enjin semuanya diwarisi daripada RouterGroup. "Penghimpunan Laluan" dalam titik ciri yang disebut di tapak web rasmi dicapai melalui struct RouterGroup.
func New() *Engine { debugPrintWARNINGNew() engine := &Engine{ RouterGroup: RouterGroup{ //... Initialize the fields of RouterGroup }, //... Initialize the remaining fields } engine.RouterGroup.engine = engine // Save the pointer of the engine in RouterGroup engine.pool.New = func() any { return engine.allocateContext() } return engine }
Setiap RouterGroup dikaitkan dengan laluan asas basePath. BasePath RouterGroup yang dibenamkan dalam Enjin ialah "/".
Terdapat juga satu set Pengendali fungsi pemprosesan. Semua permintaan di bawah laluan yang dikaitkan dengan kumpulan ini juga akan melaksanakan fungsi pemprosesan kumpulan ini, yang digunakan terutamanya untuk panggilan perisian tengah. Pengendali adalah sifar apabila Enjin dicipta, dan satu set fungsi boleh diimport melalui kaedah Use. Kita akan lihat penggunaan ini nanti.
type RouterGroup struct { Handlers HandlersChain // Processing functions of the group itself basePath string // Associated base path engine *Engine // Save the associated engine object root bool // root flag, only the one created by default in Engine is true }
Kaedah pemegang RouterGroup ialah entri terakhir untuk mendaftarkan semua fungsi panggil balik kaedah HTTP. Kaedah GET dan kaedah lain yang berkaitan dengan kaedah HTTP yang dipanggil dalam contoh awal hanyalah pembalut untuk kaedah pemegang.
Kaedah pemegang akan mengira laluan mutlak mengikut basePath RouterGroup dan parameter laluan relatif, dan pada masa yang sama memanggil kaedah combineHandlers untuk mendapatkan tatasusunan pengendali akhir. Keputusan ini dihantar sebagai parameter kepada kaedah addRoute Enjin untuk mendaftarkan fungsi pemprosesan.
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes { absolutePath := group.calculateAbsolutePath(relativePath) handlers = group.combineHandlers(handlers) group.engine.addRoute(httpMethod, absolutePath, handlers) return group.returnObj() }
Apa yang dilakukan oleh kaedah combineHandlers ialah mencipta hirisan mergedHandlers, kemudian salin Handlers RouterGroup itu sendiri ke dalamnya, kemudian salin pengendali parameter ke dalamnya, dan akhirnya kembalikan mergedHandlers. Maksudnya, apabila mendaftar sebarang kaedah menggunakan pemegang, hasil sebenar termasuk Pengendali RouterGroup itu sendiri.
Dalam titik ciri "Fast" yang disebut di laman web rasmi, disebutkan bahawa penghalaan permintaan rangkaian dilaksanakan berdasarkan pokok radix (Radix Tree). Bahagian ini tidak dilaksanakan oleh Gin, tetapi oleh httprouter yang disebut dalam pengenalan Gin pada awalnya. Gin menggunakan httprouter untuk mencapai bahagian fungsi ini. Pelaksanaan pokok radix tidak akan disebut di sini buat masa ini. Kami hanya akan fokus pada penggunaannya buat masa ini. Mungkin kita akan menulis artikel berasingan tentang pelaksanaan pokok radix nanti.
Dalam Enjin, terdapat pembolehubah pokok, yang merupakan kepingan struktur methodTree. Pembolehubah inilah yang memegang rujukan kepada semua pokok radix.
package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 }
Enjin mengekalkan pokok radix untuk setiap kaedah HTTP. Nod akar pokok ini dan nama kaedah disimpan bersama dalam pembolehubah methodTree dan semua pembolehubah methodTree berada dalam pokok.
func New() *Engine { debugPrintWARNINGNew() engine := &Engine{ RouterGroup: RouterGroup{ //... Initialize the fields of RouterGroup }, //... Initialize the remaining fields } engine.RouterGroup.engine = engine // Save the pointer of the engine in RouterGroup engine.pool.New = func() any { return engine.allocateContext() } return engine }
Ia boleh dilihat bahawa dalam kaedah addRoute Enjin, ia akan terlebih dahulu menggunakan kaedah get pokok untuk mendapatkan nod akar pokok radix yang sepadan dengan kaedah tersebut. Jika nod akar pokok radix tidak diperoleh, ini bermakna tiada kaedah telah didaftarkan untuk kaedah ini sebelum ini, dan nod pokok akan dibuat sebagai nod akar pokok dan ditambah pada pokok.
Selepas mendapat nod akar, gunakan kaedah addRoute nod akar untuk mendaftarkan satu set pengendali fungsi pemprosesan untuk laluan laluan. Langkah ini adalah untuk mencipta nod untuk laluan dan pengendali dan menyimpannya dalam pokok radix. Jika anda cuba mendaftarkan alamat yang telah didaftarkan, addRoute akan terus menimbulkan ralat panik.
Apabila memproses permintaan HTTP, adalah perlu untuk mencari nilai nod yang sepadan melalui laluan. Nod akar mempunyai kaedah getValue yang bertanggungjawab untuk mengendalikan operasi pertanyaan. Kami akan menyebut perkara ini apabila bercakap tentang Gin memproses permintaan HTTP.
Kaedah Penggunaan RouterGroup boleh mengimport satu set fungsi pemprosesan middleware. "Sokongan perisian tengah" dalam titik ciri yang disebut di tapak web rasmi dicapai melalui kaedah Gunakan.
Dalam contoh awal, apabila mencipta pembolehubah struktur Enjin, Baharu tidak digunakan, tetapi Lalai telah digunakan. Mari lihat apa yang Default lakukan tambahan.
package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 }
Ia boleh dilihat bahawa ia adalah fungsi yang sangat mudah. Selain memanggil Baharu untuk mencipta objek Enjin, ia hanya memanggil Use untuk mengimport nilai pulangan dua fungsi middleware, Logger dan Recovery. Nilai pulangan Logger ialah fungsi untuk pengelogan, dan nilai pulangan Pemulihan ialah fungsi untuk mengendalikan panik. Kami akan melangkau ini buat masa ini dan melihat dua fungsi ini kemudian.
Walaupun Enjin membenamkan RouterGroup, ia juga melaksanakan kaedah Use, tetapi ia hanyalah panggilan ke kaedah Use RouterGroup dan beberapa operasi tambahan.
func New() *Engine { debugPrintWARNINGNew() engine := &Engine{ RouterGroup: RouterGroup{ //... Initialize the fields of RouterGroup }, //... Initialize the remaining fields } engine.RouterGroup.engine = engine // Save the pointer of the engine in RouterGroup engine.pool.New = func() any { return engine.allocateContext() } return engine }
Dapat dilihat bahawa kaedah Penggunaan RouterGroup juga sangat mudah. Ia hanya menambah fungsi pemprosesan middleware bagi parameter kepada Pengendalinya sendiri melalui lampiran.
Dalam contoh kecil, langkah terakhir ialah memanggil kaedah Jalankan Enjin tanpa parameter. Selepas panggilan, keseluruhan rangka kerja mula berjalan dan melawati alamat berdaftar dengan penyemak imbas boleh mencetuskan panggilan balik dengan betul.
type RouterGroup struct { Handlers HandlersChain // Processing functions of the group itself basePath string // Associated base path engine *Engine // Save the associated engine object root bool // root flag, only the one created by default in Engine is true }
Kaedah Run hanya melakukan dua perkara: menghuraikan alamat dan mulakan perkhidmatan. Di sini, alamat sebenarnya hanya perlu melepasi rentetan, tetapi untuk mencapai kesan boleh lulus atau tidak lulus, parameter variadic digunakan. Kaedah resolveAddress mengendalikan keputusan situasi berbeza addr.
Memulakan perkhidmatan menggunakan kaedah ListenAndServe dalam pakej net/http perpustakaan standard. Kaedah ini menerima alamat pendengaran dan pembolehubah antara muka Pengendali. Takrifan antara muka Pengendali adalah sangat mudah, dengan hanya satu kaedah ServeHTTP.
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes { absolutePath := group.calculateAbsolutePath(relativePath) handlers = group.combineHandlers(handlers) group.engine.addRoute(httpMethod, absolutePath, handlers) return group.returnObj() }
Oleh kerana Enjin melaksanakan ServeHTTP, Enjin itu sendiri akan dihantar ke kaedah ListenAndServe di sini. Apabila terdapat sambungan baharu ke port yang dipantau, ListenAndServe akan bertanggungjawab untuk menerima dan mewujudkan sambungan, dan apabila terdapat data pada sambungan, ia akan memanggil kaedah ServeHTTP pengendali untuk diproses.
ServeHTTP Enjin ialah fungsi panggil balik untuk memproses mesej. Jom lihat kandungannya.
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain { finalSize := len(group.Handlers) + len(handlers) assert1(finalSize < int(abortIndex), "too many handlers") mergedHandlers := make(HandlersChain, finalSize) copy(mergedHandlers, group.Handlers) copy(mergedHandlers[len(group.Handlers):], handlers) return mergedHandlers }
Fungsi panggil balik mempunyai dua parameter. Yang pertama ialah w yang digunakan untuk menerima balasan permintaan. Tulis data balasan kepada w. Yang lain ialah req yang menyimpan data permintaan ini. Semua data yang diperlukan untuk pemprosesan seterusnya boleh dibaca dari req.
Kaedah ServeHTTP melakukan empat perkara. Mula-mula, dapatkan Konteks daripada kolam kolam, kemudian ikat Konteks kepada parameter fungsi panggil balik, kemudian panggil kaedah handleHTTPRequest dengan Konteks sebagai parameter untuk memproses permintaan rangkaian ini, dan akhirnya letakkan semula Konteks ke dalam kolam.
Mula-mula kita hanya melihat bahagian teras kaedah penanganHTTPRequest.
package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 }
Kaedah handleHTTPRequest terutamanya melakukan dua perkara. Pertama, dapatkan kaedah yang didaftarkan sebelum ini daripada pokok radix mengikut alamat permintaan. Di sini, pengendali akan diberikan kepada Konteks untuk pemprosesan ini, dan kemudian memanggil fungsi Seterusnya Konteks untuk melaksanakan kaedah dalam pengendali. Akhir sekali, tulis data pemulangan permintaan ini dalam objek jenis responseWriter bagi Konteks.
Apabila memproses permintaan HTTP, semua data berkaitan konteks berada dalam pembolehubah Konteks. Penulis juga menulis dalam ulasan struct Konteks bahawa "Konteks ialah bahagian terpenting gin", yang menunjukkan kepentingannya.
Apabila bercakap mengenai kaedah ServeHTTP Enjin di atas, dapat dilihat bahawa Konteks tidak dibuat secara langsung, tetapi diperoleh melalui kaedah Dapatkan pembolehubah kumpulan Enjin. Selepas dikeluarkan, keadaannya ditetapkan semula sebelum digunakan dan ia dimasukkan semula ke dalam kolam selepas digunakan.
Pembolehubah kumpulan Enjin adalah jenis penyegerakan.Kolam. Buat masa ini, ketahui sahaja bahawa ia adalah kumpulan objek yang disediakan oleh pegawai Go yang menyokong penggunaan serentak. Anda boleh mendapatkan objek daripada kolam melalui kaedah Dapatkannya, dan anda juga boleh meletakkan objek ke dalam kolam menggunakan kaedah Put. Apabila kolam kosong dan kaedah Dapatkan digunakan, ia akan mencipta objek melalui kaedah Baharunya sendiri dan mengembalikannya.
Kaedah Baharu ini ditakrifkan dalam kaedah Baharu Enjin. Mari kita lihat sekali lagi kaedah Baharu Enjin.
func New() *Engine { debugPrintWARNINGNew() engine := &Engine{ RouterGroup: RouterGroup{ //... Initialize the fields of RouterGroup }, //... Initialize the remaining fields } engine.RouterGroup.engine = engine // Save the pointer of the engine in RouterGroup engine.pool.New = func() any { return engine.allocateContext() } return engine }
Ia boleh dilihat daripada kod bahawa kaedah penciptaan Konteks adalah kaedah allocateContext Enjin. Tiada misteri dalam kaedah allocateContext. Ia hanya melakukan pra-peruntukan dua langkah panjang hirisan, kemudian mencipta objek dan mengembalikannya.
type RouterGroup struct { Handlers HandlersChain // Processing functions of the group itself basePath string // Associated base path engine *Engine // Save the associated engine object root bool // root flag, only the one created by default in Engine is true }
Kaedah Konteks Seterusnya yang dinyatakan di atas akan melaksanakan semua kaedah dalam pengendali. Mari kita lihat pelaksanaannya.
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes { absolutePath := group.calculateAbsolutePath(relativePath) handlers = group.combineHandlers(handlers) group.engine.addRoute(httpMethod, absolutePath, handlers) return group.returnObj() }
Walaupun pengendali adalah sekeping, kaedah Seterusnya tidak hanya dilaksanakan sebagai traversal pengendali, tetapi memperkenalkan indeks rekod kemajuan pemprosesan, yang dimulakan kepada 0, dinaikkan pada permulaan kaedah dan dinaikkan semula selepas kaedah pelaksanaan selesai.
Reka bentuk Next mempunyai hubungan yang baik dengan penggunaannya, terutamanya untuk bekerjasama dengan beberapa fungsi perisian tengah. Sebagai contoh, apabila panik dicetuskan semasa pelaksanaan pengendali tertentu, ralat boleh ditangkap menggunakan recover dalam middleware, dan kemudian Seterusnya boleh dipanggil semula untuk meneruskan pelaksanaan pengendali seterusnya tanpa menjejaskan keseluruhan tatasusunan pengendali disebabkan masalah seorang pengendali.
Dalam Gin, jika fungsi pemprosesan permintaan tertentu mencetuskan panik, keseluruhan rangka kerja tidak akan ranap secara langsung. Sebaliknya, mesej ralat akan dilemparkan, dan perkhidmatan akan terus disediakan. Ia agak serupa dengan cara rangka kerja Lua biasanya menggunakan xpcall untuk melaksanakan fungsi pemprosesan mesej. Operasi ini ialah titik ciri "bebas ranap" yang disebut dalam dokumentasi rasmi.
Seperti yang dinyatakan di atas, apabila menggunakan gin.Default untuk mencipta Enjin, kaedah Penggunaan Enjin akan dilaksanakan untuk mengimport dua fungsi. Salah satunya ialah nilai pulangan fungsi Pemulihan, yang merupakan pembalut fungsi lain. Fungsi yang dipanggil terakhir ialah CustomRecoveryWithWriter. Mari kita lihat pelaksanaan fungsi ini.
package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 }
Kami tidak menumpukan pada butiran pengendalian ralat di sini, tetapi hanya melihat apa yang dilakukannya. Fungsi ini mengembalikan fungsi tanpa nama. Dalam fungsi tanpa nama ini, satu lagi fungsi tanpa nama didaftarkan menggunakan penangguhan. Dalam fungsi tanpa nama dalaman ini, pulih digunakan untuk menangkap panik, dan kemudian pengendalian ralat dilakukan. Selepas pengendalian selesai, kaedah Seterusnya Konteks dipanggil, supaya pengendali Konteks yang pada asalnya dilaksanakan mengikut turutan boleh terus dilaksanakan.
Akhir sekali, izinkan saya memperkenalkan platform terbaik untuk menggunakan perkhidmatan Gin: Leapcell.
Terokai lagi dalam Dokumentasi!
Twitter Leapcell: https://x.com/LeapcellHQ
Atas ialah kandungan terperinci Menyelam Lebih Dalam ke dalam Gin: Rangka Kerja Utama Golang. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!