1 पॉइंट द्वारा GN⁺ 2023-10-17 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • 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 टिप्पणियां

 
GN⁺ 2023-10-17
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 के रूप में रखना पड़ता था

    • चूंकि यह guarantee नहीं की जा सकती कि registration order हमेशा एक जैसा रहेगा, यह व्यावहारिक रूप से undefined behavior बन जाता है। असल ServeMux को इसी तरह design और implement किया गया था, और उस behavior को आगे भी support करना उपयोगी माना गया
      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 या HandleFunc calls के 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
    • Go जैसी compiled, typed language इस्तेमाल करने की वजह bugs को और left shift करना है। production में किसी corner case पर फूटने वाले bug से बेहतर है कि bug application startup पर पकड़ा जाए, और startup पर पकड़े जाने वाले bug से भी बेहतर है कि वह compile time पर पकड़ा जाए
      हाल ही में एक personal project में gorilla/mux इस्तेमाल करते हुए गलती से overlapping routes बनाने वाला bug मिला; अगर router चलने के बजाय panic करता, तो वह मुझे URLs refactor करने के लिए मजबूर करता और bug पूरी तरह टल जाता
    • panic अपने-आप में एक tool है। यह ambiguous और reason करना मुश्किल code लिखने से रोकता है, और initialization order जैसे मनमाने ढंग से बदल सकने वाले factors पर निर्भर नहीं रहने देता। उदाहरण के लिए, सिर्फ file name बदलने से भी Go के file-level init functions के 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 बेहतर भी हो सकती है
    • वास्तव में execute हुआ route कोई दूसरा route हो और उससे गलत data मिले, इसकी तुलना में error मिलना debugging के लिए कहीं बेहतर है। Django में अंत का $ regex छूट जाने वाले route की वजह से मुझे सच में ऐसा अनुभव हुआ है
  • gorilla/mux project थोड़ा अजीब और भ्रमित करने वाला है। पिछले साल maintainers ने project archive कर दिया था, इसलिए मैं gin-gonic नाम के दूसरे multiplexer पर चला गया था
    लेकिन इस लेख में इसका जिक्र देखकर दोबारा जांचा तो यह अब archived नहीं था। पता नहीं यह पूरे project की stability पर सवाल उठाता है, या यह open source के वाकई stable होने का सबूत है कि developers हट जाएं तब भी दूसरे लोग आगे आकर maintenance जारी रख सकते हैं। फिर भी यह अच्छा है कि यह feature Go में ही मिल रहा है

  • मैंने पहले एक कमेंट[1] में भी लिखा था, लेकिन मुझे लगता है कि proposal का syntax गलत है
    handler define करने के लिए अजीब magic string बनानी पड़ती है। समझ नहीं आता कि इसे असली argument क्यों नहीं बनाया गया; ऐसा होता तो मौजूदा constants भी ज्यादा आसानी से इस्तेमाल किए जा सकते थे
    [1] https://github.com/golang/go/issues/61410#issuecomment-16580...

    • मुझे लगता है कि यह Go की backward compatibility guarantee की वजह से है
      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, और token whitespace, slash और तरह-तरह के brackets को exclude करता है, इसलिए बहुत अजीब custom method भी confuse होने या गलत parse होने की संभावना नहीं लगती
    • मैं मानता हूं कि यह थोड़ा अजीब है, लेकिन वजह साफ है। वे मौजूदा public interface को तोड़ना या बदलना नहीं चाहते
    • शुरुआत में मुझे भी ऐसा ही लगा था, लेकिन सच कहूं तो Gin इस्तेमाल करते समय .Get(), .Post() जैसे methods ने कभी कोई खास समस्या हल नहीं की। string के अंदर method डालने वाला single registration method भी शायद पूरी तरह ठीक रहेगा
    • HEAD route को automatically register करने में दिक्कत क्या है, समझ नहीं आता
  • मुझे यह तरीका पसंद नहीं है। सोच रहा हूं कि stringified method prefix इस्तेमाल करने की कोई वजह है क्या
    runtime पर validate होने वाली magic string की तुलना में mux.Get, mux.Post जैसे verb-specific methods की type safety बेहतर है। editor autocomplete या IntelliSense भी मिल सकता है

    • यह तो बहुत आसानी से add किया जा सकता है। अगर सच में इतना फर्क पड़ता है तो func Get(mux, uri, handler) { mux.HandleFunc("GET " + uri, handler) } जैसा बना सकते हैं
      simple दिखाने के लिए types छोड़े हैं
    • मुझे भी पसंद नहीं है। मैं यह भरोसा रखना चाहता हूं कि routing runtime नहीं, compile time पर काम करती है
    • type safety चाहिए तो type-safe language चुनें
    • strings के इस्तेमाल को मैं prefer नहीं करता, लेकिन fair तरीके से देखें तो request आने पर HTTP method भी आखिर string ही होता है। HTTP packet की पहली line के prefix से match होने में एक सुंदरता तो है
    • यह बात proposal में काफी विस्तार से cover की गई है
  • दिलचस्प। अब mux method तक match कर सकता है, तो मैं सोच रहा था कि route match हो लेकिन method match न हो तो क्या होता है। सवाल 404 बनाम 405 का है
    check करने पर पता चला कि सही तरह भरे गए Allow header के साथ 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 जैसा नहीं लगता

    • language semantics महत्वपूर्ण हैं, और यह असल में काफी 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 जैसा लगता है

    • अभी तक sqlc इस्तेमाल करते हुए मेरा experience काफी अच्छा रहा है
      htmx भी एक बार देखना पड़ेगा
    • यहां Tailwind को कैसे handle किया जाता है, यह जानना चाहूंगा। Tailwind जोड़ने का idea सच में अच्छा है, लेकिन अगर npm इस्तेमाल करना पड़े तो मैं सीधे CSS लिखना पसंद करूंगा
    • पहले मुझे लगा template की typo है, लेकिन असल में templ नाम का standard library से अलग package था। docs थोड़ी देर देखीं तो काफी clean लगा
      मुझे उम्मीद थी कि Svelte compilation जोड़ने वाला bud इस gap को भर देगा, लेकिन templ बेहतर alternative लगता है
    • templ के लिए IntelliJ support होता तो बहुत अच्छा होता। idea पसंद है
  • यह प्रस्ताव सच में पसंद नहीं आया। HTTP request method को URI के अंदर डालना, वह भी कभी-कभी ही, और शायद POST,PUT,PATCH /something जैसा कुछ करने को कहना—मैं तो इससे बचना चाहूंगा
    अगर करना ही है, तो HTTP request method का नाम लेने वाला एक dedicated method बना देना चाहिए

    • पता नहीं क्यों। असल में इससे बहुत बड़ा फर्क भी नहीं पड़ता। सिर्फ एक स्टाइल के मुद्दे की वजह से पूरे प्रस्ताव से सच में नफरत करना ठीक नहीं लगता
      मुझे जानना है कि HTTP method name लेने वाला dedicated method, string में method शामिल करने की तुलना में कौन-सी समस्या हल करता है
    • सहमत। इस तरह design करना ज़्यादा समझ में नहीं आता
      methods या options की list के लिए अलग argument रखा जा सकता था
  • Go मेरा मुख्य tool नहीं है, लेकिन मैंने Gin इस्तेमाल किया है, और उसमें यह ऐसे होता है:
    router.GET("/", func(context *gin.Context) { ... })
    कई दूसरी languages/frameworks से बहुत अलग नहीं दिखता। सोच रहा हूं कि GET /path/ वाला तरीका इस्तेमाल करने के और उदाहरण हैं या नहीं

    • backward compatibility के अलावा ऐसा करने की कोई वजह नहीं है, इसलिए शायद लगभग नहीं होंगे
    • असल में मैंने ऐसा देखा नहीं है। यह बस एक खराब तरीका है
  • official language team का enum के बजाय prefixed string इस्तेमाल करना बहुत अजीब design choice लगता है
    उदाहरण के लिए, मौजूदा ("GET /path") से (Http.GET, "/path") बेहतर लगता है। अजीब है

    • यह Go 1 compatibility promise निभाने का तरीका है
    • Go developer के तौर पर, ऐसे मामलों में backward compatibility के लिए function overloading होती तो अच्छा होता। compatibility promise शानदार है, लेकिन उससे पैदा होने वाली अजीब चीजें अच्छी नहीं लगतीं
    • यह ऐसी गलती है जिसे existing code तोड़े बिना या नया function introduce किए बिना ठीक नहीं किया जा सकता
      फिर भी, उस तरीके के बजाय मैं mux.GET, mux.POST जैसे कई functions होना पसंद करूंगा