Tarkibga o'tish

mux bilan router yaratish

Bu darsda gorilla/mux yordamida router yaratamiz va unga kitoblar API’sining marshrutlarini qo‘shamiz. Yo‘l davomida kutubxonaning standart ServeMuxdan farq qiladigan jihatlarini ham ko‘rib chiqamiz: marshrutlar qanday tartibda tekshiriladi, oxiridagi / bilan nima qilinadi va 404, 405 javoblarini qanday o‘zgartirish mumkin.

Router yaratish

r := mux.NewRouter()

mux.NewRouter() *mux.Router qaytaradi. U http.Handler interfeysini qanoatlantiradi, ya’ni standart http.ServeMux o‘rnida ishlatiladi:

http.ListenAndServe(":8080", r)

Marshrut qo‘shish

r.HandleFunc("/books", listBooks).Methods(http.MethodGet)
r.HandleFunc("/books", createBook).Methods(http.MethodPost)
r.HandleFunc("/books/{id}", getBook).Methods(http.MethodGet)
r.HandleFunc("/books/{id}", updateBook).Methods(http.MethodPut)
r.HandleFunc("/books/{id}", deleteBook).Methods(http.MethodDelete)

r.HandleFunc *mux.Route qaytaradi. Unga zanjir ko‘rinishida qo‘shimcha shartlar qo‘shiladi. .Methods(...) — shulardan biri. Bitta marshrutga bir nechta metod ham berish mumkin:

r.HandleFunc("/books/{id}", upsertBook).Methods(http.MethodPut, http.MethodPatch)

Warning

.Methods() yozilmasa, marshrut barcha metodlarga mos keladi. DELETE /books so‘rovi ham ro‘yxat qaytaradigan handlerga borib qoladi. Har bir marshrutga metodni aniq ko‘rsating.

Tayyor http.Handler uchun r.Handle ishlatiladi:

r.Handle("/metrics", metricsHandler).Methods(http.MethodGet)

Moslashtirish tartibi

Bu gorilla/mux va standart ServeMux o‘rtasidagi eng muhim farqlardan biri:

  • standart ServeMux eng aniq patternni tanlaydi, ro‘yxatdan o‘tkazish tartibi ahamiyatsiz;
  • gorilla/mux marshrutlarni ro‘yxatdan o‘tkazilgan tartibda tekshiradi va birinchi mos kelganini tanlaydi.
r.HandleFunc("/books/{id}", getBook).Methods("GET")
r.HandleFunc("/books/latest", latestBook).Methods("GET") // hech qachon ishlamaydi!

GET /books/latest so‘rovi birinchi marshrutga ham mos keladi: {id} qiymati "latest" bo‘ladi. Shu sababli latestBook hech qachon chaqirilmaydi. Tuzatishning ikki yo‘li bor:

// 1. Aniq marshrutni oldinga qo‘yish
r.HandleFunc("/books/latest", latestBook).Methods("GET")
r.HandleFunc("/books/{id}", getBook).Methods("GET")

// 2. Parametrni regex bilan cheklash
r.HandleFunc("/books/{id:[0-9]+}", getBook).Methods("GET")
r.HandleFunc("/books/latest", latestBook).Methods("GET")

Ikkinchi usul ishonchliroq: marshrutlar tartibi o‘zgarsa ham, kod to‘g‘ri ishlashda davom etadi. Regex Path parametrlari darsida batafsil ko‘riladi.

Oxiridagi /: StrictSlash

Standart holatda /books va /books/ ikki xil yo‘l hisoblanadi:

r.HandleFunc("/books", listBooks)
// GET /books   → 200
// GET /books/  → 404

StrictSlash(true) yoqilsa, router oxiridagi / farqi uchun klientni to‘g‘ri manzilga yo‘naltiradi:

r := mux.NewRouter().StrictSlash(true)
// GET /books/  → 301 → /books

Bu brauzer uchun qulay. Lekin API’da muammo tug‘diradi: POST /books/ so‘rovi 301 bilan yo‘naltirilganda, ko‘p klientlar keyingi so‘rovni GET sifatida yuboradi va so‘rov tanasi yo‘qoladi. Shuning uchun API’da odatda StrictSlash yoqilmaydi. Buning o‘rniga barcha marshrutlar oxiridagi / siz yoziladi va bu qoida hujjatda aniq ko‘rsatiladi.

404 va 405 javoblari

gorilla/mux standart holatda quyidagicha javob beradi:

Holat Status Tana
Hech bir marshrut mos kelmadi 404 404 page not found
Yo‘l mos keldi, lekin metod mos kelmadi 405 bo‘sh

JSON API uchun bu javoblarni yagona formatga keltirish kerak. Buning uchun routerda ikkita maydon bor:

r.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    writeError(w, http.StatusNotFound, "not_found", "endpoint topilmadi")
})

r.MethodNotAllowedHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "bu metod ruxsat etilmagan")
})

Standart ServeMuxdan farqli ravishda gorilla/mux 405 javobiga Allow sarlavhasini qo‘shmaydi. Kerak bo‘lsa, uni o‘zingiz hisoblab qo‘shishingiz mumkin. Lekin ko‘pchilik API’lar uchun bu shart emas.

To‘liq misol

main.go
package main

import (
    "encoding/json"
    "log"
    "net/http"
    "strconv"
    "strings"
    "sync"
    "time"

    "github.com/gorilla/mux"
)

type Book struct {
    ID     int    `json:"id"`
    Title  string `json:"title"`
    Author string `json:"author"`
}

type store struct {
    mu     sync.RWMutex
    books  map[int]Book
    nextID int
}

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

func writeError(w http.ResponseWriter, status int, code, msg string) {
    writeJSON(w, status, map[string]any{"error": map[string]string{"code": code, "message": msg}})
}

func bookID(r *http.Request) int {
    // Marshrut {id:[0-9]+} bilan cheklangan, shuning uchun xato bo‘lmaydi
    id, _ := strconv.Atoi(mux.Vars(r)["id"])
    return id
}

func (s *store) list(w http.ResponseWriter, r *http.Request) {
    s.mu.RLock()
    defer s.mu.RUnlock()
    out := make([]Book, 0, len(s.books))
    for i := 1; i < s.nextID; i++ {
        if b, ok := s.books[i]; ok {
            out = append(out, b)
        }
    }
    writeJSON(w, http.StatusOK, map[string]any{"data": out})
}

func (s *store) get(w http.ResponseWriter, r *http.Request) {
    s.mu.RLock()
    defer s.mu.RUnlock()
    b, ok := s.books[bookID(r)]
    if !ok {
        writeError(w, http.StatusNotFound, "not_found", "kitob topilmadi")
        return
    }
    writeJSON(w, http.StatusOK, map[string]any{"data": b})
}

func (s *store) create(w http.ResponseWriter, r *http.Request) {
    var in Book
    if err := json.NewDecoder(r.Body).Decode(&in); err != nil || strings.TrimSpace(in.Title) == "" {
        writeError(w, http.StatusBadRequest, "bad_request", "title majburiy")
        return
    }
    s.mu.Lock()
    in.ID = s.nextID
    s.nextID++
    s.books[in.ID] = in
    s.mu.Unlock()

    writeJSON(w, http.StatusCreated, map[string]any{"data": in})
}

func (s *store) delete(w http.ResponseWriter, r *http.Request) {
    s.mu.Lock()
    defer s.mu.Unlock()
    id := bookID(r)
    if _, ok := s.books[id]; !ok {
        writeError(w, http.StatusNotFound, "not_found", "kitob topilmadi")
        return
    }
    delete(s.books, id)
    w.WriteHeader(http.StatusNoContent)
}

func (s *store) latest(w http.ResponseWriter, r *http.Request) {
    s.mu.RLock()
    defer s.mu.RUnlock()
    for i := s.nextID - 1; i > 0; i-- {
        if b, ok := s.books[i]; ok {
            writeJSON(w, http.StatusOK, map[string]any{"data": b})
            return
        }
    }
    writeError(w, http.StatusNotFound, "not_found", "kitoblar yo‘q")
}

func newRouter(s *store) *mux.Router {
    r := mux.NewRouter()

    r.HandleFunc("/books", s.list).Methods(http.MethodGet)
    r.HandleFunc("/books", s.create).Methods(http.MethodPost)
    r.HandleFunc("/books/latest", s.latest).Methods(http.MethodGet)
    r.HandleFunc("/books/{id:[0-9]+}", s.get).Methods(http.MethodGet)
    r.HandleFunc("/books/{id:[0-9]+}", s.delete).Methods(http.MethodDelete)

    r.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        writeError(w, http.StatusNotFound, "not_found", "endpoint topilmadi")
    })
    r.MethodNotAllowedHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "bu metod ruxsat etilmagan")
    })

    return r
}

func main() {
    s := &store{books: map[int]Book{}, nextID: 1}
    r := newRouter(s)

    // Ro‘yxatdan o‘tgan marshrutlarni chiqarish
    r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
        path, _ := route.GetPathTemplate()
        methods, _ := route.GetMethods()
        log.Printf("%-7s %s", strings.Join(methods, ","), path)
        return nil
    })

    srv := &http.Server{
        Addr:              ":8080",
        Handler:           r,
        ReadHeaderTimeout: 5 * time.Second,
    }
    log.Println("Server: http://localhost:8080")
    log.Fatal(srv.ListenAndServe())
}

Server ishga tushganda marshrutlar ro‘yxati chiqadi:

2026/09/30 10:00:00 GET     /books
2026/09/30 10:00:00 POST    /books
2026/09/30 10:00:00 GET     /books/latest
2026/09/30 10:00:00 GET     /books/{id:[0-9]+}
2026/09/30 10:00:00 DELETE  /books/{id:[0-9]+}
2026/09/30 10:00:00 Server: http://localhost:8080

r.Walk barcha marshrutlarni aylanib chiqadi. Loyiha kattalashganda qaysi endpointlar mavjudligini tekshirish uchun bu qulay vosita.

Sinab ko‘ramiz:

curl -X POST http://localhost:8080/books -d '{"title":"Sarob","author":"Abdulla Qahhor"}'
curl -X POST http://localhost:8080/books -d '{"title":"Ufq","author":"Said Ahmad"}'
curl http://localhost:8080/books/latest
curl http://localhost:8080/books/1
curl http://localhost:8080/books/abc
curl -X PUT http://localhost:8080/books/1
curl http://localhost:8080/authors
{"data":{"id":1,"title":"Sarob","author":"Abdulla Qahhor"}}
{"data":{"id":2,"title":"Ufq","author":"Said Ahmad"}}
{"data":{"id":2,"title":"Ufq","author":"Said Ahmad"}}
{"data":{"id":1,"title":"Sarob","author":"Abdulla Qahhor"}}
{"error":{"code":"not_found","message":"endpoint topilmadi"}}
{"error":{"code":"method_not_allowed","message":"bu metod ruxsat etilmagan"}}
{"error":{"code":"not_found","message":"endpoint topilmadi"}}
  • /books/latest to‘g‘ri ishladi, chunki {id} parametri faqat raqamlarni qabul qiladi.
  • /books/abc hech bir marshrutga mos kelmadi va JSON formatidagi 404 qaytdi.
  • PUT /books/1 uchun yo‘l mos keldi, lekin metod yo‘q. Natijada 405 qaytdi.

Xulosa

  • mux.NewRouter() http.Handler qaytaradi va standart router o‘rnida ishlatiladi.
  • Har bir marshrutga .Methods(...) bilan metodni aniq ko‘rsating.
  • gorilla/mux marshrutlarni ro‘yxatdan o‘tkazilgan tartibda tekshiradi. Aniq marshrutlarni oldinga qo‘ying yoki parametrlarni regex bilan cheklang.
  • API’da StrictSlash odatda yoqilmaydi.
  • NotFoundHandler va MethodNotAllowedHandler bilan xato javoblari JSON formatiga keltiriladi.
  • r.Walk barcha marshrutlarni ko‘rib chiqish imkonini beradi.

Keyingi darsda yo‘l parametrlarini chuqurroq o‘rganamiz: regex, bir nechta parametr, query moslashtirish va nomli marshrutlardan URL yasash.