رابط http.Handler (HTTP Handler Interface)
قبل از اینکه بیشتر پیش برویم، باید کمی نظریه را پوشش دهیم. این کمی پیچیده است، بنابراین اگر این فصل برای شما سخت است نگران نباشید. به ساخت برنامه ادامه دهید و بعداً که با Go آشناتر شدید به آن برگردید.
در فصلهای قبلی من اصطلاح هندلر (Handler) را بدون توضیح دقیق آن به کار بردهام. به طور دقیق، منظور ما از هندلر یک شیء است که رابط http.Handler را برآورده میکند:
type Handler interface { ServeHTTP(ResponseWriter, *Request) }
به زبان ساده، این به این معنی است که برای اینکه یک شیء هندلر باشد باید یک متد ServeHTTP() با امضای دقیق زیر داشته باشد:
ServeHTTP(http.ResponseWriter, *http.Request)
بنابراین در سادهترین شکل، یک هندلر ممکن است به این صورت باشد:
type home struct {} func (h *home) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Write([]byte("This is my home page")) }
در اینجا ما یک شیء داریم (در این مورد یک ساختار خالی home است، اما میتواند به همان اندازه یک رشته یا تابع یا هر چیز دیگری باشد)، و ما یک متد با امضای ServeHTTP(http.ResponseWriter, *http.Request) بر روی آن پیادهسازی کردهایم. این تمام چیزی است که برای ساخت یک هندلر نیاز داریم.
سپس میتوانید این را با استفاده از متد Handle به یک servemux ثبت کنید:
mux := http.NewServeMux() mux.Handle("/", &home{})
وقتی این servemux یک درخواست HTTP برای "/" دریافت میکند، متد ServeHTTP() ساختار home را فراخوانی میکند — که به نوبه خود پاسخ HTTP را مینویسد.
توابع هندلر (Handler Functions)
اکنون، ایجاد یک شیء فقط برای اینکه بتوانیم یک متد ServeHTTP() بر روی آن پیادهسازی کنیم، طولانی و کمی گیجکننده است. به همین دلیل در عمل بسیار رایجتر است که هندلرهای خود را به عنوان یک تابع عادی بنویسید (مانند آنچه تاکنون در این کتاب انجام دادهایم). برای مثال:
func home(w http.ResponseWriter, r *http.Request) { w.Write([]byte("This is my home page")) }
اما این تابع home فقط یک تابع عادی است؛ متد ServeHTTP() ندارد. بنابراین به خودی خود هندلر نیست.
در عوض میتوانیم آن را با استفاده از آداپتور http.HandlerFunc() به یک هندلر تبدیل کنیم، به این صورت:
mux := http.NewServeMux() mux.Handle("/", http.HandlerFunc(home))
آداپتور http.HandlerFunc() با افزودن خودکار یک متد ServeHTTP() به تابع home کار میکند. وقتی اجرا میشود، این متد ServeHTTP() به سادگی کد داخل تابع اصلی home را فراخوانی میکند. این یک روش دوربرگردان اما راحت برای تبدیل یک تابع عادی به یک هندلر است که رابط http.Handler را برآورده میکند.
در طول این پروژه تاکنون از متد HandleFunc() برای ثبت توابع هندلر خود با servemux استفاده کردهایم. این فقط یک شکر نحوی است که یک تابع را به یک هندلر تبدیل میکند و آن را در یک مرحله ثبت میکند، به جای اینکه به صورت دستی این کار را انجام دهیم. مثال بلافاصله بالا به صورت عملکردی معادل این است:
mux := http.NewServeMux() mux.HandleFunc("/", home)
زنجیرهسازی هندلرها (Chaining Handlers)
شاید برخی از شما در ابتدای این پروژه متوجه چیزی جالب شده باشید. تابع http.ListenAndServe() یک شیء http.Handler را به عنوان پارامتر دوم میگیرد:
func ListenAndServe(addr string, handler Handler) error
… اما ما یک servemux را پاس دادهایم.
ما توانستیم این کار را انجام دهیم زیرا servemux نیز یک متد ServeHTTP() دارد، به این معنی که آن نیز رابط http.Handler را برآورده میکند.
برای من سادهتر است که به servemux به عنوان یک نوع خاصی از هندلر فکر کنم، که به جای ارائه پاسخ خود، درخواست را به یک هندلر دوم منتقل میکند. این به اندازهای که ممکن است در ابتدا به نظر برسد، جهش بزرگی نیست. زنجیرهسازی هندلرها با هم یک اصطلاح بسیار رایج در Go است و چیزی است که ما در این پروژه زیاد انجام خواهیم داد.
در واقع، آنچه دقیقاً اتفاق میافتد این است: وقتی سرور ما یک درخواست HTTP جدید دریافت میکند، متد ServeHTTP() servemux را فراخوانی میکند. این هندلر مربوطه را بر اساس متد درخواست و مسیر URL جستجو میکند و به نوبه خود متد ServeHTTP() آن هندلر را فراخوانی میکند. میتوانید به یک برنامه وب Go به عنوان یک زنجیرهای از متدهای ServeHTTP() که یکی پس از دیگری فراخوانی میشوند فکر کنید.
درخواستها به صورت همزمان پردازش میشوند (Requests are Handled Concurrently)
یک نکته دیگر که واقعاً مهم است باید اشاره کرد: همه درخواستهای HTTP ورودی در goroutine خودشان سرو میشوند. برای سرورهای شلوغ، این به این معنی است که احتمالاً کد درون یا فراخوانی شده توسط هندلرهای شما به صورت همزمان اجرا میشود. در حالی که این به Go کمک میکند تا بسیار سریع باشد، نقطه ضعف این است که باید از شرایط رقابتی هنگام دسترسی به منابع مشترک از هندلرهای خود آگاه باشید و از آنها محافظت کنید.
واژهنامه اصطلاحات فنی
| اصطلاح فارسی | معادل انگلیسی | توضیح |
|---|---|---|
| رابط http.Handler | HTTP Handler Interface | رابطی در Go که تعریف میکند یک شیء چگونه باید به درخواستهای HTTP پاسخ دهد |
| هندلر | Handler | شیئی که رابط http.Handler را پیادهسازی میکند و میتواند به درخواستهای HTTP پاسخ دهد |
| تابع هندلر | Handler Function | تابعی که میتواند به عنوان یک هندلر HTTP عمل کند |
| زنجیرهسازی هندلرها | Handler Chaining | الگوی طراحی که در آن چندین هندلر به صورت متوالی درخواست را پردازش میکنند |
| پردازش همزمان | Concurrent Processing | اجرای همزمان چندین درخواست HTTP در goroutineهای جداگانه |
| شرایط رقابتی | Race Conditions | مشکلی که در برنامهنویسی همزمان رخ میدهد، وقتی چند عملیات همزمان به منابع مشترک دسترسی دارند |
| آداپتور | Adapter | الگوی طراحی که یک رابط را به رابط دیگری تبدیل میکند |
| متد ServeHTTP | ServeHTTP Method | متدی که باید توسط هر هندلر HTTP پیادهسازی شود تا بتواند به درخواستها پاسخ دهد |
| servemux | ServeMux | مسیریاب HTTP در Go که درخواستها را به هندلرهای مناسب هدایت میکند |
| goroutine | Goroutine | واحد اجرای سبکوزن در Go که امکان اجرای همزمان کد را فراهم میکند |