مسیریابی مبتنی بر روش (Method-based Routing)
در ادامه، بیایید با رعایت اصول خوب HTTP، برنامه خود را محدود کنیم تا فقط به درخواستهایی با روش HTTP (HTTP Method) مناسب پاسخ دهد.
همانطور که در ساخت برنامه خود پیش میرویم، هندلرهای home، snippetView و snippetCreate فقط اطلاعات را بازیابی کرده و صفحات را به کاربر نمایش میدهند، بنابراین منطقی است که این هندلرها را به درخواستهای 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 گو به طور خودکار یک پاسخ 405 Method Not Allowed برای ما ارسال کرده است، شامل یک هدر Allow که روشهای HTTP را که برای URL درخواست پشتیبانی میشوند، فهرست میکند.
افزودن یک مسیر و هندلر فقط برای POST (Adding a POST-only Route and Handler)
بیایید همچنین یک هندلر جدید snippetCreatePost به کد خود اضافه کنیم، که بعداً برای ایجاد و ذخیره یک قطعه جدید در یک پایگاه داده استفاده خواهیم کرد. از آنجا که ایجاد و ذخیره یک قطعه یک عمل غیرقابل تکرار است که وضعیت سرور ما را تغییر میدهد، میخواهیم مطمئن شویم که این هندلر فقط به درخواستهای POST عمل میکند.
در مجموع، میخواهیم هندلر و مسیر چهارم ما به این صورت باشد:
| الگوی مسیر | هندلر | عمل |
|---|---|---|
| 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.Fatal(err) }
توجه داشته باشید که اعلام دو (یا بیشتر) مسیر جداگانه که روشهای 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
اطلاعات اضافی
اولویت روش (Method Precedence)
قانون الگوی خاصتر برنده میشود (More Specific Pattern Wins) نیز در صورتی که الگوهای مسیر به دلیل یک روش HTTP همپوشانی داشته باشند، اعمال میشود.
مهم است که بدانید یک الگوی مسیری که شامل یک روش نیست — مانند "/article/{id}" — با درخواستهای HTTP با هر روشی مطابقت خواهد داشت. در مقابل، مسیری مانند "POST /article/{id}" فقط با درخواستهایی که روش POST دارند مطابقت خواهد داشت. بنابراین اگر مسیرهای همپوشانی "/article/{id}" و "POST /article/{id}" را در برنامه خود اعلام کنید، مسیر "POST /article/{id}" اولویت خواهد داشت.
نامگذاری هندلر (Handler Naming)
همچنین میخواهم تأکید کنم که هیچ راه درست یا غلطی برای نامگذاری هندلرهای خود در Go وجود ندارد.
در این پروژه، ما از یک قرارداد پیروی میکنیم که نام هندلرهایی که با درخواستهای POST سروکار دارند را با کلمه ‘Post’ پسوند میدهیم. به این صورت:
| الگوی مسیر | هندلر | عمل |
|---|---|---|
| GET /snippet/create | snippetCreate | نمایش فرم برای ایجاد یک قطعه جدید |
| POST /snippet/create | snippetCreatePost | ایجاد یک قطعه جدید |
اما در کار خودتان لازم نیست که از این الگو پیروی کنید. به عنوان مثال، میتوانید نام هندلرها را با کلمات ‘get’ و ‘post’ پیشوند کنید، به این صورت:
| الگوی مسیر | هندلر | عمل |
|---|---|---|
| GET /snippet/create | getSnippetCreate | نمایش فرم برای ایجاد یک قطعه جدید |
| POST /snippet/create | postSnippetCreate | ایجاد یک قطعه جدید |
یا حتی به هندلرها نامهای کاملاً متفاوتی بدهید. به عنوان مثال:
| الگوی مسیر | هندلر | عمل |
|---|---|---|
| GET /snippet/create | newSnippetForm | نمایش فرم برای ایجاد یک قطعه جدید |
| POST /snippet/create | createSnippet | ایجاد یک قطعه جدید |
به طور کلی، شما در Go آزادی دارید که یک الگوی نامگذاری برای هندلرهای خود انتخاب کنید که برای شما کار کند و با ذهن شما سازگار باشد.
روترهای شخص ثالث
عملکرد مسیریابی مبتنی بر کاراکترهای جایگزین و روش که در دو فصل گذشته استفاده کردهایم نسبتاً جدید در Go است — این ویژگی تنها در نسخه 1.22 به کتابخانه استاندارد اضافه شد. در حالی که این یک افزودنی بسیار خوشایند به زبان و یک بهبود بزرگ است، ممکن است متوجه شوید که هنوز هم مواقعی وجود دارد که عملکرد مسیریابی کتابخانه استاندارد همه چیزهایی که نیاز دارید را فراهم نمیکند.
به عنوان مثال، موارد زیر در حال حاضر پشتیبانی نمیشوند:
- ارسال پاسخهای سفارشی
404 Not Foundو405 Method Not Allowedبه کاربر (اگرچه یک پیشنهاد باز در این باره وجود دارد). - استفاده از عبارات منظم در الگوهای مسیر یا کاراکترهای جایگزین.
- مطابقت با چندین روش HTTP در یک اعلامیه مسیر.
- پشتیبانی خودکار از درخواستهای
OPTIONS. - مسیریابی درخواستها به هندلرها بر اساس چیزهای غیرمعمول، مانند هدرهای درخواست HTTP.
اگر به این ویژگیها در برنامه خود نیاز دارید، باید از یک بسته روتر شخص ثالث استفاده کنید. آنهایی که من توصیه میکنم httprouter، chi، flow و gorilla/mux هستند، و میتوانید مقایسهای از آنها و راهنمایی درباره اینکه کدام یک را استفاده کنید در این پست وبلاگ پیدا کنید.
واژهنامه اصطلاحات فنی
| اصطلاح فارسی | معادل انگلیسی | توضیح |
|---|---|---|
| مسیریابی مبتنی بر روش | Method-based Routing | تکنیکی برای محدود کردن مسیرها به روشهای HTTP خاص |
| روش HTTP | HTTP Method | نوع عملیاتی که یک درخواست HTTP میخواهد انجام دهد (مانند GET، POST، PUT، DELETE) |
| هندلر | Handler | تابعی که درخواستهای HTTP را پردازش میکند |
| الگوی مسیر | Route Pattern | الگویی که برای تطبیق با URLهای درخواست استفاده میشود |
| وضعیت پاسخ | Response Status | کد عددی که نتیجه پردازش درخواست HTTP را نشان میدهد (مانند 200 OK، 405 Method Not Allowed) |
| هدر پاسخ | Response Header | متادیتای اضافی که همراه با پاسخ HTTP ارسال میشود |
| بدنه پاسخ | Response Body | محتوای اصلی که در پاسخ HTTP برگردانده میشود |
| درخواست غیرقابل تکرار | Non-idempotent Request | درخواستی که هر بار اجرا میتواند نتایج متفاوتی داشته باشد و وضعیت سرور را تغییر میدهد |
| همپوشانی مسیرها | Route Overlap | وضعیتی که در آن چندین الگوی مسیر میتوانند با یک URL یکسان مطابقت داشته باشند |
| اولویت روش | Method Precedence | قوانینی که تعیین میکنند کدام مسیر در صورت همپوشانی باید استفاده شود |