نحوه کار میدلور
قبلاً در کتاب چیزی گفتم که میخواهم در این فصل گسترش دهم:
“میتوانید یک برنامه وب Go را به عنوان زنجیرهای از متدهای
ServeHTTP()که یکی پس از دیگری فراخوانی میشوند، تصور کنید.”
در حال حاضر، در برنامه ما، هنگامی که سرور ما یک درخواست HTTP جدید دریافت میکند، متد ServeHTTP() servemux را فراخوانی میکند. این متد handler مربوطه را بر اساس متد درخواست و مسیر URL جستجو میکند و به نوبه خود متد ServeHTTP() آن handler را فراخوانی میکند.
ایده اصلی میدلور، درج یک handler دیگر در این زنجیره است. handler میدلور برخی منطق را اجرا میکند، مانند ثبت یک درخواست، و سپس متد ServeHTTP() handler بعدی در زنجیره را فراخوانی میکند.
در واقع، ما در حال حاضر در برنامه خود از برخی میدلور استفاده میکنیم — تابع http.StripPrefix() از سرو کردن فایلهای استاتیک، که یک پیشوند خاص را از مسیر URL درخواست حذف میکند قبل از ارسال درخواست به سرور فایل.
الگو
الگوی استاندارد برای ایجاد میدلور خود به این صورت است:
func myMiddleware(next http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { // TODO: Execute our middleware logic here... next.ServeHTTP(w, r) } return http.HandlerFunc(fn) }
خود کد بسیار مختصر است، اما چیزهای زیادی در آن وجود دارد که باید درک کنید.
تابع
myMiddleware()اساساً یک پوشش در اطراف handlernextاست که آن را به عنوان یک پارامتر به آن میدهیم.یک تابع
fnایجاد میکند که روی handlernextبسته میشود تا یک closure تشکیل دهد. هنگامی کهfnاجرا میشود، منطق میدلور ما را اجرا میکند و سپس کنترل را با فراخوانی متدServeHTTP()آن به handlernextمنتقل میکند.صرف نظر از اینکه با یک closure چه میکنید، همیشه میتواند به متغیرهایی که در محدوده ایجاد شدهاند دسترسی داشته باشد — که در این مورد به این معنی است که
fnهمیشه به متغیرnextدسترسی خواهد داشت.در خط آخر کد، این closure را به یک
http.Handlerتبدیل میکنیم و با استفاده از adapterhttp.HandlerFunc()آن را برمیگردانیم.
اگر این گیجکننده به نظر میرسد، میتوانید آن را سادهتر تصور کنید: myMiddleware() تابعی است که handler بعدی (next handler) در یک زنجیره را به عنوان پارامتر میپذیرد. این تابع یک handler برمیگرداند که برخی منطق را اجرا میکند و سپس handler بعدی را فراخوانی میکند.
سادهسازی میدلور
یک تغییر در این الگو، استفاده از یک تابع ناشناس در داخل میدلور myMiddleware() است، به این صورت:
func myMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // TODO: Execute our middleware logic here... next.ServeHTTP(w, r) }) }
این الگو در دنیای واقعی بسیار رایج است و احتمالاً بیشترین چیزی است که اگر کد منبع برنامههای دیگر یا بستههای شخص ثالث را میخوانید، خواهید دید.
موقعیتیابی میدلور
مهم است توضیح دهیم که موقعیت میدلور در زنجیره handlerها بر رفتار برنامه شما تأثیر میگذارد.
اگر میدلور خود را قبل از servemux در زنجیره قرار دهید، روی هر درخواستی که برنامه شما دریافت میکند عمل خواهد کرد.
myMiddleware → servemux → application handler
یک مثال خوب از جایی که این مفید خواهد بود، میدلوری برای ثبت درخواستها است — زیرا این معمولاً چیزی است که میخواهید برای همه درخواستها انجام دهید.
به طور جایگزین، میتوانید میدلور را بعد از servemux در زنجیره قرار دهید — با پوشاندن یک handler برنامه خاص. این باعث میشود میدلور شما فقط برای یک مسیر خاص اجرا شود.
servemux → myMiddleware → application handler
یک مثال از این مورد، چیزی مانند میدلور احراز هویت است که ممکن است فقط بخواهید روی مسیرهای خاص اجرا شود.
همانطور که در کتاب پیش میرویم، نحوه انجام هر دو این کارها را در عمل نشان خواهیم داد.