From b4a35fa887737e737145edd5ce42dd7fdfea2ee4 Mon Sep 17 00:00:00 2001 From: Eugene Burkov Date: Wed, 25 Nov 2020 15:50:59 +0300 Subject: [PATCH] Pull request: 2343 http server Merge in DNS/adguard-home from 2343-http-server to master Closes #2343. Squashed commit of the following: commit f4ebfc129484fc3489409069b3580eb70d71cc74 Merge: b13ec7002 36c7735b8 Author: Eugene Burkov Date: Wed Nov 25 15:37:27 2020 +0300 Merge branch 'master' into 2343-http-server commit b13ec70024f24f6b68b13a1ec6f27c89535feaf8 Author: Eugene Burkov Date: Wed Nov 25 15:31:36 2020 +0300 all: record changes commit ce44aac9d43e32db3f68746dec7a4f21b0a9dea4 Author: Eugene Burkov Date: Wed Nov 25 14:00:45 2020 +0300 home: set http servers timeouts commit 7f3e7385d1df39b39713b8ec443da5d9374d0bc8 Author: Eugene Burkov Date: Tue Nov 24 19:58:56 2020 +0300 home: replace default ServeMux with custom one. --- CHANGELOG.md | 2 ++ internal/home/auth.go | 2 +- internal/home/control.go | 11 ++++---- internal/home/control_install.go | 6 ++--- internal/home/filter_test.go | 5 ++-- internal/home/home.go | 9 +++++++ internal/home/middlewares.go | 17 ------------ internal/home/web.go | 44 ++++++++++++++++++++++++++++---- 8 files changed, 62 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 798bf950..99183e01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,12 +23,14 @@ and this project adheres to ### Changed +- Improved HTTP requests handling and timeouts. ([#2343]). - Our snap package now uses the `core20` image as its base [#2306]. - Various internal improvements ([#2271], [#2297]). [#2271]: https://github.com/AdguardTeam/AdGuardHome/issues/2271 [#2297]: https://github.com/AdguardTeam/AdGuardHome/issues/2297 [#2306]: https://github.com/AdguardTeam/AdGuardHome/issues/2306 +[#2343]: https://github.com/AdguardTeam/AdGuardHome/issues/2343 ### Fixed diff --git a/internal/home/auth.go b/internal/home/auth.go index de393f6f..00407fa0 100644 --- a/internal/home/auth.go +++ b/internal/home/auth.go @@ -360,7 +360,7 @@ func handleLogout(w http.ResponseWriter, r *http.Request) { // RegisterAuthHandlers - register handlers func RegisterAuthHandlers() { - http.Handle("/control/login", postInstallHandler(ensureHandler("POST", handleLogin))) + Context.mux.Handle("/control/login", postInstallHandler(ensureHandler("POST", handleLogin))) httpRegister("GET", "/control/logout", handleLogout) } diff --git a/internal/home/control.go b/internal/home/control.go index 00334b62..5c8bc1cb 100644 --- a/internal/home/control.go +++ b/internal/home/control.go @@ -107,24 +107,24 @@ func registerControlHandlers() { httpRegister(http.MethodGet, "/control/status", handleStatus) httpRegister(http.MethodPost, "/control/i18n/change_language", handleI18nChangeLanguage) httpRegister(http.MethodGet, "/control/i18n/current_language", handleI18nCurrentLanguage) - http.HandleFunc("/control/version.json", postInstall(optionalAuth(handleGetVersionJSON))) + Context.mux.HandleFunc("/control/version.json", postInstall(optionalAuth(handleGetVersionJSON))) httpRegister(http.MethodPost, "/control/update", handleUpdate) httpRegister(http.MethodGet, "/control/profile", handleGetProfile) // No auth is necessary for DOH/DOT configurations - http.HandleFunc("/apple/doh.mobileconfig", postInstall(handleMobileConfigDoh)) - http.HandleFunc("/apple/dot.mobileconfig", postInstall(handleMobileConfigDot)) + Context.mux.HandleFunc("/apple/doh.mobileconfig", postInstall(handleMobileConfigDoh)) + Context.mux.HandleFunc("/apple/dot.mobileconfig", postInstall(handleMobileConfigDot)) RegisterAuthHandlers() } func httpRegister(method string, url string, handler func(http.ResponseWriter, *http.Request)) { if len(method) == 0 { // "/dns-query" handler doesn't need auth, gzip and isn't restricted by 1 HTTP method - http.HandleFunc(url, postInstall(handler)) + Context.mux.HandleFunc(url, postInstall(handler)) return } - http.Handle(url, postInstallHandler(optionalAuthHandler(gziphandler.GzipHandler(ensureHandler(method, handler))))) + Context.mux.Handle(url, postInstallHandler(optionalAuthHandler(gziphandler.GzipHandler(ensureHandler(method, handler))))) } // ---------------------------------- @@ -201,7 +201,6 @@ func preInstallHandler(handler http.Handler) http.Handler { // it also enforces HTTPS if it is enabled and configured func postInstall(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { - if Context.firstRun && !strings.HasPrefix(r.URL.Path, "/install.") && !strings.HasPrefix(r.URL.Path, "/assets/") { diff --git a/internal/home/control_install.go b/internal/home/control_install.go index fcb8fcea..06f3bf43 100644 --- a/internal/home/control_install.go +++ b/internal/home/control_install.go @@ -372,7 +372,7 @@ func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) { } func (web *Web) registerInstallHandlers() { - http.HandleFunc("/control/install/get_addresses", preInstall(ensureGET(web.handleInstallGetAddresses))) - http.HandleFunc("/control/install/check_config", preInstall(ensurePOST(web.handleInstallCheckConfig))) - http.HandleFunc("/control/install/configure", preInstall(ensurePOST(web.handleInstallConfigure))) + Context.mux.HandleFunc("/control/install/get_addresses", preInstall(ensureGET(web.handleInstallGetAddresses))) + Context.mux.HandleFunc("/control/install/check_config", preInstall(ensurePOST(web.handleInstallCheckConfig))) + Context.mux.HandleFunc("/control/install/configure", preInstall(ensurePOST(web.handleInstallConfigure))) } diff --git a/internal/home/filter_test.go b/internal/home/filter_test.go index 317741d8..2bc23be1 100644 --- a/internal/home/filter_test.go +++ b/internal/home/filter_test.go @@ -12,7 +12,8 @@ import ( ) func testStartFilterListener() net.Listener { - http.HandleFunc("/filters/1.txt", func(w http.ResponseWriter, r *http.Request) { + mux := http.NewServeMux() + mux.HandleFunc("/filters/1.txt", func(w http.ResponseWriter, r *http.Request) { content := `||example.org^$third-party # Inline comment example ||example.com^$third-party @@ -26,7 +27,7 @@ func testStartFilterListener() net.Listener { panic(err) } - go func() { _ = http.Serve(listener, nil) }() + go func() { _ = http.Serve(listener, mux) }() return listener } diff --git a/internal/home/home.go b/internal/home/home.go index 493736f8..96bc4d68 100644 --- a/internal/home/home.go +++ b/internal/home/home.go @@ -67,6 +67,9 @@ type homeContext struct { ipDetector *ipDetector + // mux is our custom http.ServeMux. + mux *http.ServeMux + // Runtime properties // -- @@ -187,6 +190,8 @@ func setupContext(args options) { os.Exit(0) } } + + Context.mux = http.NewServeMux() } func setupConfig(args options) { @@ -306,6 +311,10 @@ func run(args options) { firstRun: Context.firstRun, BindHost: config.BindHost, BindPort: config.BindPort, + + ReadTimeout: ReadTimeout, + ReadHeaderTimeout: ReadHeaderTimeout, + WriteTimeout: WriteTimeout, } Context.web = CreateWeb(&webConf) if Context.web == nil { diff --git a/internal/home/middlewares.go b/internal/home/middlewares.go index 4a38160d..a5758985 100644 --- a/internal/home/middlewares.go +++ b/internal/home/middlewares.go @@ -2,7 +2,6 @@ package home import ( "net/http" - "strings" "github.com/AdguardTeam/AdGuardHome/internal/aghio" @@ -41,19 +40,3 @@ func limitRequestBody(h http.Handler) (limited http.Handler) { h.ServeHTTP(w, r) }) } - -// TODO(a.garipov): We currently have to use this, because everything registers -// its HTTP handlers in http.DefaultServeMux. In the future, refactor our HTTP -// API initialization process and stop using the gosh darn http.DefaultServeMux -// for anything at all. Gosh darn global variables. -func filterPProf(h http.Handler) (filtered http.Handler) { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if strings.HasPrefix(r.URL.Path, "/debug/pprof") { - http.NotFound(w, r) - - return - } - - h.ServeHTTP(w, r) - }) -} diff --git a/internal/home/web.go b/internal/home/web.go index 0d6a1628..974016da 100644 --- a/internal/home/web.go +++ b/internal/home/web.go @@ -8,6 +8,7 @@ import ( "net/http" "strconv" "sync" + "time" "github.com/AdguardTeam/AdGuardHome/internal/util" "github.com/AdguardTeam/golibs/log" @@ -15,11 +16,37 @@ import ( "github.com/gobuffalo/packr" ) +const ( + // ReadTimeout is the maximum duration for reading the entire request, + // including the body. + ReadTimeout = 10 * time.Second + + // ReadHeaderTimeout is the amount of time allowed to read request + // headers. + ReadHeaderTimeout = 10 * time.Second + + // WriteTimeout is the maximum duration before timing out writes of the + // response. + WriteTimeout = 10 * time.Second +) + type WebConfig struct { firstRun bool BindHost string BindPort int PortHTTPS int + + // ReadTimeout is an option to pass to http.Server for setting an + // appropriate field. + ReadTimeout time.Duration + + // ReadHeaderTimeout is an option to pass to http.Server for setting an + // appropriate field. + ReadHeaderTimeout time.Duration + + // WriteTimeout is an option to pass to http.Server for setting an + // appropriate field. + WriteTimeout time.Duration } // HTTPSServer - HTTPS Server @@ -66,12 +93,12 @@ func CreateWeb(conf *WebConfig) *Web { box := packr.NewBox("../../build/static") // if not configured, redirect / to /install.html, otherwise redirect /install.html to / - http.Handle("/", postInstallHandler(optionalAuthHandler(gziphandler.GzipHandler(http.FileServer(box))))) + Context.mux.Handle("/", postInstallHandler(optionalAuthHandler(gziphandler.GzipHandler(http.FileServer(box))))) // add handlers for /install paths, we only need them when we're not configured yet if conf.firstRun { log.Info("This is the first launch of AdGuard Home, redirecting everything to /install.html ") - http.Handle("/install.html", preInstallHandler(http.FileServer(box))) + Context.mux.Handle("/install.html", preInstallHandler(http.FileServer(box))) w.registerInstallHandlers() } else { registerControlHandlers() @@ -139,9 +166,12 @@ func (web *Web) Start() { // we need to have new instance, because after Shutdown() the Server is not usable address := net.JoinHostPort(web.conf.BindHost, strconv.Itoa(web.conf.BindPort)) web.httpServer = &http.Server{ - ErrorLog: web.errLogger, - Addr: address, - Handler: withMiddlewares(http.DefaultServeMux, filterPProf, limitRequestBody), + ErrorLog: web.errLogger, + Addr: address, + Handler: withMiddlewares(Context.mux, limitRequestBody), + ReadTimeout: web.conf.ReadTimeout, + ReadHeaderTimeout: web.conf.ReadHeaderTimeout, + WriteTimeout: web.conf.WriteTimeout, } err := web.httpServer.ListenAndServe() if err != http.ErrServerClosed { @@ -198,6 +228,10 @@ func (web *Web) tlsServerLoop() { RootCAs: Context.tlsRoots, CipherSuites: Context.tlsCiphers, }, + Handler: Context.mux, + ReadTimeout: web.conf.ReadTimeout, + ReadHeaderTimeout: web.conf.ReadHeaderTimeout, + WriteTimeout: web.conf.WriteTimeout, } printHTTPAddresses("https")