- Go 1.22 में
net/http का ServeMux सुधार standard HTTP multiplexer में अधिक मजबूत pattern matching जोड़ता है, जिससे simple path matching के कारण third-party routers पर निर्भरता वाले हिस्से कम होते हैं
- नया mux patterns में HTTP methods और
/task/{id}/ जैसे path wildcards को साथ इस्तेमाल करने देता है, इसलिए वही path भी GET, POST, DELETE के लिए अलग-अलग handlers से जोड़ा जा सकता है
- matched values को
req.PathValue("id") से पढ़ा जाता है, और {id}... व {$} के जरिए आगे आने वाले पूरे path या exact path end तक control किया जा सकता है
- pattern conflicts registration के समय panic के रूप में सामने आते हैं, और बताते हैं कि कौन-सा path दोनों से match होता है और क्यों कोई भी pattern दूसरे से अधिक specific नहीं है
- सिर्फ standard mux से पर्याप्त servers की संख्या बढ़ सकती है, लेकिन
gorilla/mux या Gin जैसे third-party routers/frameworks अब भी अधिक व्यापक features और tools देते हैं
Go 1.22 में ServeMux के बदलाव
- Go 1.22 में
net/http के default HTTP serving multiplexer http.ServeMux में enhanced pattern matching आने वाली है
- मौजूदा
http.ServeMux मुख्य रूप से basic path matching पर केंद्रित था, इसलिए अधिक complex routing के लिए third-party libraries का बहुत उपयोग होता था
- नया mux advanced matching को standard library के अंदर लाकर third-party packages के साथ feature gap कम करता है
- तुलना के लिए existing standard library approach और
gorilla/mux इस्तेमाल करने वाले REST server examples लिए गए हैं
नए mux का basic usage
- जिन developers ने
gorilla/mux जैसे Go router packages इस्तेमाल किए हैं, उन्हें नए standard mux का usage familiar लगेगा
- example में
http.NewServeMux() से mux बनाया जाता है और HandleFunc में अधिक rich patterns register किए जाते हैं
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)
})
- पहला handler pattern में
GET method शामिल करता है, इसलिए यह केवल /path/ से शुरू होने वाली GET requests पर काम करता है
- दूसरा handler
/task/{id}/ के {id} से single path component match करता है, और handler में r.PathValue("id") से value पढ़ता है
- क्योंकि example उस समय का है जब Go 1.22 अभी release नहीं हुआ था, इसलिए
gotip चलाने की सलाह दी गई है
- test results routing rules को तुरंत verify कर देते हैं
/what/ request: 404 page not found
/path/ की GET request: got path
/path/ की POST request: Method Not Allowed
/task/f0cd2e/ request id=f0cd2e के रूप में handle होती है
अतिरिक्त pattern features और conflict handling
- नया
ServeMux basic wildcards के अलावा path matching को और finer control देता है
{id}... से आगे आने वाला पूरा path wildcard से match होता है
{$} से path end को strict रूप से match किया जाता है
- patterns के बीच priority और conflict rules handle किए जाते हैं
- conflict की संभावना proposal में खास तौर पर ध्यान दिया गया हिस्सा है
- उदाहरण के लिए, नीचे दिए गए दोनों patterns
/task/0/status/ request से match होते हैं
mux.HandleFunc("/task/{id}/status/", ...)
mux.HandleFunc("/task/0/{action}/", ...)
- इस स्थिति में नया
ServeMux registration के समय panic पैदा करता है
- error message एक example path दिखाता है जिसे दोनों patterns match करते हैं, और यह भी बताता है कि कोई भी pattern दूसरे से अधिक specific क्यों नहीं है
- complex code में जहां कई जगहों से patterns register होते हैं, ऐसी detailed conflict diagnostics खास तौर पर उपयोगी होती हैं
task server example का पुनः implementation
- मौजूदा REST Servers in Go series के task/todo-list server को Go 1.22 के improved mux से फिर implement किया गया है
- पूरा code stdlib-newmux example में है
- registered patterns HTTP methods और path wildcards को साथ इस्तेमाल करते हैं
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 example की तरह, same path को भी HTTP method-specific handlers में बांटा जा सकता है
- पिछले
http.ServeMux में same path के कई methods same handler में जाते थे, और handler के अंदर method check करना पड़ता था
- नए mux में routing decision handler के बाहर अधिक handle होता है, इसलिए handlers को अलग करना आसान हो जाता है
PathValue का उपयोग और सीमाएं
getTaskHandler path से निकाले गए ID को req.PathValue("id") से पढ़ता है और strconv.Atoi से integer conversion करता है
- नए mux का
{id} pattern केवल integers match करने वाला regex specify नहीं करता, इसलिए strconv.Atoi की error handling जरूरी है
- अगर ID conversion fail होता है तो
http.StatusBadRequest से response दिया जाता है, और storage में task नहीं मिलता तो http.StatusNotFound से response दिया जाता है
- final implementation
gorilla/mux इस्तेमाल करने वाले Part 2 solution से बहुत मिलता-जुलता है
- जरूरत होने पर नए
http.ServeMux के {$} wildcard से आगे आने वाले path को आसानी से restrict किया जा सकता है
standard mux और third-party routers की जगह
- Go beginners के लिए “कौन-सा router package इस्तेमाल करना चाहिए” एक अक्सर पूछा जाने वाला सवाल था
- Go 1.22 के बाद कई users महसूस कर सकते हैं कि third-party packages के बिना भी नया standard mux उनके लिए पर्याप्त है
gorilla/mux जैसे routers standard library की तुलना में अब भी अधिक features देते हैं
- Gin जैसे lightweight frameworks router के साथ-साथ web backend बनाने के लिए additional tools भी देते हैं
- standard library की capabilities मजबूत होना third-party packages इस्तेमाल करने वालों और केवल standard library इस्तेमाल करने वालों, दोनों के लिए positive बदलाव है
1 टिप्पणियां
Hacker News की राय
जब दो routes एक साथ match हों तो panic करना कुछ हद तक सहज-बोध के उलट लगता है। दूसरे web frameworks आम तौर पर पहले register किए गए matching route का उपयोग करते हैं, तो सोचता हूं कि क्या Go में इसके लिए कोई खास वजह है
“कई जगहों से HTTP routes register किए जा सकते हैं, इसलिए duplicate matching ढूंढना मुश्किल है” वाला edge case tools से हल किया जा सकता है। अपने पूरे करियर में मैंने “जो पहले match हो, वही जीतता है” वाला behavior काफी इस्तेमाल किया है, और ऐसे business requirements भी बहुत रहे हैं जहां
/foo/barको अलग route और/foo/{id}को दूसरे route के रूप में रखना पड़ता थाdesign proposal[1] में कहा गया है कि ज्यादा specific pattern match करने से मूल ServeMux patterns की order independence बरकरार रखी जा सकती है, लेकिन यह एक नजर में समझना मुश्किल हो सकता है कि दो patterns में कौन सा ज्यादा specific है या वे conflict क्यों कर रहे हैं। इसलिए conflicting pattern register होने पर आने वाला panic message example path के जरिए conflict दिखाता है
background discussion[2] में भी बताया गया है कि mux semantics
HandleयाHandleFunccalls के order पर निर्भर नहीं होते। order independence की वजह से package initialization order महत्वपूर्ण नहीं रहता और code refactoring भी आसान होती है। इसलिए ties को registration order से resolve न करना, duplicate registration पर panic करना, और registration order पर निर्भर semantics से बचना मुख्य design goal बना हुआ है[1]: https://github.com/golang/go/issues/61410
[2]: https://github.com/golang/go/discussions/60227
हाल ही में एक personal project में gorilla/mux इस्तेमाल करते हुए गलती से overlapping routes बनाने वाला bug मिला; अगर router चलने के बजाय panic करता, तो वह मुझे URLs refactor करने के लिए मजबूर करता और bug पूरी तरह टल जाता
initfunctions के order पर असर पड़ सकता हैअगर
/foo/barऔर/foo/{id}जैसा behavior चाहिए, तो base path लेने वाला handler बनाइए और उसके अंदर दूसरे handler functions में explicit branching कर दीजिए। अगर standard library में मौजूद न होने वाला behavior चाहिए, तो ज्यादा features वाली दूसरी libraries भी बहुत हैंGo की standard library और language संभावित ambiguity के सामने हमेशा conservative side की ओर झुकी रही हैं, और इसी वजह से standard library कैसे behave करती है, इसकी मजबूत और सटीक intuition बनाना आसान होता है। code लिखते समय कुछ seconds बचाने से कहीं ज्यादा मूल्यवान है उसे जल्दी और सही तरीके से पढ़ और समझ पाना
/foo/barऔर/foo/{id}शायद allow होंगे। पहला ज्यादा specific है, इसलिए उसे priority मिलेगी, लिहाजा यह ठीक लगता हैहालांकि अगर
/foo/{id}/deleteऔर/foo/bar/{action}हों, तो/foo/bar/deleteदोनों से match करेगा और कोई भी ज्यादा specific नहीं होगा, इसलिए शायद panic होगा। यह reasonable लगता है, लेकिन पहले register किए गए route को जीतने वाली priority बेहतर भी हो सकती है$regex छूट जाने वाले route की वजह से मुझे सच में ऐसा अनुभव हुआ हैgorilla/mux project थोड़ा अजीब और भ्रमित करने वाला है। पिछले साल maintainers ने project archive कर दिया था, इसलिए मैं gin-gonic नाम के दूसरे multiplexer पर चला गया था
लेकिन इस लेख में इसका जिक्र देखकर दोबारा जांचा तो यह अब archived नहीं था। पता नहीं यह पूरे project की stability पर सवाल उठाता है, या यह open source के वाकई stable होने का सबूत है कि developers हट जाएं तब भी दूसरे लोग आगे आकर maintenance जारी रख सकते हैं। फिर भी यह अच्छा है कि यह feature Go में ही मिल रहा है
[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
मैंने पहले एक कमेंट[1] में भी लिखा था, लेकिन मुझे लगता है कि proposal का syntax गलत है
handler define करने के लिए अजीब magic string बनानी पड़ती है। समझ नहीं आता कि इसे असली argument क्यों नहीं बनाया गया; ऐसा होता तो मौजूदा constants भी ज्यादा आसानी से इस्तेमाल किए जा सकते थे
[1] https://github.com/golang/go/issues/61410#issuecomment-16580...
Mux interface में नया argument जोड़ने से मौजूदा code टूट जाएगा, और method signature बदला नहीं जा सकता, इसलिए ऐसी magic string एक reasonable compromise लगती है। HTTP methods भी जल्द बदलने वाले नहीं हैं, और registration time या static analysis से validate करना भी आसान है। constants इस्तेमाल करने से क्या value मिलती है, यह मुझे साफ नहीं है, खासकर अगर इसके लिए backward compatibility तोड़नी पड़े या मौजूदा API से बस थोड़ा अलग नया mux API हमेशा parallel चलाना पड़े
{parameter}parsing की वजह से यह पहले से ही magic string नहीं है क्याइसे RFC2616 terminology में
Request-URIके साथ थोड़ी parameter magic जुड़ी हुई समझें, और सोचें कि यह request line जैसा दिखने वाला[ Method SP ] Request-URIबन जाता है। यह पूरी तरह backward compatible है, और documentation देखे बिना भी लगभग साफ है कि यह क्या करता हैMethodया तो कुछ predefined constants में से एक होता है याextension-method = token, औरtokenwhitespace, slash और तरह-तरह के brackets को exclude करता है, इसलिए बहुत अजीब custom method भी confuse होने या गलत parse होने की संभावना नहीं लगती.Get(),.Post()जैसे methods ने कभी कोई खास समस्या हल नहीं की। string के अंदर method डालने वाला single registration method भी शायद पूरी तरह ठीक रहेगाHEADroute को automatically register करने में दिक्कत क्या है, समझ नहीं आतामुझे यह तरीका पसंद नहीं है। सोच रहा हूं कि stringified method prefix इस्तेमाल करने की कोई वजह है क्या
runtime पर validate होने वाली magic string की तुलना में
mux.Get,mux.Postजैसे verb-specific methods की type safety बेहतर है। editor autocomplete या IntelliSense भी मिल सकता हैfunc Get(mux, uri, handler) { mux.HandleFunc("GET " + uri, handler) }जैसा बना सकते हैंsimple दिखाने के लिए types छोड़े हैं
दिलचस्प। अब mux method तक match कर सकता है, तो मैं सोच रहा था कि route match हो लेकिन method match न हो तो क्या होता है। सवाल 404 बनाम 405 का है
check करने पर पता चला कि सही तरह भरे गए
Allowheader के साथ 405 return होता हैhttps://cs.opensource.google/go/go/+/master:src/net/http/ser...
मुझे पता है कि बहुत लोगों को string-based interface पसंद नहीं है, लेकिन मुझे लगता है कि HTTP method name में typo करने की बजाय लोग सही ढंग से कोई गलत चीज enter करने की संभावना ज्यादा रखते हैं। व्यक्तिगत रूप से, अगर static analysis गलत syntax पर warning दे दे तो मेरे लिए काफी है
हालांकि अगर पहले से advanced requirements हैं, तो basic serve mux इस्तेमाल न करना बेहतर है। अलग-अलग use cases के लिए ज्यादा उपयुक्त विकल्प बहुत हैं, और अगर आप पहले से dynamic route generation कर रहे हैं, तो अपने data structures को किसी मौजूदा router में जबरदस्ती fit कराने के बजाय अपना router इस्तेमाल करना कम मेहनत वाला हो सकता है
overlapping paths के लिए panic के बजाय defined order में matching होना कहीं बेहतर होगा
example में अगर
/task/0/{action}/path को wildcard path से पहले define किया गया होता, तो मैं उम्मीद करता कि वही पहले match होगा। इससे special cases के लिए handler define करना आसान होगा“helpful” तरीके से fail होने के बजाय, मेरे कहे मुताबिक बस काम करना ज्यादा अच्छा है। इसमें थोड़ी अजीब smell है और यह Go जैसा नहीं लगता
original ServeMux को registration order का सम्मान न करने के लिए design किया गया था, क्योंकि registration order पर depend करना risky है। Go के core design goals में से एक “programming at scale” को support करना है, और दूर पड़े code changes से पैदा होने वाले अजीब side effects ठीक वही चीज हैं जिनसे बचना है
अगर सभी registrations किसी single package के single function में होने वाले simple example हों, तो registration order का intent साफ होता है। लेकिन बड़े codebase में registrations कई जगहों पर हो सकते हैं, और input files या generated code का result भी हो सकते हैं। generated code spec लिखने वाले को execution order की subtleties और routing पर उनके असर की जानकारी न हो सकती है। सिर्फ package imports की lexicographic sorting बदलने से routing unexpectedly बदल सकती है
unexpected results से बचना, refactoring आसान बनाना, और complex programs के बारे में reasoning आसान करना साफ तौर पर Go जैसी दिशा है। हर choice से सहमत होना जरूरी नहीं, लेकिन वजह consistent है
अच्छा है कि एक external dependency कम हुई। वैसे sqlc + Postgres + templ + htmx + Tailwind combo development productivity के लिए बहुत high stack रहा। templ Go के लिए JSX जैसा लगता है
htmx भी एक बार देखना पड़ेगा
मुझे उम्मीद थी कि Svelte compilation जोड़ने वाला bud इस gap को भर देगा, लेकिन templ बेहतर alternative लगता है
यह प्रस्ताव सच में पसंद नहीं आया। HTTP request method को URI के अंदर डालना, वह भी कभी-कभी ही, और शायद
POST,PUT,PATCH /somethingजैसा कुछ करने को कहना—मैं तो इससे बचना चाहूंगाअगर करना ही है, तो HTTP request method का नाम लेने वाला एक dedicated method बना देना चाहिए
मुझे जानना है कि HTTP method name लेने वाला dedicated method, string में method शामिल करने की तुलना में कौन-सी समस्या हल करता है
methods या options की list के लिए अलग argument रखा जा सकता था
Go मेरा मुख्य tool नहीं है, लेकिन मैंने Gin इस्तेमाल किया है, और उसमें यह ऐसे होता है:
router.GET("/", func(context *gin.Context) { ... })कई दूसरी languages/frameworks से बहुत अलग नहीं दिखता। सोच रहा हूं कि
GET /path/वाला तरीका इस्तेमाल करने के और उदाहरण हैं या नहींofficial language team का enum के बजाय prefixed string इस्तेमाल करना बहुत अजीब design choice लगता है
उदाहरण के लिए, मौजूदा
("GET /path")से(Http.GET, "/path")बेहतर लगता है। अजीब हैफिर भी, उस तरीके के बजाय मैं
mux.GET,mux.POSTजैसे कई functions होना पसंद करूंगा