Let's Go مبانی › مسیریابی مبتنی بر متد
قبلی · فهرست · بعدی
فصل ۲.۵.

مسیریابی مبتنی بر متد

بعد، بیایید از تمرین خوب HTTP پیروی کنیم و برنامه خود را محدود کنیم تا فقط به درخواست‌هایی با یک متد HTTP مناسب پاسخ دهد.

همانطور که در ساخت برنامه پیش می‌رویم، handlerهای home، snippetView و snippetCreate ما فقط اطلاعات را بازیابی می‌کنند و صفحات را به کاربر نمایش می‌دهند، پس منطقی است که این handlerها به عمل کردن روی درخواست‌های GET محدود شوند.

برای محدود کردن یک مسیر به یک متد HTTP خاص، می‌توانید الگوی مسیر را با متد HTTP لازم هنگام اعلام آن پیشوند کنید، مانند این:

File: main.go
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 خود اضافه کنیم، مانند این:

File: 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 بخشی از کتابخانه استاندارد شد. در حالی که این یک افزودنی بسیار خوش‌آمد به زبان و یک بهبود بزرگ است، ممکن است ببینید که هنوز زمان‌هایی وجود دارد که قابلیت مسیریابی کتابخانه استاندارد همه چیزهایی که نیاز دارید را فراهم نمی‌کند.

برای مثال، چیزهای زیر در حال حاضر پشتیبانی نمی‌شوند:

اگر به این ویژگی‌ها در برنامه خود نیاز دارید، باید از یک بسته router شخص ثالث استفاده کنید. مواردی که توصیه می‌کنم httprouter، chi، flow و gorilla/mux هستند، و می‌توانید یک مقایسه از آن‌ها و راهنمایی در مورد اینکه کدام یک را استفاده کنید در این پست وبلاگ پیدا کنید.