الگوهای مسیر با کاراکترهای جایگزین
همچنین امکان تعریف الگوهای مسیر که شامل بخشهای wildcard هستند وجود دارد. میتوانید از اینها برای ایجاد قوانین مسیریابی انعطافپذیرتر استفاده کنید، و همچنین برای ارسال متغیرها به برنامه Go خود از طریق URL درخواست. اگر قبلاً برنامههای وب با استفاده از فریمورکها در زبانهای دیگر ساختهاید، مفاهیم این فصل احتمالاً برای شما آشنا به نظر میرسند.
بیایید برای لحظهای از ساخت برنامه خود فاصله بگیریم تا توضیح دهیم چگونه کار میکند.
بخشهای wildcard در یک الگوی مسیر با یک شناسه wildcard داخل براکتهای {} نشان داده میشوند. مانند این:
mux.HandleFunc("/products/{category}/item/{itemID}", exampleHandler)
در این مثال، الگوی مسیر شامل دو بخش wildcard است. بخش اول شناسه category دارد و بخش دوم شناسه itemID دارد.
قوانین تطبیق برای الگوهای مسیر حاوی بخشهای wildcard همانهایی هستند که در فصل قبل دیدیم، با قانون اضافی که مسیر درخواست میتواند شامل هر مقدار غیر خالی برای بخشهای wildcard باشد. پس، برای مثال، درخواستهای زیر همگی با مسیری که در بالا تعریف کردیم تطبیق داده میشوند:
/products/hammocks/item/sku123456789 /products/seasonal-plants/item/pdt-1234-wxyz /products/experimental_foods/item/quantum%20bananas
داخل handler خود، میتوانید مقدار مربوطه برای یک بخش wildcard را با استفاده از شناسه آن و متد r.PathValue() بازیابی کنید. برای مثال:
func exampleHandler(w http.ResponseWriter, r *http.Request) { category := r.PathValue("category") itemID := r.PathValue("itemID") ... }
متد r.PathValue() همیشه یک مقدار string برمیگرداند، و مهم است به یاد داشته باشید که این میتواند هر مقداری باشد که کاربر در URL قرار میدهد — پس باید مقدار را اعتبارسنجی یا بررسی کنید قبل از انجام هر کار مهمی با آن.
استفاده از بخشهای wildcard در عمل
خوب، بیایید به برنامه خود برگردیم و آن را بهروزرسانی کنیم تا شامل یک بخش wildcard جدید {id} در مسیر /snippet/view شود، تا مسیرهای ما شبیه به این شوند:
| Route pattern | Handler | Action |
|---|---|---|
| /{$} | home | نمایش صفحه اصلی |
| /snippet/view/{id} | snippetView | نمایش یک تکه متن خاص |
| /snippet/create | snippetCreate | نمایش فرم برای ایجاد یک تکه متن جدید |
فایل main.go خود را باز کنید و این تغییر را مانند این انجام دهید:
package main ... func main() { mux := http.NewServeMux() mux.HandleFunc("/{$}", home) mux.HandleFunc("/snippet/view/{id}", snippetView) // Add the {id} wildcard segment mux.HandleFunc("/snippet/create", snippetCreate) log.Print("starting server on :4000") err := http.ListenAndServe(":4000", mux) log.Fatal(err) }
حالا بیایید به handler snippetView خود برویم و آن را بهروزرسانی کنیم تا مقدار id را از مسیر URL درخواست بازیابی کند. بعداً در کتاب، از این مقدار id برای انتخاب یک تکه متن خاص از پایگاه داده استفاده خواهیم کرد، اما برای حالا فقط id را به عنوان بخشی از پاسخ HTTP به کاربر برمیگردانیم.
چون مقدار id ورودی کاربر غیرقابل اعتماد است، باید آن را اعتبارسنجی کنیم تا مطمئن شویم که معقول و منطقی است قبل از استفاده از آن. برای هدف برنامه ما میخواهیم بررسی کنیم که مقدار id شامل یک عدد صحیح مثبت است، که میتوانیم با تلاش برای تبدیل مقدار رشته به یک عدد صحیح با تابع strconv.Atoi() و سپس بررسی اینکه مقدار بزرگتر از صفر است انجام دهیم.
اینجا نحوه انجام آن است:
package main import ( "fmt" // New import "log" "net/http" "strconv" // New import ) ... func snippetView(w http.ResponseWriter, r *http.Request) { // Extract the value of the id wildcard from the request using r.PathValue() // and try to convert it to an integer using the strconv.Atoi() function. If // it can't be converted to an integer, or the value is less than 1, we // return a 404 page not found response. id, err := strconv.Atoi(r.PathValue("id")) if err != nil || id < 1 { http.NotFound(w, r) return } // Use the fmt.Sprintf() function to interpolate the id value with a // message, then write it as the HTTP response. msg := fmt.Sprintf("Display a specific snippet with ID %d...", id) w.Write([]byte(msg)) } ...
تغییرات خود را ذخیره کنید، برنامه را دوباره راهاندازی کنید، سپس مرورگر خود را باز کنید و سعی کنید از URL مانند http://localhost:4000/snippet/view/123 بازدید کنید. باید یک پاسخ حاوی مقدار wildcard id برگردانده شده از URL درخواست را ببینید، شبیه به این:
همچنین ممکن است بخواهید سعی کنید از برخی URLهایی که مقادیر نامعتبر برای wildcard id دارند، یا اصلاً مقدار wildcard ندارند بازدید کنید. برای مثال:
http://localhost:4000/snippet/view/http://localhost:4000/snippet/view/-1http://localhost:4000/snippet/view/foo
برای همه این درخواستها باید یک پاسخ 404 page not found دریافت کنید.
اطلاعات اضافی
اولویت و تعارض
هنگام تعریف الگوهای مسیر با بخشهای wildcard، ممکن است برخی از الگوهای شما ‘همپوشانی’ داشته باشند. برای مثال، اگر مسیرهایی با الگوهای "/post/edit" و "/post/{id}" تعریف کنید، آنها همپوشانی دارند چون یک درخواست HTTP ورودی با مسیر /post/edit یک تطبیق معتبر برای هر دو الگو است.
وقتی الگوهای مسیر همپوشانی دارند، servemux در Go باید تصمیم بگیرد که کدام الگو اولویت دارد تا بتواند درخواست را به handler مناسب ارسال کند.
قانون برای این کار بسیار مرتب و مختصر است: الگوی مسیر خاصتر برنده میشود. به طور رسمی، Go یک الگو را خاصتر از دیگری تعریف میکند اگر فقط یک زیرمجموعه از درخواستهایی که الگوی دیگر تطبیق میدهد را تطبیق دهد.
با ادامه مثال بالا، الگوی مسیر "/post/edit" فقط درخواستهایی با مسیر دقیق /post/edit را تطبیق میدهد، در حالی که الگوی "/post/{id}" درخواستهایی با مسیر /post/edit، /post/123، /post/abc و موارد بیشتر را تطبیق میدهد. بنابراین "/post/edit" الگوی مسیر خاصتر است و اولویت خواهد داشت.
در حالی که روی این موضوع هستیم، چند چیز دیگر ارزش ذکر دارد:
یک عارضه جانبی خوب از قانون الگوی خاصتر برنده میشود این است که میتوانید الگوها را به هر ترتیبی ثبت کنید و این نحوه رفتار servemux را تغییر نمیدهد.
یک مورد خاص (edge case) بالقوه وجود دارد که در آن دو الگوی مسیر همپوشانی دارید اما هیچ کدام به وضوح خاصتر از دیگری نیست. برای مثال، الگوهای
"/post/new/{id}"و"/post/{author}/latest"همپوشانی دارند چون هر دو مسیر درخواست/post/new/latestرا تطبیق میدهند، اما واضح نیست که کدام یک باید اولویت داشته باشد. در این سناریو، servemux در Go الگوها را در تعارض (conflict) در نظر میگیرد، و هنگام مقداردهی اولیه (initializing) مسیرها در زمان اجرا panic میکند.فقط به این دلیل که servemux در Go از مسیرهای همپوشانی پشتیبانی میکند، به این معنی نیست که باید از آنها استفاده کنید! داشتن الگوهای مسیر همپوشانی خطر باگها و رفتارهای ناخواسته در برنامه شما را افزایش میدهد، و اگر آزادی طراحی ساختار URL برای برنامه خود را دارید، به طور کلی تمرین خوبی است که همپوشانیها را به حداقل برسانید یا کاملاً از آنها اجتناب کنید.
الگوهای مسیر زیردرختی با wildcard
مهم است درک کنیم که قوانین مسیریابی که در فصل قبل توضیح دادیم هنوز اعمال میشوند، حتی وقتی از بخشهای wildcard استفاده میکنید. به طور خاص، اگر الگوی مسیر شما با یک اسلش انتهایی به پایان میرسد و {$} در انتها ندارد، سپس به عنوان یک الگوی مسیر زیردرختی (subtree path pattern) در نظر گرفته میشود و فقط نیاز دارد که شروع مسیر URL درخواست تطبیق داشته باشد.
پس، اگر یک الگوی مسیر زیردرختی مانند "/user/{id}/" در مسیرهای خود دارید (توجه کنید به اسلش انتهایی)، این الگو درخواستهایی مانند /user/1/، /user/2/a، /user/2/a/b/c و غیره را تطبیق میدهد.
دوباره، اگر نمیخواهید آن رفتار را داشته باشید، یک {$} در انتها قرار دهید — مانند "/user/{id}/{$}".
Wildcardهای باقیمانده
Wildcardها در الگوهای مسیر به طور معمول فقط یک بخش غیر خالی از مسیر درخواست را تطبیق میدهند. اما یک مورد خاص وجود دارد.
اگر یک الگوی مسیر با یک wildcard به پایان میرسد، و این شناسه wildcard نهایی با ... به پایان میرسد، سپس wildcard هر و همه بخشهای باقیمانده یک مسیر درخواست را تطبیق میدهد.
برای مثال، اگر یک الگوی مسیر مانند "/post/{path...}" اعلام کنید، درخواستهایی مانند /post/a، /post/a/b، /post/a/b/c و غیره را تطبیق میدهد — بسیار شبیه به کاری که یک الگوی مسیر زیردرختی انجام میدهد. اما تفاوت این است که میتوانید به کل بخش wildcard از طریق متد r.PathValue() در handlerهای خود دسترسی داشته باشید. در این مثال، میتوانید مقدار wildcard برای {path...} را با فراخوانی r.PathValue("path") دریافت کنید.