Tarkibga o'tish

http.HandleFunc va http.ListenAndServe

Go’da har qanday web server ikki qadamdan boshlanadi. Avval qaysi yo‘lga qaysi funksiya javob berishini aytamiz. Keyin serverni ishga tushirib, portni tinglashni boshlaymiz. Birinchi qadamni http.HandleFunc, ikkinchisini http.ListenAndServe bajaradi.

Bu darsda ikkala funksiyani ichidan ko‘rib chiqamiz: ular aslida nima qiladi, qanday xatolar qaytaradi va serverni qanday qilib to‘g‘ri to‘xtatish kerak.

Eng kichik server

main.go
package main

import (
    "fmt"
    "log"
    "net/http"
)

func salom(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "Salom!")
}

func main() {
    http.HandleFunc("/salom", salom)

    log.Println("Server: http://localhost:8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}
go run main.go

Boshqa terminalda:

curl http://localhost:8080/salom
Salom!

Kodda ikkita muhim qator bor. Ularni navbatma-navbat ko‘ramiz.

http.HandleFunc

func HandleFunc(pattern string, handler func(ResponseWriter, *Request))

Funksiya ikki argument oladi:

  • pattern — qaysi yo‘l uchun ekanini bildiruvchi qolip, masalan "/salom" yoki "GET /books";
  • handler — shu yo‘lga so‘rov kelganda chaqiriladigan funksiya.

http.HandleFunc handlerni global http.DefaultServeMux routeriga qo‘shadi. Ichki ko‘rinishi taxminan quyidagicha:

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    DefaultServeMux.HandleFunc(pattern, handler)
}

Handler funksiyaning imzosi

Handler har doim bir xil imzoga ega bo‘ladi:

func nom(w http.ResponseWriter, r *http.Request)

Funksiya hech narsa qaytarmaydi. Javob w orqali yoziladi. So‘rov haqidagi ma’lumot esa r ichida keladi.

Funksiya nomi muhim emas. Anonim funksiya ham ishlatish mumkin:

http.HandleFunc("/vaqt", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, time.Now().Format(time.DateTime))
})

HandleFunc va Handle farqi

Routerga handler qo‘shishning ikki usuli bor:

http.HandleFunc("/a", funksiya)       // func(w, r) qabul qiladi
http.Handle("/b", handlerObyekt)      // http.Handler interfeysini qabul qiladi

Handle — ServeHTTP metodiga ega obyekt uchun. HandleFunc — oddiy funksiya uchun. Aslida HandleFunc ichida funksiya http.HandlerFunc turiga o‘tkaziladi va Handlega beriladi. Demak, ikkalasi bir xil natijaga olib keladi.

Bir pattern ikki marta qo‘shilsa

Bir xil pattern ikki marta ro‘yxatdan o‘tkazilsa, dastur panic bilan to‘xtaydi:

http.HandleFunc("/salom", salom)
http.HandleFunc("/salom", boshqaSalom) // panic: pattern "/salom" ... conflicts with pattern "/salom"

Bu xato server ishga tushishidan oldin, dastur boshlanishida yuz beradi. Shuning uchun uni sezmay qolish qiyin.

http.ListenAndServe

func ListenAndServe(addr string, handler Handler) error

Funksiya berilgan manzilda TCP ulanishlarni tinglaydi va har bir so‘rovni handlerga uzatadi.

Manzil (addr)

Qiymat Ma’nosi
":8080" Barcha tarmoq interfeyslarida 8080-port
"localhost:8080" Faqat shu kompyuterdan kelgan ulanishlar
"127.0.0.1:8080" Xuddi yuqoridagidek, IPv4 manzil bilan
"0.0.0.0:8080" Barcha IPv4 interfeyslar
"" Standart HTTP porti, ya’ni :80

Ishlab chiqish paytida localhost:8080 xavfsizroq: server tarmoqdagi boshqa kompyuterlarga ko‘rinmaydi. Serverga yoki konteynerga joylashtirganda odatda :8080 ishlatiladi.

Handler (handler)

Ikkinchi argument — barcha so‘rovlarni qabul qiladigan handler. nil berilsa, http.DefaultServeMux ishlatiladi. O‘z router’ingiz bo‘lsa, shu yerga beriladi:

mux := http.NewServeMux()
mux.HandleFunc("/salom", salom)

http.ListenAndServe(":8080", mux)

Bloklovchi chaqiruv

ListenAndServe server ishlab turgan vaqt davomida qaytmaydi. Undan keyingi qatorlar faqat server to‘xtaganda bajariladi:

http.ListenAndServe(":8080", nil)
fmt.Println("Bu qator server ishlayotganda bajarilmaydi")

Shu sababli serverni ishga tushirish odatda main funksiyasining oxirida turadi. Undan oldin barcha handlerlar ro‘yxatdan o‘tkazilgan bo‘lishi kerak.

Har bir so‘rov alohida goroutine’da

Server har bir yangi ulanish uchun alohida goroutine ishga tushiradi. Ya’ni bir vaqtda kelgan yuzta so‘rov parallel qayta ishlanadi.

Bu qulay, lekin bitta muhim oqibati bor: bir nechta handler umumiy o‘zgaruvchini o‘zgartirsa, data race yuzaga keladi.

main.go
package main

import (
    "fmt"
    "log"
    "net/http"
    "sync"
)

var (
    mu      sync.Mutex
    counter int
)

func tashrif(w http.ResponseWriter, r *http.Request) {
    mu.Lock()
    counter++
    n := counter
    mu.Unlock()

    fmt.Fprintf(w, "Siz %d-tashrifchisiz\n", n)
}

func main() {
    http.HandleFunc("/", tashrif)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

counter o‘zgaruvchisi barcha so‘rovlar uchun umumiy. Shuning uchun unga murojaat sync.Mutex bilan himoyalangan. Mutex’siz yozilgan kodni go run -race main.go bilan ishga tushirib, bir nechta so‘rov yuborsangiz, race detector ogohlantirish beradi. Bu mavzu Mutex darsida batafsil yoritilgan.

Xatoni tekshirish

ListenAndServe doim nil bo‘lmagan xato qaytaradi. Xato ikki holatda keladi:

  1. Server umuman ishga tushmadi: port band, ruxsat yo‘q yoki manzil noto‘g‘ri.
  2. Server Shutdown yoki Close bilan to‘xtatildi. Bu holatda http.ErrServerClosed qaytadi.

Eng oddiy usul — xatoni log.Fatal bilan chiqarish:

log.Fatal(http.ListenAndServe(":8080", nil))

Port band bo‘lsa, shunday xabar chiqadi:

listen tcp :8080: bind: address already in use

Warning

http.ListenAndServe(":8080", nil) natijasini tekshirmaslik keng tarqalgan xato. Port band bo‘lsa, dastur hech narsa demasdan darhol tugaydi va nima bo‘lganini tushunish qiyin bo‘ladi.

O‘z http.Serveringiz bilan

Production’da http.ListenAndServe o‘rniga http.Server yaratiladi. Sababi — timeoutlar va serverni to‘g‘ri to‘xtatish imkoniyati:

srv := &http.Server{
    Addr:              ":8080",
    Handler:           mux,
    ReadHeaderTimeout: 5 * time.Second,
    ReadTimeout:       10 * time.Second,
    WriteTimeout:      10 * time.Second,
    IdleTimeout:       60 * time.Second,
}

err := srv.ListenAndServe()

srv.ListenAndServe() xuddi paketdagi http.ListenAndServe kabi ishlaydi. Farqi shundaki, barcha sozlamalar sizning qo‘lingizda.

Serverni to‘g‘ri to‘xtatish (graceful shutdown)

Terminalda Ctrl+C bosilsa, dastur darhol o‘ladi. Shu paytda qayta ishlanayotgan so‘rovlar yarim yo‘lda uziladi. Masalan, foydalanuvchi buyurtma bergan, ma’lumot bazaga yozilmoqda, lekin javob yetib bormadi.

Graceful shutdown — serverni tartib bilan to‘xtatish. Bunda server yangi ulanishlarni qabul qilishni to‘xtatadi, boshlangan so‘rovlarning tugashini kutadi va shundan keyin chiqadi.

Misol

main.go
package main

import (
    "context"
    "errors"
    "fmt"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("GET /sekin", func(w http.ResponseWriter, r *http.Request) {
        time.Sleep(3 * time.Second) // uzoq ishni taqlid qiladi
        fmt.Fprintln(w, "Tayyor")
    })

    srv := &http.Server{
        Addr:              ":8080",
        Handler:           mux,
        ReadHeaderTimeout: 5 * time.Second,
    }

    // Serverni alohida goroutine'da ishga tushiramiz
    go func() {
        log.Println("Server: http://localhost:8080")
        if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
            log.Fatalf("server xatosi: %v", err)
        }
    }()

    // Ctrl+C (SIGINT) yoki SIGTERM signalini kutamiz
    ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
    defer stop()
    <-ctx.Done()

    log.Println("To‘xtatish signali keldi, server yopilmoqda...")

    // Boshlangan so‘rovlar uchun 10 soniya beramiz
    shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    if err := srv.Shutdown(shutdownCtx); err != nil {
        log.Printf("serverni to‘xtatishda xato: %v", err)
    }

    log.Println("Server to‘xtadi")
}

Sinab ko‘ring: serverni ishga tushiring, curl http://localhost:8080/sekin yuboring va javob kelishini kutmasdan server terminalida Ctrl+C bosing.

2026/09/30 10:00:00 Server: http://localhost:8080
^C2026/09/30 10:00:01 To‘xtatish signali keldi, server yopilmoqda...
2026/09/30 10:00:03 Server to‘xtadi

curl baribir Tayyor javobini oladi. Server so‘rov tugashini kutib, keyin to‘xtadi.

Kod qanday ishlaydi?

  • srv.ListenAndServe() goroutine ichida chaqiriladi. Shunda main bloklanmaydi va signalni kutishi mumkin.
  • signal.NotifyContext — SIGINT yoki SIGTERM kelganda bekor bo‘ladigan context qaytaradi. <-ctx.Done() shu signalni kutadi.
  • srv.Shutdown(shutdownCtx) — yangi ulanishlarni qabul qilishni to‘xtatadi va faol so‘rovlar tugashini kutadi. 10 soniya o‘tsa ham tugamasa, xato qaytaradi.
  • Shutdown chaqirilgach, goroutine ichidagi ListenAndServe http.ErrServerClosed qaytaradi. Bu xato emas, shuning uchun errors.Is bilan alohida tekshiriladi.

Info

SIGTERM — Docker, Kubernetes va systemd konteyner yoki servisni to‘xtatayotganda yuboradigan signal. Server shu signalga to‘g‘ri javob bersa, yangi versiyani chiqarish (deploy) paytida foydalanuvchi so‘rovlari uzilmaydi.

Xulosa

Bu darsda quyidagilarni o‘rgandik:

  • http.HandleFunc handlerni DefaultServeMuxga qo‘shadi, http.Handle esa tayyor http.Handler obyektini qabul qiladi;
  • bir pattern ikki marta qo‘shilsa, dastur panic bilan to‘xtaydi;
  • http.ListenAndServe bloklovchi va doim xato qaytaradi, shuning uchun natijasini tekshirish kerak;
  • har bir so‘rov alohida goroutine’da ishlaydi, umumiy ma’lumotni himoyalash lozim;
  • production’da http.Server timeoutlar bilan yaratiladi va Shutdown orqali tartib bilan to‘xtatiladi.

Keyingi darsda handlerning ikki argumenti — http.ResponseWriter va *http.Request bilan yaqindan tanishamiz.