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

مسیریابی درخواست‌ها

داشتن یک برنامه وب با فقط یک مسیر خیلی هیجان‌انگیز نیست… یا مفید! بیایید چند مسیر دیگر اضافه کنیم تا برنامه شروع به شکل گرفتن کند مانند این:

Route pattern Handler Action
/ home نمایش صفحه اصلی
/snippet/view snippetView نمایش یک تکه متن خاص
/snippet/create snippetCreate نمایش فرم برای ایجاد یک تکه متن جدید

فایل main.go را دوباره باز کنید و آن را به صورت زیر به‌روزرسانی کنید:

File: 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

اگر لینک‌های زیر را در مرورگر وب خود بازدید کنید، اکنون باید پاسخ مناسب برای هر مسیر را دریافت کنید:

02.03-01.png
02.03-02.png

اسلش‌های انتهایی در الگوهای مسیر

حالا که دو مسیر جدید راه‌اندازی و اجرا شده‌اند، بیایید کمی تئوری صحبت کنیم.

مهم است بدانید که 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 متوقف شود، مانند این:

File: main.go
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 دریافت کنید که کمی شبیه به این است:

02.03-03.png

اطلاعات اضافی

ویژگی‌های اضافی servemux

چند ویژگی دیگر servemux وجود دارد که ارزش اشاره دارد:

تطبیق نام 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 برای مسیریابی استفاده می‌کند.

اگرچه این رویکرد می‌تواند کد شما را کمی کوتاه‌تر کند، اما به دو دلیل آن را توصیه نمی‌کنم:

پس، به خاطر وضوح، قابلیت نگهداری و امنیت، به طور کلی ایده خوبی است که از http.DefaultServeMux و توابع کمکی مربوطه اجتناب کنید. در عوض از servemux با محدوده محلی خود استفاده کنید، مانند کاری که تا کنون در این پروژه انجام داده‌ایم.