* move HTTP server code

This commit is contained in:
Simon Zolin
2020-02-19 15:24:55 +03:00
parent 0789e4b20d
commit 8e4bc29103
6 changed files with 234 additions and 159 deletions

View File

@@ -34,8 +34,6 @@ import (
"github.com/AdguardTeam/AdGuardHome/querylog"
"github.com/AdguardTeam/AdGuardHome/stats"
"github.com/AdguardTeam/golibs/log"
"github.com/NYTimes/gziphandler"
"github.com/gobuffalo/packr"
)
const (
@@ -58,18 +56,17 @@ type homeContext struct {
// Modules
// --
clients clientsContainer // per-client-settings module
stats stats.Stats // statistics module
queryLog querylog.QueryLog // query log module
dnsServer *dnsforward.Server // DNS module
rdns *RDNS // rDNS module
whois *Whois // WHOIS module
dnsFilter *dnsfilter.Dnsfilter // DNS filtering module
dhcpServer *dhcpd.Server // DHCP module
auth *Auth // HTTP authentication module
httpServer *http.Server // HTTP module
httpsServer HTTPSServer // HTTPS module
filters Filtering
clients clientsContainer // per-client-settings module
stats stats.Stats // statistics module
queryLog querylog.QueryLog // query log module
dnsServer *dnsforward.Server // DNS module
rdns *RDNS // rDNS module
whois *Whois // WHOIS module
dnsFilter *dnsfilter.Dnsfilter // DNS filtering module
dhcpServer *dhcpd.Server // DHCP module
auth *Auth // HTTP authentication module
filters Filtering
web *Web
// Runtime properties
// --
@@ -243,9 +240,22 @@ func run(args options) {
log.Fatalf("Cannot create DNS data dir at %s: %s", Context.getDataDir(), err)
}
err = initWeb()
if err != nil {
log.Fatalf("%s", err)
sessFilename := filepath.Join(Context.getDataDir(), "sessions.db")
Context.auth = InitAuth(sessFilename, config.Users, config.WebSessionTTLHours*60*60)
if Context.auth == nil {
log.Fatalf("Couldn't initialize Auth module")
}
config.Users = nil
webConf := WebConfig{
firstRun: Context.firstRun,
BindHost: config.BindHost,
BindPort: config.BindPort,
TLS: config.TLS,
}
Context.web = CreateWeb(&webConf)
if Context.web == nil {
log.Fatalf("Can't initialize Web module")
}
if !Context.firstRun {
@@ -266,115 +276,12 @@ func run(args options) {
}
}
startWeb()
Context.web.Start()
// wait indefinitely for other go-routines to complete their job
select {}
}
// Initialize Web modules
func initWeb() error {
sessFilename := filepath.Join(Context.getDataDir(), "sessions.db")
Context.auth = InitAuth(sessFilename, config.Users, config.WebSessionTTLHours*60*60)
if Context.auth == nil {
return fmt.Errorf("Couldn't initialize Auth module")
}
config.Users = nil
// Initialize and run the admin Web interface
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)))))
// add handlers for /install paths, we only need them when we're not configured yet
if Context.firstRun {
log.Info("This is the first launch of AdGuard Home, redirecting everything to /install.html ")
http.Handle("/install.html", preInstallHandler(http.FileServer(box)))
registerInstallHandlers()
} else {
registerControlHandlers()
}
Context.httpsServer.cond = sync.NewCond(&Context.httpsServer.Mutex)
return nil
}
func startWeb() {
// for https, we have a separate goroutine loop
go httpServerLoop()
// this loop is used as an ability to change listening host and/or port
for !Context.httpsServer.shutdown {
printHTTPAddresses("http")
// we need to have new instance, because after Shutdown() the Server is not usable
address := net.JoinHostPort(config.BindHost, strconv.Itoa(config.BindPort))
Context.httpServer = &http.Server{
Addr: address,
}
err := Context.httpServer.ListenAndServe()
if err != http.ErrServerClosed {
cleanupAlways()
log.Fatal(err)
}
// We use ErrServerClosed as a sign that we need to rebind on new address, so go back to the start of the loop
}
}
func httpServerLoop() {
for !Context.httpsServer.shutdown {
Context.httpsServer.cond.L.Lock()
// this mechanism doesn't let us through until all conditions are met
for config.TLS.Enabled == false ||
config.TLS.PortHTTPS == 0 ||
len(config.TLS.PrivateKeyData) == 0 ||
len(config.TLS.CertificateChainData) == 0 { // sleep until necessary data is supplied
Context.httpsServer.cond.Wait()
}
address := net.JoinHostPort(config.BindHost, strconv.Itoa(config.TLS.PortHTTPS))
// validate current TLS config and update warnings (it could have been loaded from file)
data := validateCertificates(string(config.TLS.CertificateChainData), string(config.TLS.PrivateKeyData), config.TLS.ServerName)
if !data.ValidPair {
cleanupAlways()
log.Fatal(data.WarningValidation)
}
config.Lock()
config.TLS.tlsConfigStatus = data // update warnings
config.Unlock()
// prepare certs for HTTPS server
// important -- they have to be copies, otherwise changing the contents in config.TLS will break encryption for in-flight requests
certchain := make([]byte, len(config.TLS.CertificateChainData))
copy(certchain, config.TLS.CertificateChainData)
privatekey := make([]byte, len(config.TLS.PrivateKeyData))
copy(privatekey, config.TLS.PrivateKeyData)
cert, err := tls.X509KeyPair(certchain, privatekey)
if err != nil {
cleanupAlways()
log.Fatal(err)
}
Context.httpsServer.cond.L.Unlock()
// prepare HTTPS server
Context.httpsServer.server = &http.Server{
Addr: address,
TLSConfig: &tls.Config{
Certificates: []tls.Certificate{cert},
MinVersion: tls.VersionTLS12,
RootCAs: Context.tlsRoots,
},
}
printHTTPAddresses("https")
err = Context.httpsServer.server.ListenAndServeTLS("", "")
if err != http.ErrServerClosed {
cleanupAlways()
log.Fatal(err)
}
}
}
// Check if the current user has root (administrator) rights
// and if not, ask and try to run as root
func requireAdminRights() {
@@ -484,7 +391,14 @@ func configureLogger(args options) {
func cleanup() {
log.Info("Stopping AdGuard Home")
stopHTTPServer()
if Context.web != nil {
Context.web.Close()
Context.web = nil
}
if Context.auth != nil {
Context.auth.Close()
Context.auth = nil
}
err := stopDNSServer()
if err != nil {
@@ -496,25 +410,6 @@ func cleanup() {
}
}
// Stop HTTP server, possibly waiting for all active connections to be closed
func stopHTTPServer() {
log.Info("Stopping HTTP server...")
Context.httpsServer.shutdown = true
if Context.httpsServer.server != nil {
_ = Context.httpsServer.server.Shutdown(context.TODO())
}
if Context.httpServer != nil {
_ = Context.httpServer.Shutdown(context.TODO())
}
if Context.auth != nil {
Context.auth.Close()
Context.auth = nil
}
log.Info("Stopped HTTP server")
}
// This function is called before application exits
func cleanupAlways() {
if len(Context.pidFileName) != 0 {