Gin arxitekturasi
Gin’dan foydalanish oson: r.GET(...), c.JSON(...) va hammasi ishlaydi. Lekin kutilmagan xatti-harakat yuz berganda, masalan middleware ikki marta ishlaganda yoki goroutine ichida ma’lumotlar aralashib ketganda, Gin ichida nima bo‘layotganini bilish kerak bo‘ladi.
Bu darsda Gin’ning to‘rtta asosiy qismini ko‘rib chiqamiz: Engine, RouterGroup, Context va handlerlar zanjiri.
Umumiy sxema
http.Server
│ ServeHTTP(w, r)
↓
gin.Engine ─────────────────────────┐
│ 1. pool'dan Context oladi │ sync.Pool
│ 2. radix tree'dan marshrutni │ ┌─────────┐
│ va handlerlar zanjirini │ │ Context │
│ topadi │ │ Context │
↓ │ └─────────┘
HandlersChain: [Logger, Recovery, Auth, handler]
│ c.Next() → c.Next() → c.Next() → ...
↓
Context pool'ga qaytariladi ────────┘
gin.Engine
Engine — Gin ilovasining markazi. gin.New() yoki gin.Default() aynan shu turni qaytaradi.
r := gin.New() // bo‘sh engine, middleware'siz
r := gin.Default() // Logger va Recovery middleware'lari bilan
Engine http.Handler interfeysini qanoatlantiradi, ya’ni unda ServeHTTP metodi bor. Shu sababli Gin istalgan http.Server bilan ishlaydi:
Engine’ning muhim sozlamalari:
| Maydon | Standart qiymat | Ma’nosi |
|---|---|---|
RedirectTrailingSlash |
true |
/books/ so‘ralsa va faqat /books bo‘lsa, 301 bilan yo‘naltiradi |
HandleMethodNotAllowed |
false |
true bo‘lsa, noto‘g‘ri metod uchun 405 qaytaradi. Aks holda 404 |
ContextWithFallback |
false |
true bo‘lsa, c.Done(), c.Value() so‘rov contextiga yo‘naltiriladi |
Warning
Standart holatda Gin noto‘g‘ri metod uchun 405 emas, 404 qaytaradi. REST API uchun 405 to‘g‘riroq. Buni yoqish uchun r.HandleMethodNotAllowed = true yozing.
RouterGroup — guruhlar
RouterGroup umumiy prefiks va umumiy middleware’larga ega marshrutlar guruhi. Enginening o‘zi ham ildiz guruhni o‘z ichiga oladi, shuning uchun r.GET va r.Use metodlari aslida RouterGroup metodlari.
r := gin.New()
r.Use(gin.Logger(), gin.Recovery()) // barcha marshrutlar uchun
api := r.Group("/api/v1")
{
api.GET("/books", listBooks)
api.GET("/books/:id", getBook)
private := api.Group("")
private.Use(authRequired)
{
private.POST("/books", createBook)
private.DELETE("/books/:id", deleteBook)
}
}
{ } qavslari Go’da oddiy blok. Ular kodni o‘qishni osonlashtirish uchun Gin hamjamiyatida keng qo‘llanadi, sintaktik ahamiyatga ega emas.
gorilla/mux subrouter’laridan muhim farqi bor: Gin’da middleware’lar marshrut ro‘yxatdan o‘tkazilayotgan paytda uning handlerlar zanjiriga nusxalanadi. Shu sababli Use marshrutlardan oldin chaqirilishi kerak:
api := r.Group("/api")
api.GET("/a", handlerA) // authRequired'siz!
api.Use(authRequired)
api.GET("/b", handlerB) // authRequired bilan
/api/a marshruti himoyasiz qoldi, chunki u Use chaqirilishidan oldin ro‘yxatdan o‘tgan. Bu Gin’da keng tarqalgan va xavfli xato.
gin.Context
*gin.Context — har bir so‘rov uchun yaratiladigan obyekt. U handlerga kerakli hamma narsani birlashtiradi:
c.Request // *http.Request
c.Writer // gin.ResponseWriter (http.ResponseWriter ustida)
c.Param("id") // yo‘l parametri
c.Query("page") // query parametr
c.GetHeader("X-Request-ID")
c.ShouldBindJSON(&req) // JSON o‘qish va validatsiya
c.JSON(200, data) // JSON javob
c.Set("user", u) // middleware'lar orasida qiymat uzatish
c.Get("user")
c.Next() // zanjirdagi keyingi handler
c.Abort() // zanjirni to‘xtatish
c.Set va c.Get
Middleware’dan handlerga qiymat uzatish uchun net/httpda context.WithValue ishlatilgan edi. Gin’da buning uchun c.Set va c.Get bor:
func authRequired(c *gin.Context) {
c.Set("userID", int64(42))
c.Next()
}
func me(c *gin.Context) {
id := c.GetInt64("userID") // yoki c.MustGet("userID").(int64)
c.JSON(200, gin.H{"id": id})
}
Qiymatlar Context ichidagi map’da saqlanadi. c.Request.Context()ga esa ular tushmaydi. Shuning uchun service qatlamiga gin.Contextni emas, oddiy qiymatlarni uzating.
Handlerlar zanjiri
Gin’da middleware va handler bir xil turga ega:
Marshrut ro‘yxatdan o‘tkazilganda uning handlerlar zanjiri tuziladi: guruh middleware’lari va oxirida handlerning o‘zi. So‘rov kelganda Context zanjirdagi joriy o‘rinni index maydonida saqlaydi.
c.Next()
c.Next() zanjirdagi keyingi handlerlarni chaqiradi. Ulardan keyin boshqaruv yana joriy middleware’ga qaytadi:
func timing(c *gin.Context) {
start := time.Now()
c.Next() // keyingi barcha handlerlar shu yerda bajariladi
log.Printf("%s %d %v", c.Request.URL.Path, c.Writer.Status(), time.Since(start))
}
c.Next()dan oldingi kod so‘rov handlerga yetib borishidan oldin, keyingi kod esa javob tayyor bo‘lgach bajariladi. Bu net/httpdagi next.ServeHTTP(w, r) bilan bir xil g‘oya.
Middleware c.Next()ni chaqirmasa ham, Gin zanjirni davom ettiradi. Shu sababli "oldin" qismiga ega middleware’da c.Next()ni yozmaslik ham mumkin. Lekin "keyin" qismi bo‘lsa, c.Next() majburiy.
c.Abort()
c.Abort() zanjirning qolgan qismini bekor qiladi:
func authRequired(c *gin.Context) {
if c.GetHeader("Authorization") == "" {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "tizimga kiring"})
return
}
c.Next()
}
Danger
c.Abort() joriy funksiyani to‘xtatmaydi, u faqat keyingi handlerlarni bekor qiladi. Abortdan keyin return yozilmasa, joriy funksiya kodi bajarilishda davom etadi. net/httpdagi kabi, xato javobidan keyin doim return yozing.
Tartibni ko‘rsatuvchi misol
--- /
A: oldin
B: oldin
handler
B: keyin
A: keyin
status: 200
--- /?block=1
A: oldin
B: oldin
guard: to‘xtatildi
B: keyin
A: keyin
status: 403
Ikkinchi so‘rovda guard zanjirni to‘xtatdi va handler chaqirilmadi. Lekin tashqi middleware’larning "keyin" qismi baribir ishladi. Shu sababli logging middleware’i rad etilgan so‘rovlarni ham yozib qoladi.
Routing: radix tree
Gin marshrutlarni har bir HTTP metodi uchun alohida radix tree'da saqlaydi. Bu umumiy prefiksli yo‘llarni siqib saqlaydigan daraxt:
GET daraxti:
/
└── api/v1/
├── books
│ └── /
│ ├── latest
│ └── :id
│ └── /reviews
└── users/
└── :id
Marshrutni topish uchun yo‘l bo‘ylab daraxtda pastga tushiladi. Qidiruv vaqti marshrutlar soniga emas, yo‘l uzunligiga bog‘liq. gorilla/mux esa marshrutlarni ro‘yxat bo‘ylab birma-bir tekshiradi.
Bu tuzilmaning amaliy oqibatlari:
- statik yo‘l parametrli yo‘ldan ustun turadi:
/books/latestva/books/:idbirga ishlaydi, tartib ahamiyatsiz; - parametrlar regex bilan cheklanmaydi, ya’ni
:idistalgan segmentni qabul qiladi. Formatni handlerda yoki binding orqali tekshirish kerak; *pathko‘rinishidagi "catch-all" parametri qolgan butun yo‘lni oladi va u/bilan boshlanadi:
r.GET("/files/*path", func(c *gin.Context) {
c.String(200, c.Param("path")) // /files/a/b.txt → "/a/b.txt"
})
Context pool va goroutine’lar
Har bir so‘rov uchun yangi Context yaratmaslik maqsadida Gin ularni sync.Pool orqali qayta ishlatadi. Handler tugagach, Context tozalanib, pool’ga qaytariladi va keyingi so‘rovga beriladi.
Bundan muhim qoida kelib chiqadi: *gin.Contextni handler tugaganidan keyin ishlatib bo‘lmaydi.
// ❌ XATO: handler tugagach c boshqa so‘rovga berilishi mumkin
r.POST("/books", func(c *gin.Context) {
go func() {
time.Sleep(time.Second)
log.Println(c.Request.URL.Path) // boshqa so‘rovning ma’lumoti bo‘lishi mumkin!
}()
c.JSON(202, gin.H{"status": "qabul qilindi"})
})
// ✅ TO‘G‘RI: goroutine uchun nusxa olinadi
r.POST("/books", func(c *gin.Context) {
cc := c.Copy()
go func() {
time.Sleep(time.Second)
log.Println(cc.Request.URL.Path)
}()
c.JSON(202, gin.H{"status": "qabul qilindi"})
})
c.Copy() goroutine’da xavfsiz ishlatish mumkin bo‘lgan nusxa yaratadi. Yana ham yaxshiroq usul — goroutine’ga Contextni umuman bermasdan, faqat kerakli qiymatlarni uzatish:
gin.Context va context.Context
*gin.Context context.Context interfeysini ham qanoatlantiradi. Lekin standart holatda uning Done() va Value() metodlari so‘rov contextiga ulanmagan. Database’ga va tashqi servislarga doim so‘rov contextini uzating:
book, err := svc.Get(c.Request.Context(), id) // ✅
book, err := svc.Get(c, id) // ⚠️ ContextWithFallback yoqilmagan bo‘lsa, bekor qilish ishlamaydi
Bu farq Context bilan ishlash darsida ko‘rilgan bekor qilish mexanizmi uchun muhim.
Xulosa
gin.Engine—http.Handler. U istalganhttp.Serverbilan ishlaydi.- Standart holatda noto‘g‘ri metod
404oladi.405uchunHandleMethodNotAllowed = trueo‘rnating. RouterGroupprefiks va middleware’larni guruhlaydi.Usemarshrutlardan oldin chaqiriladi.- Middleware va handler bir xil turga ega va bitta zanjirda ishlaydi.
c.Next()keyingisini chaqiradi,c.Abort()qolganini bekor qiladi.returnni unutmang. - Routing radix tree’ga asoslangan: statik yo‘llar parametrlidan ustun, regex yo‘q.
Contextqayta ishlatiladi. Goroutine’gac.Copy()yoki oddiy qiymatlarni bering.- Service qatlamiga
c.Request.Context()uzatiladi.
Keyingi darsda Gin’ning eng qulay imkoniyatlaridan biri — so‘rov ma’lumotlarini struct’ga bog‘lash va validatsiya qilishni ko‘ramiz.