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 <e.burkov@adguard.com>
Date:   Wed Nov 25 15:37:27 2020 +0300

    Merge branch 'master' into 2343-http-server

commit b13ec70024f24f6b68b13a1ec6f27c89535feaf8
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Wed Nov 25 15:31:36 2020 +0300

    all: record changes

commit ce44aac9d43e32db3f68746dec7a4f21b0a9dea4
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Wed Nov 25 14:00:45 2020 +0300

    home: set http servers timeouts

commit 7f3e7385d1df39b39713b8ec443da5d9374d0bc8
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Tue Nov 24 19:58:56 2020 +0300

    home: replace default ServeMux with custom one.
This commit is contained in:
Eugene Burkov 2020-11-25 15:50:59 +03:00
parent 36c7735b85
commit b4a35fa887
8 changed files with 62 additions and 34 deletions

View File

@ -23,12 +23,14 @@ and this project adheres to
### Changed ### Changed
- Improved HTTP requests handling and timeouts. ([#2343]).
- Our snap package now uses the `core20` image as its base [#2306]. - Our snap package now uses the `core20` image as its base [#2306].
- Various internal improvements ([#2271], [#2297]). - Various internal improvements ([#2271], [#2297]).
[#2271]: https://github.com/AdguardTeam/AdGuardHome/issues/2271 [#2271]: https://github.com/AdguardTeam/AdGuardHome/issues/2271
[#2297]: https://github.com/AdguardTeam/AdGuardHome/issues/2297 [#2297]: https://github.com/AdguardTeam/AdGuardHome/issues/2297
[#2306]: https://github.com/AdguardTeam/AdGuardHome/issues/2306 [#2306]: https://github.com/AdguardTeam/AdGuardHome/issues/2306
[#2343]: https://github.com/AdguardTeam/AdGuardHome/issues/2343
### Fixed ### Fixed

View File

@ -360,7 +360,7 @@ func handleLogout(w http.ResponseWriter, r *http.Request) {
// RegisterAuthHandlers - register handlers // RegisterAuthHandlers - register handlers
func RegisterAuthHandlers() { func RegisterAuthHandlers() {
http.Handle("/control/login", postInstallHandler(ensureHandler("POST", handleLogin))) Context.mux.Handle("/control/login", postInstallHandler(ensureHandler("POST", handleLogin)))
httpRegister("GET", "/control/logout", handleLogout) httpRegister("GET", "/control/logout", handleLogout)
} }

View File

@ -107,24 +107,24 @@ func registerControlHandlers() {
httpRegister(http.MethodGet, "/control/status", handleStatus) httpRegister(http.MethodGet, "/control/status", handleStatus)
httpRegister(http.MethodPost, "/control/i18n/change_language", handleI18nChangeLanguage) httpRegister(http.MethodPost, "/control/i18n/change_language", handleI18nChangeLanguage)
httpRegister(http.MethodGet, "/control/i18n/current_language", handleI18nCurrentLanguage) 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.MethodPost, "/control/update", handleUpdate)
httpRegister(http.MethodGet, "/control/profile", handleGetProfile) httpRegister(http.MethodGet, "/control/profile", handleGetProfile)
// No auth is necessary for DOH/DOT configurations // No auth is necessary for DOH/DOT configurations
http.HandleFunc("/apple/doh.mobileconfig", postInstall(handleMobileConfigDoh)) Context.mux.HandleFunc("/apple/doh.mobileconfig", postInstall(handleMobileConfigDoh))
http.HandleFunc("/apple/dot.mobileconfig", postInstall(handleMobileConfigDot)) Context.mux.HandleFunc("/apple/dot.mobileconfig", postInstall(handleMobileConfigDot))
RegisterAuthHandlers() RegisterAuthHandlers()
} }
func httpRegister(method string, url string, handler func(http.ResponseWriter, *http.Request)) { func httpRegister(method string, url string, handler func(http.ResponseWriter, *http.Request)) {
if len(method) == 0 { if len(method) == 0 {
// "/dns-query" handler doesn't need auth, gzip and isn't restricted by 1 HTTP method // "/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 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 // it also enforces HTTPS if it is enabled and configured
func postInstall(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { func postInstall(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
if Context.firstRun && if Context.firstRun &&
!strings.HasPrefix(r.URL.Path, "/install.") && !strings.HasPrefix(r.URL.Path, "/install.") &&
!strings.HasPrefix(r.URL.Path, "/assets/") { !strings.HasPrefix(r.URL.Path, "/assets/") {

View File

@ -372,7 +372,7 @@ func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
} }
func (web *Web) registerInstallHandlers() { func (web *Web) registerInstallHandlers() {
http.HandleFunc("/control/install/get_addresses", preInstall(ensureGET(web.handleInstallGetAddresses))) Context.mux.HandleFunc("/control/install/get_addresses", preInstall(ensureGET(web.handleInstallGetAddresses)))
http.HandleFunc("/control/install/check_config", preInstall(ensurePOST(web.handleInstallCheckConfig))) Context.mux.HandleFunc("/control/install/check_config", preInstall(ensurePOST(web.handleInstallCheckConfig)))
http.HandleFunc("/control/install/configure", preInstall(ensurePOST(web.handleInstallConfigure))) Context.mux.HandleFunc("/control/install/configure", preInstall(ensurePOST(web.handleInstallConfigure)))
} }

View File

@ -12,7 +12,8 @@ import (
) )
func testStartFilterListener() net.Listener { 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 content := `||example.org^$third-party
# Inline comment example # Inline comment example
||example.com^$third-party ||example.com^$third-party
@ -26,7 +27,7 @@ func testStartFilterListener() net.Listener {
panic(err) panic(err)
} }
go func() { _ = http.Serve(listener, nil) }() go func() { _ = http.Serve(listener, mux) }()
return listener return listener
} }

View File

@ -67,6 +67,9 @@ type homeContext struct {
ipDetector *ipDetector ipDetector *ipDetector
// mux is our custom http.ServeMux.
mux *http.ServeMux
// Runtime properties // Runtime properties
// -- // --
@ -187,6 +190,8 @@ func setupContext(args options) {
os.Exit(0) os.Exit(0)
} }
} }
Context.mux = http.NewServeMux()
} }
func setupConfig(args options) { func setupConfig(args options) {
@ -306,6 +311,10 @@ func run(args options) {
firstRun: Context.firstRun, firstRun: Context.firstRun,
BindHost: config.BindHost, BindHost: config.BindHost,
BindPort: config.BindPort, BindPort: config.BindPort,
ReadTimeout: ReadTimeout,
ReadHeaderTimeout: ReadHeaderTimeout,
WriteTimeout: WriteTimeout,
} }
Context.web = CreateWeb(&webConf) Context.web = CreateWeb(&webConf)
if Context.web == nil { if Context.web == nil {

View File

@ -2,7 +2,6 @@ package home
import ( import (
"net/http" "net/http"
"strings"
"github.com/AdguardTeam/AdGuardHome/internal/aghio" "github.com/AdguardTeam/AdGuardHome/internal/aghio"
@ -41,19 +40,3 @@ func limitRequestBody(h http.Handler) (limited http.Handler) {
h.ServeHTTP(w, r) 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)
})
}

View File

@ -8,6 +8,7 @@ import (
"net/http" "net/http"
"strconv" "strconv"
"sync" "sync"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/util" "github.com/AdguardTeam/AdGuardHome/internal/util"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
@ -15,11 +16,37 @@ import (
"github.com/gobuffalo/packr" "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 { type WebConfig struct {
firstRun bool firstRun bool
BindHost string BindHost string
BindPort int BindPort int
PortHTTPS 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 // HTTPSServer - HTTPS Server
@ -66,12 +93,12 @@ func CreateWeb(conf *WebConfig) *Web {
box := packr.NewBox("../../build/static") box := packr.NewBox("../../build/static")
// if not configured, redirect / to /install.html, otherwise redirect /install.html to / // 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 // add handlers for /install paths, we only need them when we're not configured yet
if conf.firstRun { if conf.firstRun {
log.Info("This is the first launch of AdGuard Home, redirecting everything to /install.html ") 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() w.registerInstallHandlers()
} else { } else {
registerControlHandlers() registerControlHandlers()
@ -141,7 +168,10 @@ func (web *Web) Start() {
web.httpServer = &http.Server{ web.httpServer = &http.Server{
ErrorLog: web.errLogger, ErrorLog: web.errLogger,
Addr: address, Addr: address,
Handler: withMiddlewares(http.DefaultServeMux, filterPProf, limitRequestBody), Handler: withMiddlewares(Context.mux, limitRequestBody),
ReadTimeout: web.conf.ReadTimeout,
ReadHeaderTimeout: web.conf.ReadHeaderTimeout,
WriteTimeout: web.conf.WriteTimeout,
} }
err := web.httpServer.ListenAndServe() err := web.httpServer.ListenAndServe()
if err != http.ErrServerClosed { if err != http.ErrServerClosed {
@ -198,6 +228,10 @@ func (web *Web) tlsServerLoop() {
RootCAs: Context.tlsRoots, RootCAs: Context.tlsRoots,
CipherSuites: Context.tlsCiphers, CipherSuites: Context.tlsCiphers,
}, },
Handler: Context.mux,
ReadTimeout: web.conf.ReadTimeout,
ReadHeaderTimeout: web.conf.ReadHeaderTimeout,
WriteTimeout: web.conf.WriteTimeout,
} }
printHTTPAddresses("https") printHTTPAddresses("https")