مسیریابی مبتنی بر متد
بعد، بیایید از تمرین خوب HTTP پیروی کنیم و برنامه خود را محدود کنیم تا فقط به درخواستهایی با یک متد HTTP مناسب پاسخ دهد.
همانطور که در ساخت برنامه پیش میرویم، handlerهای home، snippetView و snippetCreate ما فقط اطلاعات را بازیابی میکنند و صفحات را به کاربر نمایش میدهند، پس منطقی است که این handlerها به عمل کردن روی درخواستهای GET محدود شوند.
برای محدود کردن یک مسیر به یک متد HTTP خاص، میتوانید الگوی مسیر را با متد HTTP لازم هنگام اعلام آن پیشوند کنید، مانند این:
package main ... func main() { mux := http.NewServeMux() // Prefix the route patterns with the required HTTP method (for now, we will // restrict all three routes to acting on GET requests). mux.HandleFunc("GET /{$}", home) mux.HandleFunc("GET /snippet/view/{id}", snippetView) mux.HandleFunc("GET /snippet/create", snippetCreate) log.Print("starting server on :4000") err := http.ListenAndServe(":4000", mux) log.Fatal(err) }
همچنین ارزش ذکر دارد که وقتی یک الگوی مسیر که از متد GET استفاده میکند را ثبت میکنید، هم درخواستهای GET و هم HEAD را تطبیق میدهد. همه متدهای دیگر (مانند POST، PUT و DELETE) نیاز به تطبیق دقیق دارند.
بیایید این تغییر را با استفاده از curl برای ارسال برخی درخواستها به برنامه خود تست کنیم. اگر همراه هستید، با ارسال یک درخواست GET معمولی به http://localhost:4000/ شروع کنید، مانند این:
$ curl -i localhost:4000/ HTTP/1.1 200 OK Date: Wed, 18 Mar 2024 11:29:23 GMT Content-Length: 21 Content-Type: text/plain; charset=utf-8 Hello from Snippetbox
پاسخ اینجا خوب به نظر میرسد. میبینیم که مسیر ما هنوز کار میکند، و یک وضعیت 200 OK و یک بدنه پاسخ Hello from Snippetbox دریافت میکنیم، دقیقاً مانند قبل.
همچنین میتوانید بروید و سعی کنید یک درخواست HEAD برای همان URL ارسال کنید. باید ببینید که این نیز به درستی کار میکند، فقط هدرهای پاسخ HTTP را برمیگرداند، و هیچ بدنه پاسخی ندارد.
$ curl --head localhost:4000/ HTTP/1.1 200 OK Date: Wed, 18 Mar 2024 11:29:23 GMT Content-Length: 21 Content-Type: text/plain; charset=utf-8
در مقابل، بیایید سعی کنیم یک درخواست POST به http://localhost:4000/ ارسال کنیم. متد POST برای این مسیر پشتیبانی نمیشود، پس باید یک پاسخ خطا شبیه به این دریافت کنید:
$ curl -i -d "" localhost:4000/ HTTP/1.1 405 Method Not Allowed Allow: GET, HEAD Content-Type: text/plain; charset=utf-8 X-Content-Type-Options: nosniff Date: Wed, 18 Mar 2024 11:29:23 GMT Content-Length: 19 Method Not Allowed*
پس این واقعاً خوب به نظر میرسد. میبینیم که servemux در Go به طور خودکار یک پاسخ 405 Method Not Allowed برای ما ارسال کرده است، شامل یک هدر Allow که متدهای HTTP که پشتیبانی میشوند برای URL درخواست را فهرست میکند.
افزودن یک مسیر و handler فقط POST
بیایید همچنین یک handler جدید snippetCreatePost به کدبیس خود اضافه کنیم، که بعداً از آن برای ایجاد و ذخیره یک تکه متن جدید در پایگاه داده استفاده خواهیم کرد. چون ایجاد و ذخیره یک تکه متن یک عمل غیر idempotent است که وضعیت سرور ما را تغییر میدهد، میخواهیم مطمئن شویم که این handler فقط روی درخواستهای POST عمل میکند.
در مجموع، میخواهیم handler و مسیر چهارم ما شبیه به این باشد:
| Route pattern | Handler | Action |
|---|---|---|
| GET /{$} | home | نمایش صفحه اصلی |
| GET /snippet/view/{id} | snippetView | نمایش یک تکه متن خاص |
| GET /snippet/create | snippetCreate | نمایش فرم برای ایجاد یک تکه متن جدید |
| POST /snippet/create | snippetCreatePost | ذخیره یک تکه متن جدید |
بیایید برویم و کد لازم را به فایل main.go خود اضافه کنیم، مانند این:
package main ... // Add a snippetCreatePost handler function. func snippetCreatePost(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Save a new snippet...")) } func main() { mux := http.NewServeMux() mux.HandleFunc("GET /{$}", home) mux.HandleFunc("GET /snippet/view/{id}", snippetView) mux.HandleFunc("GET /snippet/create", snippetCreate) // Create the new route, which is restricted to POST requests only. mux.HandleFunc("POST /snippet/create", snippetCreatePost) log.Print("starting server on :4000") err := http.ListenAndServe(":4000", mux) log.Fatalerr) }
توجه کنید که کاملاً خوب است که دو (یا بیشتر) مسیر جداگانه که متدهای HTTP متفاوت دارند اما در غیر این صورت الگوی یکسانی دارند را اعلام کنید، مانند کاری که اینجا با "GET /snippet/create" و "POST /snippet/create" انجام میدهیم.
اگر برنامه را دوباره راهاندازی کنید و سعی کنید برخی درخواستها با مسیر URL /snippet/create ارسال کنید، اکنون باید پاسخهای متفاوتی بسته به متد درخواستی که استفاده میکنید ببینید.
$ curl -i localhost:4000/snippet/create HTTP/1.1 200 OK Date: Wed, 18 Mar 2024 11:29:23 GMT Content-Length: 50 Content-Type: text/plain; charset=utf-8 Display a form for creating a new snippet... $ curl -i -d "" localhost:4000/snippet/create HTTP/1.1 200 OK Date: Wed, 18 Mar 2024 11:29:23 GMT Content-Length: 21 Content-Type: text/plain; charset=utf-8 Save a new snippet... $ curl -i -X DELETE localhost:4000/snippet/create HTTP/1.1 405 Method Not Allowed Allow: GET, HEAD, POST Content-Type: text/plain; charset=utf-8 X-Content-Type-Options: nosniff Date: Wed, 18 Mar 2024 11:29:23 GMT Content-Length: 19 Method Not Allowed
اطلاعات اضافی
اولویت متد
قانون الگوی خاصتر برنده میشود نیز اعمال میشود اگر الگوهای مسیری دارید که به دلیل یک متد HTTP همپوشانی دارند.
مهم است آگاه باشید که یک الگوی مسیر که شامل یک متد نمیشود — مانند "/article/{id}" — درخواستهای HTTP ورودی با هر متدی را تطبیق میدهد. در مقابل، یک مسیر مانند "POST /article/{id}" فقط درخواستهایی که متد POST دارند را تطبیق میدهد. پس اگر مسیرهای همپوشانی "/article/{id}" و "POST /article/{id}" را در برنامه خود اعلام کنید، سپس مسیر "POST /article/{id}" اولویت خواهد داشت.
نامگذاری handler
همچنین میخواهم تأکید کنم که هیچ راه درست یا غلطی برای نامگذاری handlerهای خود در Go وجود ندارد.
در این پروژه، از یک قرارداد استفاده میکنیم که نام هر handler که با درخواستهای POST سروکار دارد را با کلمه ‘Post’ پسوند میکنیم. مانند این:
| Route pattern | Handler | Action |
|---|---|---|
| GET /snippet/create | snippetCreate | نمایش فرم برای ایجاد یک تکه متن جدید |
| POST /snippet/create | snippetCreatePost | ایجاد یک تکه متن جدید |
اما در کار خودتان لازم نیست این الگو را دنبال کنید. برای مثال، میتوانید نام handlerها را با کلمات ‘get’ و ‘post’ پیشوند کنید، مانند این:
| Route pattern | Handler | Action |
|---|---|---|
| GET /snippet/create | getSnippetCreate | نمایش فرم برای ایجاد یک تکه متن جدید |
| POST /snippet/create | postSnippetCreate | ایجاد یک تکه متن جدید |
یا حتی نامهای کاملاً متفاوتی به handlerها بدهید. برای مثال:
| Route pattern | Handler | Action |
|---|---|---|
| GET /snippet/create | newSnippetForm | نمایش فرم برای ایجاد یک تکه متن جدید |
| POST /snippet/create | createSnippet | ایجاد یک تکه متن جدید |
اساساً، در Go آزادی دارید که یک قرارداد نامگذاری برای handlerهای خود انتخاب کنید که برای شما کار میکند و با ذهن شما سازگار است.
Routerهای شخص ثالث
قابلیت مسیریابی wildcard و مبتنی بر متد که در دو فصل گذشته استفاده کردهایم نسبتاً جدید در Go است — فقط در Go 1.22 بخشی از کتابخانه استاندارد شد. در حالی که این یک افزودنی بسیار خوشآمد به زبان و یک بهبود بزرگ است، ممکن است ببینید که هنوز زمانهایی وجود دارد که قابلیت مسیریابی کتابخانه استاندارد همه چیزهایی که نیاز دارید را فراهم نمیکند.
برای مثال، چیزهای زیر در حال حاضر پشتیبانی نمیشوند:
- ارسال پاسخهای سفارشی
404 Not Foundو405 Method Not Allowedبه کاربر (اگرچه یک پیشنهاد باز در مورد این وجود دارد). - استفاده از عبارات منظم (regular expressions) در الگوهای مسیر یا wildcardها.
- تطبیق چندین متد HTTP در یک اعلام مسیر واحد.
- پشتیبانی خودکار برای درخواستهای
OPTIONS. - مسیریابی درخواستها به handlerها بر اساس چیزهای غیرمعمول، مانند هدرهای درخواست HTTP.
اگر به این ویژگیها در برنامه خود نیاز دارید، باید از یک بسته router شخص ثالث استفاده کنید. مواردی که توصیه میکنم httprouter، chi، flow و gorilla/mux هستند، و میتوانید یک مقایسه از آنها و راهنمایی در مورد اینکه کدام یک را استفاده کنید در این پست وبلاگ پیدا کنید.