AdGuardHome/internal/home/web.go
Ainar Garipov ae8de95d89 Pull request: * all: move internal Go packages to internal/
Merge in DNS/adguard-home from 2234-move-to-internal to master

Squashed commit of the following:

commit d26a288cabeac86f9483fab307677b1027c78524
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Fri Oct 30 12:44:18 2020 +0300

    * all: move internal Go packages to internal/

    Closes #2234.
2020-10-30 13:32:02 +03:00

209 lines
5.3 KiB
Go

package home
import (
"context"
"crypto/tls"
golog "log"
"net"
"net/http"
"strconv"
"sync"
"github.com/AdguardTeam/AdGuardHome/internal/util"
"github.com/AdguardTeam/golibs/log"
"github.com/NYTimes/gziphandler"
"github.com/gobuffalo/packr"
)
type WebConfig struct {
firstRun bool
BindHost string
BindPort int
PortHTTPS int
}
// HTTPSServer - HTTPS Server
type HTTPSServer struct {
server *http.Server
cond *sync.Cond
condLock sync.Mutex
shutdown bool // if TRUE, don't restart the server
enabled bool
cert tls.Certificate
}
// Web - module object
type Web struct {
conf *WebConfig
forceHTTPS bool
portHTTPS int
httpServer *http.Server // HTTP module
httpsServer HTTPSServer // HTTPS module
errLogger *golog.Logger
}
// Proxy between Go's "log" and "golibs/log"
type logWriter struct {
}
// HTTP server calls this function to log an error
func (w *logWriter) Write(p []byte) (int, error) {
log.Debug("Web: %s", string(p))
return 0, nil
}
// CreateWeb - create module
func CreateWeb(conf *WebConfig) *Web {
log.Info("Initialize web module")
w := Web{}
w.conf = conf
lw := logWriter{}
w.errLogger = golog.New(&lw, "", 0)
// 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 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)))
w.registerInstallHandlers()
} else {
registerControlHandlers()
}
w.httpsServer.cond = sync.NewCond(&w.httpsServer.condLock)
return &w
}
// WebCheckPortAvailable - check if port is available
// BUT: if we are already using this port, no need
func WebCheckPortAvailable(port int) bool {
alreadyRunning := false
if Context.web.httpsServer.server != nil {
alreadyRunning = true
}
if !alreadyRunning {
err := util.CheckPortAvailable(config.BindHost, port)
if err != nil {
return false
}
}
return true
}
// TLSConfigChanged - called when TLS configuration has changed
func (web *Web) TLSConfigChanged(tlsConf tlsConfigSettings) {
log.Debug("Web: applying new TLS configuration")
web.conf.PortHTTPS = tlsConf.PortHTTPS
web.forceHTTPS = (tlsConf.ForceHTTPS && tlsConf.Enabled && tlsConf.PortHTTPS != 0)
web.portHTTPS = tlsConf.PortHTTPS
enabled := tlsConf.Enabled &&
tlsConf.PortHTTPS != 0 &&
len(tlsConf.PrivateKeyData) != 0 &&
len(tlsConf.CertificateChainData) != 0
var cert tls.Certificate
var err error
if enabled {
cert, err = tls.X509KeyPair(tlsConf.CertificateChainData, tlsConf.PrivateKeyData)
if err != nil {
log.Fatal(err)
}
}
web.httpsServer.cond.L.Lock()
if web.httpsServer.server != nil {
_ = web.httpsServer.server.Shutdown(context.TODO())
}
web.httpsServer.enabled = enabled
web.httpsServer.cert = cert
web.httpsServer.cond.Broadcast()
web.httpsServer.cond.L.Unlock()
}
// Start - start serving HTTP requests
func (web *Web) Start() {
// for https, we have a separate goroutine loop
go web.tlsServerLoop()
// this loop is used as an ability to change listening host and/or port
for !web.httpsServer.shutdown {
printHTTPAddresses("http")
// 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,
}
err := web.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
}
}
// Close - stop HTTP server, possibly waiting for all active connections to be closed
func (web *Web) Close() {
log.Info("Stopping HTTP server...")
web.httpsServer.cond.L.Lock()
web.httpsServer.shutdown = true
web.httpsServer.cond.L.Unlock()
if web.httpsServer.server != nil {
_ = web.httpsServer.server.Shutdown(context.TODO())
}
if web.httpServer != nil {
_ = web.httpServer.Shutdown(context.TODO())
}
log.Info("Stopped HTTP server")
}
func (web *Web) tlsServerLoop() {
for {
web.httpsServer.cond.L.Lock()
if web.httpsServer.shutdown {
web.httpsServer.cond.L.Unlock()
break
}
// this mechanism doesn't let us through until all conditions are met
for !web.httpsServer.enabled { // sleep until necessary data is supplied
web.httpsServer.cond.Wait()
if web.httpsServer.shutdown {
web.httpsServer.cond.L.Unlock()
return
}
}
web.httpsServer.cond.L.Unlock()
// prepare HTTPS server
address := net.JoinHostPort(web.conf.BindHost, strconv.Itoa(web.conf.PortHTTPS))
web.httpsServer.server = &http.Server{
ErrorLog: web.errLogger,
Addr: address,
TLSConfig: &tls.Config{
Certificates: []tls.Certificate{web.httpsServer.cert},
MinVersion: tls.VersionTLS12,
RootCAs: Context.tlsRoots,
CipherSuites: Context.tlsCiphers,
},
}
printHTTPAddresses("https")
err := web.httpsServer.server.ListenAndServeTLS("", "")
if err != http.ErrServerClosed {
cleanupAlways()
log.Fatal(err)
}
}
}