- Go 1.22’deki
net/http ServeMux iyileştirmesi, standart HTTP çoklayıcısına daha güçlü desen eşleme ekleyerek, yalnızca basit yol eşleme nedeniyle üçüncü taraf router’lara bağımlı olunan alanları azaltıyor
- Yeni mux, desen içinde HTTP metodu ile
/task/{id}/ gibi yol jokerlerini birlikte kullanabildiği için, aynı yolu GET, POST, DELETE gibi metodlara göre farklı handler’lara bağlayabiliyor
- Eşleşen değerler
req.PathValue("id") ile okunuyor; {id}... ve {$} sayesinde de takip eden yolun tamamı ya da tam yol sonu üzerinde kontrol sağlanabiliyor
- Desen çakışmaları, kayıt anında oluşan panic ile ortaya çıkıyor; hangi yolun iki tarafa da eşleştiğini ve neden hiçbir tarafın daha spesifik olmadığını da gösteriyor
- Yalnızca standart mux ile yeterli olan sunucuların sayısı artabilir, ancak
gorilla/mux ya da Gin gibi üçüncü taraf router ve framework’ler hâlâ daha geniş özellikler ve araçlar sunuyor
Go 1.22’deki ServeMux değişiklikleri
- Go 1.22 ile birlikte,
net/http içindeki varsayılan HTTP sunum çoklayıcısı http.ServeMux için güçlendirilmiş desen eşleme geliyor
- Mevcut
http.ServeMux, temel olarak basit yol eşleme odaklı olduğundan, daha karmaşık yönlendirme ihtiyaçlarında üçüncü taraf kütüphaneler sıkça kullanılıyordu
- Yeni mux, gelişmiş eşleme yeteneklerini standart kütüphaneye taşıyarak üçüncü taraf paketlerle arasındaki işlev farkını azaltıyor
- Karşılaştırma için, mevcut standart kütüphane yaklaşımı ile
gorilla/mux kullanan bir REST sunucusu örneği kullanılıyor
Yeni mux’un temel kullanım biçimi
- Daha önce
gorilla/mux gibi Go router paketleri kullanmış geliştiricilere, yeni standart mux’un kullanım şekli tanıdık gelecektir
- Örnekte
http.NewServeMux() ile bir mux oluşturuluyor ve HandleFunc ile daha zengin desenler kaydediliyor
mux.HandleFunc("GET /path/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "got path\n")
})
mux.HandleFunc("/task/{id}/", func(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
fmt.Fprintf(w, "handling task with id=%v\n", id)
})
- İlk handler, desene
GET metodunu dahil ettiği için yalnızca /path/ ile başlayan GET isteklerinde çalışıyor
- İkinci handler,
/task/{id}/ içindeki {id} ile tek bir yol bileşenini eşliyor; handler içinde de değer r.PathValue("id") ile okunuyor
- Go 1.22 henüz yayımlanmamışken hazırlanmış bir örnek olduğu için,
gotip ile çalıştırılması öneriliyor
- Test sonuçları yönlendirme kurallarını doğrudan gösteriyor
/what/ isteği 404 page not found döndürüyor
/path/ için GET isteği got path döndürüyor
/path/ için POST isteği Method Not Allowed döndürüyor
/task/f0cd2e/ isteği id=f0cd2e olarak işleniyor
Ek desen özellikleri ve çakışma yönetimi
- Yeni
ServeMux, temel joker desteğinin ötesinde yol eşlemeyi daha ayrıntılı kontrol etme imkânı sunuyor
{id}... ile takip eden yolun tamamını bir jokerle eşleyebiliyor
{$} ile yol sonunu sıkı biçimde eşleyebiliyor
- Desenler arasındaki öncelik ve çakışma kurallarını işleyebiliyor
- Çakışma olasılığı, öneride özellikle önem verilen konulardan biri
- Örneğin aşağıdaki iki desen de
/task/0/status/ isteğine eşleşiyor
mux.HandleFunc("/task/{id}/status/", ...)
mux.HandleFunc("/task/0/{action}/", ...)
- Bu durumda yeni
ServeMux, kayıt anında panic oluşturuyor
- Hata mesajı, iki desenin birlikte eşlediği örnek yolu ve neden hiçbir desenin diğerinden daha spesifik olmadığını birlikte gösteriyor
- Desenlerin birden fazla yerde kaydedildiği karmaşık kodlarda, bu tür ayrıntılı çakışma teşhisi özellikle yararlı oluyor
task sunucusu örneğinin yeniden uygulanması
- Mevcut REST Servers in Go serisindeki task/todo-list sunucusu, Go 1.22’nin geliştirilmiş mux’u ile yeniden uygulanıyor
- Tüm kod stdlib-newmux örneğinde yer alıyor
- Kayıt desenleri, HTTP metodunu ve yol jokerlerini birlikte kullanıyor
mux.HandleFunc("POST /task/", server.createTaskHandler)
mux.HandleFunc("GET /task/", server.getAllTasksHandler)
mux.HandleFunc("DELETE /task/", server.deleteAllTasksHandler)
mux.HandleFunc("GET /task/{id}/", server.getTaskHandler)
mux.HandleFunc("DELETE /task/{id}/", server.deleteTaskHandler)
mux.HandleFunc("GET /tag/{tag}/", server.tagHandler)
mux.HandleFunc("GET /due/{year}/{month}/{day}/", server.dueHandler)
gorilla/mux örneğinde olduğu gibi, aynı yol HTTP metoduna göre farklı handler’lara ayrılabiliyor
- Önceki
http.ServeMux sürümünde, aynı yolun farklı metodları aynı handler’a gider ve metod kontrolünün handler içinde yapılması gerekirdi
- Yeni mux ile yönlendirme kararı daha çok handler dışına taşındığı için handler’ları ayırmak kolaylaşıyor
PathValue kullanımı ve sınırlamalar
getTaskHandler, yoldan çıkarılan ID’yi req.PathValue("id") ile okuyor ve strconv.Atoi ile tam sayıya dönüştürüyor
- Yeni mux’taki
{id} deseni yalnızca tam sayıları eşleyen bir regex belirtmediği için, strconv.Atoi hata kontrolü gerekiyor
- ID dönüştürme başarısız olursa
http.StatusBadRequest, depoda task bulunamazsa http.StatusNotFound döndürülüyor
- Nihai uygulama,
gorilla/mux kullanılan Part 2 çözümüne oldukça benziyor
- Gerekirse yeni
http.ServeMux içindeki {$} jokeriyle takip eden yolu kolayca sınırlandırmak mümkün
Standart mux ile üçüncü taraf router’ların konumu
- Go’ya yeni başlayanlar için “hangi router paketini kullanmalıyım?” sorusu sıkça soruluyordu
- Go 1.22 sonrasında birçok kullanıcı, üçüncü taraf paket kullanmadan da yalnızca yeni standart mux ile ihtiyaçlarının karşılandığını düşünebilir
gorilla/mux gibi router’lar, standart kütüphaneye göre hâlâ daha fazla özellik sunuyor
- Gin gibi hafif framework’ler ise yalnızca router değil, web backend geliştirmek için ek araçlar da sağlıyor
- Standart kütüphanenin güçlenmesi, hem üçüncü taraf paket kullananlar hem de yalnızca standart kütüphane kullananlar için olumlu bir gelişme
1 yorum
Hacker News yorumları
İki route aynı anda eşleştiğinde panic üretmesi sezgilere aykırı gibi geliyor. Diğer web framework’leri genelde önce kaydedilen eşleşen route’u kullanır; bunun Go’ya özgü bir nedeni olup olmadığını merak ediyorum.
“HTTP route’ları birden fazla yerde kaydedilebildiği için çakışan eşleşmeleri bulmak zor” şeklindeki uç durum araçlarla çözülebilir gibi görünüyor. Kariyerim boyunca “ilk eşleşen kazanır” davranışından çok yararlandım; ayrıca
/foo/bar’ın ayrı bir route,/foo/{id}’nin başka bir route olması gereken pek çok iş gereksinimi de vardı.Tasarım önerisinde[1], daha spesifik örüntünün eşleştirilmesiyle eski ServeMux örüntülerinin sıradan bağımsızlığı korunabilir deniyor; ancak iki örüntüden hangisinin daha spesifik olduğunu ya da neden çakıştıklarını ilk bakışta görmek zor olabilir. Bu yüzden çakışan örüntü kaydedildiğinde çıkan panic mesajı, çakışmayı örnek bir path ile göstermeye ayarlanmış.
Arka plan tartışmasında[2] da mux semantiğinin
HandleveyaHandleFuncçağrılarının sırasına bağlı olmadığı açıklanıyor. Sıradan bağımsızlık sayesinde paket başlatma sırası önemli olmaz ve kod refactoring’i de kolaylaşır. Bu nedenle eşitlik durumunda ölçüt olarak kayıt sırasını kullanmamak, yinelenen kayıtların panic üretmesi ve kayıt sırasına bağlı semantikten kaçınmak temel tasarım hedefi olarak kalmış.[1]: https://github.com/golang/go/issues/61410
[2]: https://github.com/golang/go/discussions/60227
Yakınlarda kişisel bir projede gorilla/mux kullanırken yanlışlıkla çakışan route’lar oluşturduğum bir hatayla karşılaştım; router çalışmak yerine panic üretseydi, URL’leri yeniden düzenlemeye zorlanır ve hatayı tamamen önlemiş olurdum.
initfonksiyonlarının sırasını etkileyebilir./foo/barve/foo/{id}gibi bir davranış gerekiyorsa, temel path’i alan bir handler yazıp onun içinde diğer handler fonksiyonlarına açıkça dallanabilirsiniz. Standart kütüphanede olmayan bir davranış istiyorsanız, daha fazla özelliğe sahip pek çok başka kütüphane de var.Go standart kütüphanesi ve dili, olası belirsizlikler karşısında her zaman muhafazakâr tarafa eğilimli oldu; bu da standart kütüphanenin nasıl davrandığına dair güçlü ve doğru bir sezgi oluşturmayı kolaylaştırıyor. Kod yazarken birkaç saniye kazanmaktansa onu hızlı ve doğru biçimde okuyup anlayabilmek çok daha değerlidir.
/foo/barile/foo/{id}’ye izin verilecek gibi duruyor. İlki daha spesifik olduğu için önceliğe sahip olur; bu yüzden sorun yok gibi görünüyor.Ancak
/foo/{id}/deleteve/foo/bar/{action}varsa,/foo/bar/deleteikisiyle de eşleşir ve hiçbiri daha spesifik olmaz; bu durumda panic çıkacak gibi. Mantıklı hissettiriyor, ama önce kaydedilenin kazandığı bir öncelik daha iyi de olabilir.$regex’i eksik olan bir route yüzünden gerçekten böyle bir şey yaşamıştım.gorilla/mux projesi biraz tuhaf ve kafa karıştırıcı. Geçen yıl bakımcılar projeyi arşivlediği için gin-gonic adlı başka bir multiplexer’a geçmiştik.
Ama yazıda adı geçince tekrar kontrol ettim ve artık arşiv durumunda olmadığını gördüm. Bunun projenin genel kararlılığı hakkında soru işareti mi yarattığını, yoksa geliştiriciler ayrılsa bile başkalarının devreye girip bakımı sürdürebilmesi açısından açık kaynağın gerçekten istikrarlı olduğunun kanıtı mı olduğunu bilmiyorum. Yine de bu özelliğin Go’nun kendisinde sunulması güzel.
[0] https://gorilla.github.io/blog/2023-07-17-project-status-upd...
[1] https://www.reddit.com/r/golang/comments/1528e25/gorilla_web...
[2] https://news.ycombinator.com/item?id=36935541
Bunu daha önce yorumda[1] da yazmıştım ama önerinin sözdiziminin yanlış olduğunu düşünüyorum
Bir handler tanımlamak için tuhaf sihirli string’ler oluşturmak gerekiyor. Neden bunu gerçek argüman yapmadıklarını bilmiyorum; öyle olsa mevcut sabitler de daha kolay kullanılabilirdi
[1] https://github.com/golang/go/issues/61410#issuecomment-16580...
Mux arayüzüne yeni bir argüman eklemek mevcut kodu bozar; metot imzasını değiştiremeyince bu tür sihirli string’ler makul bir uzlaşma gibi görünüyor. HTTP metotları yakın zamanda değişecek de değil; kayıt anında ya da statik analizle doğrulaması da kolay. Sabit kullanmanın nasıl bir değer kattığını pek bilmiyorum; özellikle de geriye dönük uyumluluğu bozacaksa veya mevcut API’den çok az farklı yeni bir mux API’sini sonsuza dek paralel götürmek gerekecekse.
{parameter}ayrıştırması yüzünden zaten sihirli string değil mi?RFC2616 terimleriyle
Request-URIüzerine biraz parametre sihri eklenmiş gibi düşünüp, bunun istek satırına benzeyen[ Method SP ] Request-URIhaline geldiğini varsayabiliriz. Tamamen geriye dönük uyumlu ve dokümana bakmadan bile ne yaptığı neredeyse açıkMethodya önceden tanımlı birkaç sabitten biri ya daextension-method = token;tokenise boşluk, slash ve çeşitli parantezleri dışladığı için çok garip bir özel metotta bile karışma veya yanlış ayrıştırılma ihtimali yok gibi görünüyor.Get(),.Post()gibi metotların bir şeyi çözdüğünü hiç görmedim. Metodu string’in içine koyan tek bir kayıt metodu da muhtemelen tamamen iyi olurHEADroute’unu otomatik kaydetmenin nesi sorun, bilmiyorumBu yaklaşımı sevmiyorum. Neden stringleştirilmiş metot öneki kullandıklarını merak ediyorum; bir nedeni var mı?
Runtime’da doğrulanan sihirli string’ler yerine
mux.Get,mux.Postgibi fiile özel metotların tip güvenliği daha iyi. Editör otomatik tamamlama ya da IntelliSense de mümkün olurfunc Get(mux, uri, handler) { mux.HandleFunc("GET " + uri, handler) }gibi yazarsınBasit görünsün diye tipleri atladım
İlginç. Artık mux metoda kadar eşleştirebildiğine göre, route eşleşip metot eşleşmediğinde ne olacağını merak etmiştim. 404 mü 405 mi meselesi
Kontrol edince
Allowheader’ı düzgün doldurulmuş bir 405 döndüğünü gördümhttps://cs.opensource.google/go/go/+/master:src/net/http/ser...
String tabanlı arayüzden hoşlanmayan çok kişi olduğunu biliyorum ama HTTP metodu adını yanlış yazmaktansa doğru biçimde bambaşka bir şey girme ihtimalinin daha yüksek olduğunu düşünüyorum. Kişisel olarak statik analiz hatalı sözdizimi için uyarı verirse yeterli
Yine de zaten gelişmiş gereksinimleriniz varsa varsayılan serve mux’ı kullanmamak daha iyi bence. Çeşitli kullanım senaryolarına daha uygun çok seçenek var; zaten dinamik route üretimi yapıyorsanız, elinizdeki veri yapısını mevcut bir router’a zorla uydurmak yerine doğrudan kendi router’ınızı kullanmak daha az uğraştırabilir
Çakışan yolların panic etmek yerine tanımlandıkları sırayla eşleşmesi çok daha iyi olurdu
Örnekte
/task/0/{action}/yolu wildcard yoldan önce tanımlansaydı, önce onun eşleşmesini beklerdim. Özel durumlara yönelik handler’ları kolayca tanımlayabilmek için böyle olmalı“Yardımcı” bir şekilde başarısız olmak yerine, söylediğim gibi çalışması daha iyi bence. Biraz kötü kokuyor ve Go’ya pek yakışmıyor
Orijinal ServeMux, kayıt sırasına saygı duymayacak şekilde tasarlandı; çünkü kayıt sırasına bağımlı olmak tehlikeli. Go’nun temel tasarım hedeflerinden biri “ölçekli programlama”yı desteklemek ve uzaktaki kod değişikliklerinden kaynaklanan tuhaf yan etkiler tam da kaçınmak istediği türden şeyler
Tüm kayıtların tek bir paketteki tek bir fonksiyon içinde yapıldığı basit örnekte kayıt sırasının niyeti açık. Ama yeterince büyük bir kod tabanında kayıtların birden fazla yerde yapılması olası; hatta girdi dosyalarının veya üretilmiş kodun sonucu da olabilir. Üretilmiş kodun spesifikasyonunu yazan kişi, çalışma sırasının inceliklerini ve bunun routing’e etkisini bilmiyor olabilir. Paket import’larının sözlük sıralamasının değişmesi bile routing’i beklenmedik şekilde değiştirebilir
Beklenmeyen sonuçları önlemek, refactoring’i kolaylaştırmak ve karmaşık programlar üzerinde akıl yürütmeyi kolaylaştırmak açıkça Go’ya uygun bir yön. Tercihe her zaman katılmak zorunda değiliz ama gerekçe tutarlı
Dış bağımlılığın bir tane azalması güzel. Konu dışı olarak sqlc + Postgres + templ + htmx + Tailwind kombinasyonu geliştirme üretkenliği çok yüksek bir stack’ti. templ, Go için JSX gibi hissettiriyor
htmx’e de bir bakmam gerekecek
Svelte derlemesi eklenmiş bud’ın bu boşluğu doldurmasını umuyordum ama templ daha iyi bir alternatif gibi görünüyor
Bu öneri hiç hoşuma gitmiyor. HTTP istek metodunu URI’nin içine koymak; üstelik bunu yalnızca bazen yapmak ve belki
POST,PUT,PATCH /somethinggibi şeyler de önermek, kalsın.Mutlaka gerekiyorsa HTTP istek metodu adını alan özel bir metot yapılabilir.
HTTP metodu adını alan özel bir metodun, metodu string’in içine koymaya kıyasla hangi sorunu çözdüğünü merak ediyorum.
Metot ya da seçenek listesi için ayrı bir argüman konulabilirdi.
Go ana aracım değil ama Gin kullandım; şöyle bir şey:
router.GET("/", func(context *gin.Context) { ... })Diğer birçok dil ve framework’ten çok farklı görünmüyor.
GET /path/biçimini kullanan başka örnekler var mı merak ediyorum.Resmî dil ekibinin enum yerine önekli string kullanması çok tuhaf bir tasarım tercihi gibi görünüyor.
Örneğin mevcut
("GET /path")yerine(Http.GET, "/path")daha iyi görünüyor. Garip.Yine de bu yaklaşımdansa
mux.GET,mux.POSTgibi birden fazla fonksiyon olmasını tercih ederim.