Tarkibga o'tish

Middleware integratsiyasi

gorilla/mux middleware’lar bilan ishlash uchun Use metodini beradi. U standart func(http.Handler) http.Handler imzosidagi middleware’larni qabul qiladi. Shu sababli oldingi darslarda yozilgan logging, recoverer, auth va ruxsat middleware’lari hech qanday o‘zgarishsiz ishlaydi.

Lekin gorilla/mux middleware’larining bitta muhim xususiyati bor. U ko‘pchilikni chalg‘itadi, shuning uchun bu darsda unga alohida to‘xtalamiz.

r.Use

r := mux.NewRouter()
r.Use(loggingMiddleware)
r.Use(requestIDMiddleware)

Use bir nechta middleware’ni birdaniga ham qabul qiladi:

r.Use(loggingMiddleware, requestIDMiddleware)

Middleware’lar qo‘shilgan tartibda ishlaydi: birinchi qo‘shilgani eng tashqi qatlam bo‘ladi.

r.Use(A, B, C)

so‘rov → A → B → C → handler
javob  ← A ← B ← C ← handler

Bu Middleware va logging asoslari darsidagi chain funksiyasi bilan bir xil tartib.

mux.MiddlewareFunc

Kutubxonada maxsus tur bor:

type MiddlewareFunc func(http.Handler) http.Handler

Use aynan shu turni qabul qiladi. Oddiy func(http.Handler) http.Handler imzosidagi funksiyalar unga avtomatik mos keladi, shuning uchun tur o‘zgartirish shart emas.

Muhim xususiyat: middleware faqat mos kelgan marshrutda ishlaydi

r.Use bilan qo‘shilgan middleware’lar faqat biror marshrut mos kelganda ishlaydi. So‘rov hech bir marshrutga mos kelmasa, 404 javobi middleware’larni chetlab o‘tadi.

r := mux.NewRouter()
r.Use(logging)
r.HandleFunc("/books", listBooks).Methods("GET")
So‘rov Middleware ishlaydimi?
GET /books ✅ ha
GET /authors → 404 ❌ yo‘q
POST /books → 405 ❌ yo‘q

Buning amaliy oqibatlari bor:

  1. Logging. 404 va 405 so‘rovlari logga tushmaydi. Kimdir API’ni skaner qilayotgani, ya’ni mavjud bo‘lmagan yo‘llarni ketma-ket tekshirib chiqayotgani ko‘rinmay qoladi.
  2. CORS. Brauzer yuboradigan OPTIONS preflight so‘rovi uchun alohida marshrut bo‘lmasa, u 405 oladi. CORS middleware’i esa umuman ishlamaydi.
  3. Recoverer va request ID. NotFoundHandler ichidagi xatolar ushlanmaydi.

Yechim: routerni tashqaridan o‘rash

Barcha so‘rovlarda ishlashi kerak bo‘lgan middleware’lar r.Use orqali emas, butun router’ni o‘rash orqali ulanadi:

r := mux.NewRouter()
r.HandleFunc("/books", listBooks).Methods("GET")

var handler http.Handler = r
handler = logging(handler)   // barcha so‘rovlar, jumladan 404
handler = recoverer(handler) // eng tashqi qatlam

http.ListenAndServe(":8080", handler)

Qoida:

  • router’ni tashqaridan o‘rash — logging, recoverer, request ID, CORS, xavfsizlik sarlavhalari. Ular har qanday so‘rov uchun ishlashi kerak;
  • r.Use yoki subrouter.Use — auth, ruxsat tekshiruvi. Ular faqat mavjud marshrutlar uchun ma’noga ega.

Subrouter middleware’lari

r := mux.NewRouter()

api := r.PathPrefix("/api/v1").Subrouter()
api.HandleFunc("/books", listBooks).Methods("GET")

private := api.PathPrefix("").Subrouter()
private.Use(auth.Require)
private.HandleFunc("/me", me).Methods("GET")
private.HandleFunc("/books", createBook).Methods("POST")

admin := api.PathPrefix("/admin").Subrouter()
admin.Use(auth.Require, RequireRole(RoleAdmin))
admin.HandleFunc("/stats", stats).Methods("GET")
  • api.PathPrefix("").Subrouter() — prefiksi bo‘sh subrouter. U yangi yo‘l qo‘shmaydi, faqat bir guruh marshrutlarni umumiy middleware bilan birlashtiradi.
  • GET /api/v1/books ochiq, POST /api/v1/books esa himoyalangan. Ikkalasi bir yo‘lda, lekin turli guruhlarda joylashgan.
  • Ota router’ning middleware’lari ichki subrouter’larga ham qo‘llanadi. Ichki subrouter’ning middleware’lari esa ulardan keyin ishlaydi.
r.Use(M1)
api.Use(M2)
admin.Use(M3)

GET /api/v1/admin/stats:  M1 → M2 → M3 → stats

CORS preflight

Brauzer boshqa domendan Content-Type: application/json bilan POST so‘rov yuborishdan oldin preflight so‘rovini yuboradi: OPTIONS /api/v1/books. gorilla/muxda OPTIONS metodli marshrut bo‘lmasa, bu so‘rov 405 oladi va brauzer asosiy so‘rovni yubormaydi.

Kutubxonada mux.CORSMethodMiddleware bor. U Access-Control-Allow-Methods sarlavhasini marshrutdagi metodlarga qarab to‘ldiradi. Lekin u faqat OPTIONS metodi e’lon qilingan marshrutlarda ishlaydi va CORS’ning boshqa sarlavhalarini qo‘ymaydi.

Amalda CORS’ni router’ni tashqaridan o‘raydigan alohida middleware bilan hal qilish soddaroq. Bunday middleware OPTIONS so‘rovini router’ga yetkazmasdan o‘zi javob beradi. CORS’ni to‘liq yozish Security headers & CORS darsida ko‘riladi.

To‘liq misol

main.go
package main

import (
    "encoding/json"
    "log"
    "net/http"
    "time"

    "github.com/gorilla/mux"
)

func writeJSON(w http.ResponseWriter, status int, v any) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(status)
    json.NewEncoder(w).Encode(v)
}

// ---------- Middleware'lar ----------

type statusRecorder struct {
    http.ResponseWriter
    status int
}

func (s *statusRecorder) WriteHeader(code int) {
    s.status = code
    s.ResponseWriter.WriteHeader(code)
}

// logging — barcha so‘rovlar uchun, router'ni tashqaridan o‘raydi
func logging(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        rec := &statusRecorder{ResponseWriter: w, status: http.StatusOK}
        next.ServeHTTP(rec, r)
        log.Printf("%s %s → %d (%v)", r.Method, r.URL.Path, rec.status, time.Since(start).Round(time.Microsecond))
    })
}

// requireToken — faqat himoyalangan guruh uchun
func requireToken(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if r.Header.Get("Authorization") != "Bearer demo-token" {
            writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "tizimga kiring"})
            return
        }
        next.ServeHTTP(w, r)
    })
}

// routeName — mos kelgan marshrut shablonini sarlavhaga yozadi
func routeName(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if route := mux.CurrentRoute(r); route != nil {
            tpl, _ := route.GetPathTemplate()
            w.Header().Set("X-Route", tpl)
        }
        next.ServeHTTP(w, r)
    })
}

// ---------- Handlerlar ----------

func ok(msg string) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        writeJSON(w, http.StatusOK, map[string]string{"message": msg})
    }
}

func newRouter() *mux.Router {
    r := mux.NewRouter()
    r.Use(routeName) // faqat mos kelgan marshrutlar uchun

    api := r.PathPrefix("/api/v1").Subrouter()
    api.HandleFunc("/books", ok("kitoblar ro‘yxati")).Methods(http.MethodGet)

    private := api.PathPrefix("").Subrouter()
    private.Use(requireToken)
    private.HandleFunc("/books", ok("kitob yaratildi")).Methods(http.MethodPost)
    private.HandleFunc("/me", ok("joriy foydalanuvchi")).Methods(http.MethodGet)

    r.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        writeJSON(w, http.StatusNotFound, map[string]string{"error": "endpoint topilmadi"})
    })

    return r
}

func main() {
    var handler http.Handler = newRouter()
    handler = logging(handler) // 404 va 405 ham logga tushadi

    log.Println("Server: http://localhost:8080")
    log.Fatal(http.ListenAndServe(":8080", handler))
}
curl -i http://localhost:8080/api/v1/books
curl -X POST http://localhost:8080/api/v1/books
curl -X POST -H "Authorization: Bearer demo-token" http://localhost:8080/api/v1/books
curl http://localhost:8080/api/v1/authors
curl -X DELETE http://localhost:8080/api/v1/books

Klient javoblari:

HTTP/1.1 200 OK
Content-Type: application/json
X-Route: /api/v1/books
...
{"message":"kitoblar ro‘yxati"}

{"error":"tizimga kiring"}
{"message":"kitob yaratildi"}
{"error":"endpoint topilmadi"}
{"error":"endpoint topilmadi"}

Server loglari:

2026/09/30 10:00:01 GET /api/v1/books → 200 (41µs)
2026/09/30 10:00:02 POST /api/v1/books → 401 (22µs)
2026/09/30 10:00:03 POST /api/v1/books → 200 (18µs)
2026/09/30 10:00:04 GET /api/v1/authors → 404 (15µs)
2026/09/30 10:00:05 DELETE /api/v1/books → 404 (9µs)

Natijalarni tahlil qilamiz:

  • logging router’ni tashqaridan o‘ragani uchun marshrut topilmagan 404 so‘rovlari ham logga tushdi.
  • mux.CurrentRoute(r) middleware ichida qaysi marshrut mos kelganini beradi. Bu metrikalarni yo‘l shabloni bo‘yicha guruhlash uchun foydali. Masalan, /books/1, /books/2 va /books/3 alohida-alohida emas, bitta /books/{id} sifatida hisoblanadi.
  • GET /api/v1/books ochiq, POST esa requireToken bilan himoyalangan.
  • DELETE /api/v1/books so‘rovi 405 emas, 404 oldi. Bu gorilla/muxning ma’lum xususiyati: yo‘l bir nechta subrouter’ga bo‘lingan bo‘lsa, metod mos kelmagan holat ba’zan "marshrut topilmadi" deb baholanadi. 405 javobi muhim bo‘lsa, bitta yo‘lning barcha metodlarini bitta subrouter ichida e’lon qiling va natijani test bilan tekshiring.

Xulosa

  • r.Use standart func(http.Handler) http.Handler middleware’larini qabul qiladi va ularni qo‘shilgan tartibda ishlatadi.
  • r.Use middleware’lari faqat mos kelgan marshrutlarda ishlaydi. 404 va 405 ularni chetlab o‘tadi.
  • Barcha so‘rovlar uchun zarur middleware’lar, ya’ni logging, recoverer va CORS, router’ni tashqaridan o‘raydi.
  • Auth va ruxsat middleware’lari subrouter’larga Use orqali ulanadi.
  • mux.CurrentRoute(r) middleware ichida mos kelgan marshrutni aniqlash imkonini beradi.

Shu bilan gorilla/mux bo‘limi tugadi. Keyingi bo‘limda to‘liq framework — Gin bilan tanishamiz.