Birkaç satırda bir sunucu
Handler, isteği alıp yanıtı yazan bir fonksiyondur. ServeMux istekleri handler'lara yönlendirir.
Editör tarayıcınızdan bağlantı kabul edemez, bu yüzden bu sayfadaki örnekler sunucuyu httptest.NewServer ile başlatır ve aynı programdan çağırır. Gerçek bir programda son kısmın yerini, bloklanıp sonsuza kadar hizmet veren tek bir satır alır:
log.Fatal(http.ListenAndServe(":8080", mux))
Sonra curl localhost:8080/hello/gopher, Hello, gopher! yazdırır. ListenAndServe yalnızca bir hatada (örneğin port kullanılıyorsa) döner; log.Fatal ile sarılmasının nedeni budur.
Her istek kendi goroutine'inde çalışır. Handler'larınızın paylaştığı bir map ya da sayaç gibi her şey bir mutex gerektirir.
Handler'lar
ServeHTTP(http.ResponseWriter, *http.Request) metodu olan her şey bir http.Handler'dır. http.HandlerFunc düz bir fonksiyonu bu interface'e uyarlar ve mux.HandleFunc dönüşümü sizin yerinize yapar. Handler'ların bağımlılıklara ihtiyacı olduğunda struct handler kullanışlıdır:
type API struct {
db *sql.DB
}
func (a *API) listItems(w http.ResponseWriter, r *http.Request) { /* uses a.db */ }
mux.HandleFunc("GET /items", api.listItems)
*http.Request size şunları verir:
| Alan ya da metot | İçeriği |
|---|---|
r.Method | GET, POST, ... |
r.URL.Path | yol, /items/42 |
r.PathValue("id") | rota deseninden bir joker karakter (Go 1.22) |
r.URL.Query().Get("q") | bir sorgu string'i parametresi |
r.Header.Get("Authorization") | bir istek header'ı |
r.Body | istek gövdesi, bir io.ReadCloser (sunucu onu kapatır) |
r.FormValue("name") | bir form alanı ya da sorgu parametresi |
r.Context() | istemci bağlantıyı kestiğinde iptal edilen bir context |
Yönlendirme desenleri (Go 1.22)
Go 1.22'den beri ServeMux desenleri [METHOD ][HOST]/[PATH] biçimindedir ve yollar joker karakterler içerebilir.
| Desen | Eşleştiği |
|---|---|
"/items/" | /items/ ve altındaki her şey (sondaki eğik çizgi = önek) |
"/items" | yalnızca /items |
"GET /items/{id}" | /items/42 üzerinde GET (ve HEAD); r.PathValue("id") == "42" |
"POST /items" | yalnızca /items üzerinde POST |
"/files/{path...}" | /files/a/b/c; path değeri "a/b/c" |
"/{$}" | her yol değil, yalnızca / |
"/" | başka hiçbir desenin eşleşmediği her yol |
İki desen eşleştiğinde daha belirli olan kazanır, bu yüzden /items/new, /items/{id}'yi yener. Hiçbiri daha belirli değilse, örneğin /items/{id} ve /{kind}/new (ikisi de /items/new ile eşleşir), ikincisini kaydetmek iki deseni de adlandıran bir mesajla panic olur. Bir yol eşleşip metot eşleşmezse mux, sizden hiçbir kod gerektirmeden bir Allow header'ıyla 405 Method Not Allowed yanıtı verir.
"/" deseni her şeyi yakalar. Bu, bir ana sayfayı "/" ile kaydedip onun bilinmeyen her URL'e 200 ile yanıt verdiğini görenleri şaşırtır. Ana sayfa için "GET /{$}" kullanın.
Bu desenler go.mod içinde go 1.22 ya da sonrasını gerektirir. Daha eski bir sürüm satırıyla mux eski davranışa döner: "GET /items"'ı bir host adı ardından bir yol olarak okur, bu yüzden rota hiç eşleşmez ve /items'a gelen her istek 404 alır.
Küçük bir JSON API'si
Son istek hiçbir rotanın kabul etmediği DELETE'i kullanır ve mux kendiliğinden Allow: GET, HEAD ile 405 yanıtı verir: bunlar o yol için kaydedilmiş metotlardır (bir GET rotası HEAD'i de kabul eder).
Durum kodları ve yazma sırası
Bir yanıtın üç parçası vardır ve sırayla yazılmaları gerekir: header'lar, durum, gövde.
w.Header().Set(...)header'ları değiştirir. Yalnızca durum gönderilene kadar etkisi vardır.w.WriteHeader(code)durum satırını ve header'ları gönderir.w.Write(...)(ya dafmt.Fprint(w, ...)veya bir encoder) gövdeyi gönderir.WriteHeaderçağrılmamışsa ilkWriteönce200 OKgönderir.
Sonuçları:
- Gövde başladıktan sonra bir header ayarlamak hiçbir şey yapmaz.
WriteHeader'ı iki kez çağırmakhttp: superfluous response.WriteHeader callloglar ve ilk durumu tutar.- Bir hata yanıtından sonra
returnedin.http.Errorhandler'ınızı durdurmaz ve ondan sonraki kod aynı yanıta yazmaya devam eder.
Düz sayılar yerine adlandırılmış sabitleri (http.StatusOK, http.StatusCreated, http.StatusBadRequest, http.StatusUnauthorized, http.StatusNotFound, http.StatusInternalServerError) kullanın. http.StatusText(404), "Not Found" döndürür.
Middleware
Middleware, bir handler alıp bir handler döndüren ve bir sonrakini çağırmadan önce ya da sonra bir şey yapan bir fonksiyondur:
Aynı istek için log: satırı her zaman client got:'tan önce görünür. Handler goroutine'i onu dönmeden önce yazdırır ve sunucu handler dönene kadar yanıtı bitirmez.
statusRecorder gerçek ResponseWriter'ı gömer ve yalnızca WriteHeader'ı geçersiz kılar; bu, middleware'den durum kodunu gözlemlemenin standart yoludur.
Production ayarları
http.ListenAndServe timeout'u olmayan bir sunucu kullanır, bu yüzden yavaş bir istemci bir bağlantıyı süresiz açık tutabilir. İnternete açık her şey için bir http.Server'ı açıkça yapılandırın ve onu düzgün biçimde kapatın:
srv := &http.Server{
Addr: ":8080",
Handler: mux,
ReadHeaderTimeout: 5 * time.Second,
ReadTimeout: 10 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 120 * time.Second,
}
go func() {
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatal(err)
}
}()
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
<-ctx.Done() // wait for Ctrl+C or a SIGTERM from the orchestrator
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(shutdownCtx); err != nil { // finish in-flight requests
log.Println("shutdown:", err)
}
Shutdown yeni bağlantıları kabul etmeyi bırakır ve context'in deadline'ına kadar aktif isteklerin bitmesini bekler. ListenAndServe, Shutdown başlar başlamaz http.ErrServerClosed döndürür; bu hatanın başarısızlık sayılmamasının nedeni budur. Uzun süren handler'lar için r.Context()'i aşağıya aktarın ki istemci gidince dursunlar.
Statik dosya sunmak tek satırdır: mux.Handle("GET /static/", http.StripPrefix("/static/", http.FileServer(http.Dir("public")))).
Sık yapılan hatalar
http.Error'dan sonra return etmemek. Handler çalışmaya devam eder ve yanıta daha fazlasını yazar.- Gövdeyi yazdıktan sonra header ayarlamak. Sessizce atılırlar.
- Ana sayfayı
"/"üzerine kaydetmek. Bilinmeyen her yol için her şeyi yakalayan rota olur."/{$}"kullanın. - Handler'lar arasında kilit olmadan durum paylaşmak. İstekler eşzamanlı çalışır.
- Production'da varsayılan sunucuyu kullanmak. Timeout'u yoktur; onları bir
http.Serverüzerinde ayarlayın. - Yok sayılan yönlendirme desenleri.
"GET /x/{id}"hiç eşleşmiyorsago.mod'ungo 1.22ya da sonrasını belirttiğini kontrol edin.
Sıkça Sorulan Sorular
Go'da basit bir web sunucusu nasıl oluşturulur?
Bir handler kaydedin ve dinlemeye başlayın: http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "hello") }) ardından log.Fatal(http.ListenAndServe(":8080", nil)). Standart kütüphane sunucusu production kalitesindedir; HTTP/1.1'i, TLS üzerinden HTTP/2'yi, keep-alive'ı ve bağlantı başına bir goroutine'i ele alır.
Go net/http'de bir yol parametresi nasıl alınır?
Go 1.22'den beri ServeMux desenleri joker karakterler içerebilir: mux.HandleFunc("GET /items/{id}", h) kaydedin ve değeri handler'ın içinde r.PathValue("id") ile okuyun. Sondaki bir {path...} yolun geri kalanıyla eşleşir. 1.22'den önce r.URL.Path'i kendiniz bölmeniz ya da chi gibi bir router kullanmanız gerekiyordu.
Go'da REST API yazmak için Gin gibi bir framework gerekli mi?
Hayır. Go 1.22'den beri net/http metoda ve yol parametrelerine göre yönlendirir; bu da insanların router kullanmasının ana nedenini karşıladı. Gövdeler için encoding/json ve loglama ile yetkilendirme için küçük middleware fonksiyonlarıyla standart kütüphane çoğu API için yeterlidir. Framework'ler istek bağlama ve doğrulama gibi kolaylıklar ekler.
Go HTTP handler'ında durum kodu nasıl ayarlanır?
Gövdeyi yazmadan önce w.WriteHeader(http.StatusCreated) çağırın. Önce gövdeyi yazarsanız Go otomatik olarak 200 OK gönderir ve sonraki bir WriteHeader bir superfluous response.WriteHeader call log mesajıyla yok sayılır. Header'ları da WriteHeader'dan önce w.Header().Set ile ayarlayın; hatalar için http.Error(w, msg, code) hepsini yapar.