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

تایم‌اوت‌های اتصال

بیایید لحظه‌ای وقت بگذاریم تا مقاومت سرور خود را با اضافه کردن برخی تنظیمات تایم‌اوت بهبود دهیم، به این صورت:

File: cmd/web/main.go
package main

...

func main() {
    ...
    
    tlsConfig := &tls.Config{
        CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256},
    }

    srv := &http.Server{
        Addr:      *addr,
        Handler:   app.routes(),
        ErrorLog:  slog.NewLogLogger(logger.Handler(), slog.LevelError),
        TLSConfig: tlsConfig,
        // Add Idle, Read and Write timeouts to the server.
        IdleTimeout:  time.Minute,
        ReadTimeout:  5 * time.Second,
        WriteTimeout: 10 * time.Second,
    }

    logger.Info("starting server", "addr", srv.Addr)

    err = srv.ListenAndServeTLS("./tls/cert.pem", "./tls/key.pem")
    logger.Error(err.Error())
    os.Exit(1)
}

...

هر سه این تایم‌اوت‌ها — IdleTimeout، ReadTimeout و WriteTimeout — تنظیمات سراسری سرور هستند که روی اتصال زیرین عمل می‌کنند و برای همه درخواست‌ها بدون توجه به handler یا URL آن‌ها اعمال می‌شوند.

تنظیم IdleTimeout

به طور پیش‌فرض، Go keep-alive را روی همه اتصال‌های پذیرفته شده فعال می‌کند. این کمک می‌کند تا تأخیر کاهش یابد (به خصوص برای اتصال‌های HTTPS) زیرا یک کلاینت می‌تواند همان اتصال را برای چندین درخواست بدون نیاز به تکرار handshake TLS استفاده مجدد کند.

به طور پیش‌فرض، اتصال‌های keep-alive پس از چند دقیقه به طور خودکار بسته می‌شوند (زمان دقیق به سیستم عامل شما بستگی دارد). این کمک می‌کند تا اتصال‌هایی که کاربر به طور غیرمنتظره ناپدید شده است پاک شوند — به عنوان مثال، به دلیل قطع برق در سمت کلاینت.

هیچ راهی برای افزایش این پیش‌فرض وجود ندارد (مگر اینکه net.Listener خود را بسازید)، اما می‌توانید آن را از طریق تنظیم IdleTimeout کاهش دهید. در مورد ما، IdleTimeout را به 1 دقیقه تنظیم کرده‌ایم، که به این معنی است که همه اتصال‌های keep-alive پس از 1 دقیقه عدم فعالیت به طور خودکار بسته می‌شوند.

تنظیم ReadTimeout

در کد ما همچنین تنظیم ReadTimeout را به 5 ثانیه تنظیم کرده‌ایم. این به این معنی است که اگر هدرها یا بدنه درخواست هنوز 5 ثانیه پس از پذیرش اولیه درخواست در حال خواندن باشند، Go اتصال زیرین را می‌بندد. چون این یک بستن ‘سخت’ روی اتصال است، کاربر هیچ پاسخ HTTP(S) دریافت نخواهد کرد.

تنظیم یک دوره ReadTimeout کوتاه کمک می‌کند تا خطر حملات کلاینت کند — مانند Slowloris — کاهش یابد که در غیر این صورت می‌تواند با ارسال درخواست‌های HTTP(S) جزئی و ناقص یک اتصال را به طور نامحدود باز نگه دارد.

تنظیم WriteTimeout

تنظیم WriteTimeout اتصال زیرین را می‌بندد اگر سرور ما پس از یک دوره معین (در کد ما، 10 ثانیه) سعی کند به اتصال بنویسد. اما این بسته به پروتکل مورد استفاده کمی متفاوت رفتار می‌کند.

مهم است که در نظر داشته باشید که نوشته‌های انجام شده توسط یک handler بافر می‌شوند و به صورت یکجا وقتی handler برمی‌گردد به اتصال نوشته می‌شوند. بنابراین، ایده WriteTimeout به طور کلی جلوگیری از handlerهای طولانی‌مدت نیست، بلکه جلوگیری از این است که داده‌هایی که handler برمی‌گرداند بیش از حد طول بکشد تا نوشته شوند.


اطلاعات تکمیلی

تنظیم ReadHeaderTimeout

http.Server همچنین یک تنظیم ReadHeaderTimeout ارائه می‌دهد که ما در برنامه خود استفاده نکرده‌ایم. این به روشی مشابه ReadTimeout کار می‌کند، با این تفاوت که فقط به خواندن هدرهای HTTP(S) اعمال می‌شود. بنابراین، اگر ReadHeaderTimeout را به 3 ثانیه تنظیم کنید، یک اتصال بسته می‌شود اگر هدرهای درخواست هنوز 3 ثانیه پس از پذیرش درخواست در حال خواندن باشند. با این حال، خواندن بدنه درخواست هنوز می‌تواند پس از گذشت 3 ثانیه انجام شود، بدون اینکه اتصال بسته شود.

این می‌تواند مفید باشد اگر می‌خواهید یک محدودیت سراسری سرور برای خواندن هدرهای درخواست اعمال کنید، اما می‌خواهید تایم‌اوت‌های مختلفی در مسیرهای مختلف هنگام خواندن بدنه درخواست پیاده‌سازی کنید (احتمالاً با استفاده از middleware http.TimeoutHandler()).

برای برنامه وب Snippetbox ما هیچ عملی نداریم که تایم‌اوت‌های خواندن برای هر مسیر را توجیه کند — خواندن هدرها و بدنه‌های درخواست برای همه مسیرهای ما باید به راحتی در 5 ثانیه تکمیل شود، بنابراین به استفاده از ReadTimeout پایبند می‌مانیم.

تنظیم MaxHeaderBytes

http.Server شامل یک فیلد MaxHeaderBytes است که می‌توانید از آن برای کنترل حداکثر تعداد بایت‌هایی که سرور هنگام تجزیه هدرهای درخواست می‌خواند استفاده کنید. به طور پیش‌فرض، Go حداکثر طول هدر 1MB را مجاز می‌کند.

اگر می‌خواهید حداکثر طول هدر را به 0.5MB محدود کنید، به عنوان مثال، می‌نویسید:

srv := &http.Server{
    Addr:           *addr,
    MaxHeaderBytes: 524288,
    ...
}

اگر MaxHeaderBytes تجاوز شود، کاربر به طور خودکار یک پاسخ 431 Request Header Fields Too Large دریافت می‌کند.

یک نکته مهم در اینجا وجود دارد: Go همیشه یک 4096 بایت اضافی فضای اضافی به عددی که تنظیم می‌کنید اضافه می‌کند. اگر نیاز دارید MaxHeaderBytes یک عدد دقیق یا بسیار کم باشد، باید این را در نظر بگیرید.