رابط http.Handler
قبل از اینکه بیشتر پیش برویم، کمی تئوری وجود دارد که باید پوشش دهیم. کمی پیچیده است، پس اگر این فصل را سخت میبینید نگران نباشید. با ساخت برنامه ادامه دهید و بعداً وقتی با Go بیشتر آشنا شدید به آن برگردید.
در فصلهای قبلی اصطلاح handler را بدون توضیح اینکه واقعاً چه معنایی دارد استفاده کردهام. به طور دقیق، آنچه که با handler منظور داریم یک شیء است که رابط http.Handler را برآورده میکند:
type Handler interface { ServeHTTP(ResponseWriter, *Request) }
به زبان ساده، این اساساً به این معنی است که برای اینکه یک handler باشد، یک شیء باید یک متد ServeHTTP() با signature دقیق داشته باشد:
ServeHTTP(http.ResponseWriter, *http.Request)
پس در سادهترین شکل خود یک handler ممکن است شبیه به این باشد:
type home struct {} func (h *home) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Write([]byte("This is my home page")) }
اینجا یک شیء داریم (در این مورد یک struct خالی home است، اما میتواند به همان اندازه یک string یا function یا هر چیز دیگری باشد)، و یک متد با signature ServeHTTP(http.ResponseWriter, *http.Request) روی آن پیادهسازی کردهایم. این همه چیزی است که برای ساخت یک handler نیاز داریم.
سپس میتوانید این را با یک servemux با استفاده از متد Handle مانند این ثبت کنید:
mux := http.NewServeMux() mux.Handle("/", &home{})
وقتی این servemux یک درخواست HTTP برای "/" دریافت میکند، سپس متد ServeHTTP() struct home را فراخوانی میکند — که به نوبه خود پاسخ HTTP را مینویسد.
توابع handler
حالا، ایجاد یک شیء فقط برای اینکه بتوانیم یک متد ServeHTTP() روی آن پیادهسازی کنیم طولانی و کمی گیجکننده است. به همین دلیل در عمل بسیار رایجتر است که handlerهای خود را به عنوان یک تابع عادی بنویسید (مانند آنچه که تا الان در این کتاب داشتهایم). برای مثال:
func home(w http.ResponseWriter, r *http.Request) { w.Write([]byte("This is my home page")) }
اما این تابع home فقط یک تابع عادی است؛ یک متد ServeHTTP() ندارد. پس به خودی خود یک handler نیست.
در عوض میتوانیم آن را با استفاده از adapter http.HandlerFunc() به یک handler تبدیل کنیم، مانند این:
mux := http.NewServeMux() mux.Handle("/", http.HandlerFunc(home))
Adapter http.HandlerFunc() با اضافه کردن خودکار یک متد ServeHTTP() به تابع home کار میکند. وقتی اجرا میشود، این متد ServeHTTP() سپس به سادگی کد داخل تابع اصلی home را فراخوانی میکند. این یک راه غیرمستقیم اما راحت برای وادار کردن یک تابع عادی به برآورده کردن رابط http.Handler است.
در طول این پروژه تا الان از متد HandleFunc() برای ثبت توابع handler خود با servemux استفاده کردهایم. این فقط یک syntactic sugar است که یک تابع را به یک handler تبدیل میکند و آن را در یک مرحله ثبت میکند، به جای اینکه باید به صورت دستی انجام دهیم. مثال بالا از نظر عملکردی معادل این است:
mux := http.NewServeMux() mux.HandleFunc("/", home)
زنجیره کردن handlerها
آنهایی که چشم تیزبین دارند ممکن است چیزی جالب درست در ابتدای این پروژه متوجه شده باشند. تابع http.ListenAndServe() یک شیء http.Handler را به عنوان پارامتر دوم میگیرد:
func ListenAndServe(addr string, handler Handler) error
… اما ما یک servemux را ارسال کردهایم.
قادر بودیم این کار را انجام دهیم چون servemux همچنین یک متد ServeHTTP() دارد، به این معنی که آن نیز رابط http.Handler را برآورده میکند.
برای من سادهتر میکند که servemux را فقط به عنوان یک نوع خاص handler در نظر بگیرم، که به جای ارائه یک پاسخ خودش، درخواست را به یک handler دوم منتقل میکند. این به اندازهای که ممکن است در ابتدا به نظر برسد جهش نیست. زنجیره کردن handlerها با هم یک idiom بسیار رایج در Go است، و چیزی است که بعداً در این پروژه زیاد انجام خواهیم داد.
در واقع، آنچه که دقیقاً اتفاق میافتد این است: وقتی سرور ما یک درخواست HTTP جدید دریافت میکند، متد ServeHTTP() servemux را فراخوانی میکند. این handler مربوطه را بر اساس متد درخواست و مسیر URL جستجو میکند، و به نوبه خود متد ServeHTTP() آن handler را فراخوانی میکند. میتوانید یک برنامه وب Go را به عنوان یک زنجیره از متدهای ServeHTTP() که یکی پس از دیگری فراخوانی میشوند در نظر بگیرید.
درخواستها به صورت همزمان handle میشوند
یک چیز دیگر وجود دارد که واقعاً مهم است اشاره کنم: همه درخواستهای HTTP ورودی در goroutine خودشان سرو میشوند. برای سرورهای شلوغ، این به این معنی است که بسیار محتمل است که کد در یا فراخوانی شده توسط handlerهای شما به صورت همزمان (concurrently) اجرا شود. در حالی که این به سریع شدن Go کمک میکند، نکته منفی این است که باید از (و در برابر) race conditions هنگام دسترسی به منابع مشترک از handlerهای خود آگاه باشید.