Tarkibga o'tish

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:

srv := &http.Server{Addr: ":8080", Handler: r}
srv.ListenAndServe()

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:

type HandlerFunc func(*gin.Context)
type HandlersChain []HandlerFunc

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

main.go
package main

import (
    "fmt"
    "net/http"
    "net/http/httptest"

    "github.com/gin-gonic/gin"
)

func mw(name string) gin.HandlerFunc {
    return func(c *gin.Context) {
        fmt.Printf("%s: oldin\n", name)
        c.Next()
        fmt.Printf("%s: keyin\n", name)
    }
}

func guard(c *gin.Context) {
    if c.Query("block") == "1" {
        fmt.Println("guard: to‘xtatildi")
        c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "ruxsat yo‘q"})
        return
    }
    c.Next()
}

func main() {
    gin.SetMode(gin.ReleaseMode)
    r := gin.New()
    r.Use(mw("A"), mw("B"))
    r.GET("/", guard, func(c *gin.Context) {
        fmt.Println("handler")
        c.String(http.StatusOK, "OK")
    })

    for _, url := range []string{"/", "/?block=1"} {
        fmt.Println("---", url)
        rec := httptest.NewRecorder()
        r.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, url, nil))
        fmt.Println("status:", rec.Code)
    }
}
--- /
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/latest va /books/:id birga ishlaydi, tartib ahamiyatsiz;
  • parametrlar regex bilan cheklanmaydi, ya’ni :id istalgan segmentni qabul qiladi. Formatni handlerda yoki binding orqali tekshirish kerak;
  • *path ko‘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:

path := c.Request.URL.Path
go func() { log.Println(path) }()

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 istalgan http.Server bilan ishlaydi.
  • Standart holatda noto‘g‘ri metod 404 oladi. 405 uchun HandleMethodNotAllowed = true o‘rnating.
  • RouterGroup prefiks va middleware’larni guruhlaydi. Use marshrutlardan 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.
  • Context qayta ishlatiladi. Goroutine’ga c.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.