مسیریابی درخواستها
داشتن یک برنامه وب با فقط یک مسیر خیلی هیجانانگیز نیست… یا مفید! بیایید چند مسیر دیگر اضافه کنیم تا برنامه شروع به شکل گرفتن کند مانند این:
| Route pattern | Handler | Action |
|---|---|---|
| / | home | نمایش صفحه اصلی |
| /snippet/view | snippetView | نمایش یک تکه متن خاص |
| /snippet/create | snippetCreate | نمایش فرم برای ایجاد یک تکه متن جدید |
فایل main.go را دوباره باز کنید و آن را به صورت زیر بهروزرسانی کنید:
package main import ( "log" "net/http" ) func home(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hello from Snippetbox")) } // Add a snippetView handler function. func snippetView(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Display a specific snippet...")) } // Add a snippetCreate handler function. func snippetCreate(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Display a form for creating a new snippet...")) } func main() { // Register the two new handler functions and corresponding route patterns with // the servemux, in exactly the same way that we did before. mux := http.NewServeMux() mux.HandleFunc("/", home) mux.HandleFunc("/snippet/view", snippetView) mux.HandleFunc("/snippet/create", snippetCreate) log.Print("starting server on :4000") err := http.ListenAndServe(":4000", mux) log.Fatal(err) }
مطمئن شوید که این تغییرات ذخیره شدهاند و سپس برنامه وب را دوباره راهاندازی کنید:
$ cd $HOME/code/snippetbox $ go run . 2024/03/18 11:29:23 starting server on :4000
اگر لینکهای زیر را در مرورگر وب خود بازدید کنید، اکنون باید پاسخ مناسب برای هر مسیر را دریافت کنید:
اسلشهای انتهایی در الگوهای مسیر
حالا که دو مسیر جدید راهاندازی و اجرا شدهاند، بیایید کمی تئوری صحبت کنیم.
مهم است بدانید که servemux در Go قوانین تطبیق متفاوتی دارد بسته به اینکه الگوی مسیر با یک اسلش انتهایی به پایان میرسد یا نه.
دو الگوی مسیر جدید ما — "/snippet/view" و "/snippet/create" — با اسلش انتهایی به پایان نمیرسند. وقتی یک الگو اسلش انتهایی ندارد، فقط زمانی تطبیق داده میشود (و handler مربوطه فراخوانی میشود) که مسیر URL درخواست دقیقاً با الگو به طور کامل مطابقت داشته باشد.
وقتی یک الگوی مسیر با یک اسلش انتهایی به پایان میرسد — مانند "/" یا "/static/" — به عنوان یک الگوی مسیر زیردرختی (subtree path pattern) شناخته میشود. الگوهای مسیر زیردرختی زمانی تطبیق داده میشوند (و handler مربوطه فراخوانی میشود) که شروع مسیر URL درخواست با مسیر زیردرختی مطابقت داشته باشد. اگر به درک شما کمک میکند، میتوانید مسیرهای زیردرختی را کمی شبیه به داشتن یک wildcard در انتها در نظر بگیرید، مانند "/**" یا "/static/**".
این کمک میکند توضیح دهد که چرا الگوی مسیر "/" مانند یک catch-all عمل میکند. الگو اساساً به معنای تطبیق یک اسلش، به دنبال هر چیزی (یا هیچ چیز) است.
محدود کردن مسیرهای زیردرختی
برای جلوگیری از عمل کردن الگوهای مسیر زیردرختی مانند داشتن یک wildcard در انتها، میتوانید دنباله کاراکتر خاص {$} را به انتهای الگو اضافه کنید — مانند "/{$}" یا "/static/{$}".
پس اگر الگوی مسیر "/{$}" داشته باشید، به طور مؤثر به معنای تطبیق یک اسلش، به دنبال هیچ چیز دیگری است. فقط درخواستهایی را تطبیق میدهد که مسیر URL دقیقاً / باشد.
بیایید از این در برنامه خود استفاده کنیم تا handler home ما از عمل کردن به عنوان catch all متوقف شود، مانند این:
package main ... func main() { mux := http.NewServeMux() mux.HandleFunc("/{$}", home) // Restrict this route to exact matches on / only. mux.HandleFunc("/snippet/view", snippetView) mux.HandleFunc("/snippet/create", snippetCreate) log.Print("starting server on :4000") err := http.ListenAndServe(":4000", mux) log.Fatal(err) }
پس از انجام این تغییر، سرور را دوباره راهاندازی کنید و یک درخواست برای یک مسیر URL ثبت نشده مانند http://localhost:4000/foo/bar ارسال کنید. اکنون باید یک پاسخ 404 دریافت کنید که کمی شبیه به این است:
اطلاعات اضافی
ویژگیهای اضافی servemux
چند ویژگی دیگر servemux وجود دارد که ارزش اشاره دارد:
مسیرهای URL درخواست به طور خودکار پاکسازی میشوند. اگر مسیر درخواست شامل هر عنصر
.یا..یا اسلشهای تکراری باشد، کاربر به طور خودکار به یک URL تمیز معادل هدایت میشود. برای مثال، اگر کاربر درخواستی به/foo/bar/..//bazارسال کند، به طور خودکار یک301 Permanent Redirectبه/foo/bazارسال میشود.اگر یک مسیر زیردرختی ثبت شده باشد و درخواستی برای آن مسیر زیردرختی بدون اسلش انتهایی دریافت شود، سپس کاربر به طور خودکار یک
301 Permanent Redirectبه مسیر زیردرختی با اسلش اضافه شده ارسال میشود. برای مثال، اگر مسیر زیردرختی/foo/را ثبت کردهاید، سپس هر درخواستی به/fooبه/foo/هدایت میشود.
تطبیق نام host
امکان شامل کردن نامهای host در الگوهای مسیر شما وجود دارد. این میتواند مفید باشد وقتی میخواهید همه درخواستهای HTTP را به یک URL استاندارد (canonical URL) هدایت کنید، یا اگر برنامه شما به عنوان backend برای چندین سایت یا سرویس عمل میکند. برای مثال:
mux := http.NewServeMux() mux.HandleFunc("foo.example.org/", fooHandler) mux.HandleFunc("bar.example.org/", barHandler) mux.HandleFunc("/baz", bazHandler)
وقتی نوبت به تطبیق الگو (pattern matching) میرسد، هر الگوی خاص host ابتدا بررسی میشود و اگر تطبیقی وجود داشته باشد، درخواست به handler مربوطه ارسال میشود. فقط زمانی که تطبیق خاص host یافت نشود، الگوهای غیر خاص host نیز بررسی میشوند.
servemux پیشفرض
اگر مدتی با Go کار کردهاید، ممکن است با توابع http.Handle() و http.HandleFunc() برخورد کرده باشید. اینها به شما اجازه میدهند مسیرها را بدون اعلام صریح یک servemux ثبت کنید، مانند این:
func main() { http.HandleFunc("/", home) http.HandleFunc("/snippet/view", snippetView) http.HandleFunc("/snippet/create", snippetCreate) log.Print("starting server on :4000") err := http.ListenAndServe(":4000", nil) log.Fatal(err) }
در پشت صحنه، این توابع مسیرهای خود را با چیزی به نام servemux پیشفرض (default servemux) ثبت میکنند. این فقط یک servemux معمولی مانند آنچه که قبلاً استفاده میکردیم است، اما به طور خودکار توسط Go مقداردهی اولیه میشود و در متغیر سراسری http.DefaultServeMux ذخیره میشود.
اگر nil را به عنوان آرگومان دوم به http.ListenAndServe() ارسال کنید، سرور از http.DefaultServeMux برای مسیریابی استفاده میکند.
اگرچه این رویکرد میتواند کد شما را کمی کوتاهتر کند، اما به دو دلیل آن را توصیه نمیکنم:
کمتر صریح است و بیشتر از اعلام و استفاده از servemux با محدوده محلی خودتان ‘جادویی’ به نظر میرسد.
چون
http.DefaultServeMuxیک متغیر سراسری در کتابخانه استاندارد است، به این معنی است که هر کد Go در پروژه شما میتواند به آن دسترسی داشته باشد و به طور بالقوه یک مسیر ثبت کند. اگر یک کدبیس پروژه بزرگ دارید (به خصوص یکی که توسط افراد زیادی کار میشود)، این میتواند اطمینان از اینکه همه اعلامهای مسیر برای برنامه شما به راحتی در یک مکان مرکزی قابل کشف هستند را سختتر کند.همچنین به این معنی است که هر بسته شخص ثالث که برنامه شما import میکند نیز میتواند مسیرها را با
http.DefaultServeMuxثبت کند. اگر یکی از آن بستههای شخص ثالث به خطر بیفتد، میتوانند به طور بالقوه ازhttp.DefaultServeMuxبرای افشای یک handler مخرب به وب استفاده کنند. ساده است که این خطر را با فقط استفاده نکردن ازhttp.DefaultServeMuxاجتناب کنید.
پس، به خاطر وضوح، قابلیت نگهداری و امنیت، به طور کلی ایده خوبی است که از http.DefaultServeMux و توابع کمکی مربوطه اجتناب کنید. در عوض از servemux با محدوده محلی خود استفاده کنید، مانند کاری که تا کنون در این پروژه انجام دادهایم.