"Fossies" - the Fresh Open Source Software Archive

Member "AdGuardHome-0.104.3/internal/home/web.go" (19 Nov 2020, 6047 Bytes) of package /linux/misc/dns/AdGuardHome-0.104.3.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Go source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. See also the latest Fossies "Diffs" side-by-side code changes report for "web.go": 0.104.1_vs_0.104.3.

    1 package home
    2 
    3 import (
    4     "context"
    5     "crypto/tls"
    6     golog "log"
    7     "net"
    8     "net/http"
    9     "strconv"
   10     "strings"
   11     "sync"
   12 
   13     "github.com/AdguardTeam/AdGuardHome/internal/util"
   14     "github.com/AdguardTeam/golibs/log"
   15     "github.com/NYTimes/gziphandler"
   16     "github.com/gobuffalo/packr"
   17 )
   18 
   19 type WebConfig struct {
   20     firstRun  bool
   21     BindHost  string
   22     BindPort  int
   23     PortHTTPS int
   24 }
   25 
   26 // HTTPSServer - HTTPS Server
   27 type HTTPSServer struct {
   28     server   *http.Server
   29     cond     *sync.Cond
   30     condLock sync.Mutex
   31     shutdown bool // if TRUE, don't restart the server
   32     enabled  bool
   33     cert     tls.Certificate
   34 }
   35 
   36 // Web - module object
   37 type Web struct {
   38     conf        *WebConfig
   39     forceHTTPS  bool
   40     portHTTPS   int
   41     httpServer  *http.Server // HTTP module
   42     httpsServer HTTPSServer  // HTTPS module
   43     errLogger   *golog.Logger
   44 }
   45 
   46 // Proxy between Go's "log" and "golibs/log"
   47 type logWriter struct {
   48 }
   49 
   50 // HTTP server calls this function to log an error
   51 func (w *logWriter) Write(p []byte) (int, error) {
   52     log.Debug("Web: %s", string(p))
   53     return 0, nil
   54 }
   55 
   56 // CreateWeb - create module
   57 func CreateWeb(conf *WebConfig) *Web {
   58     log.Info("Initialize web module")
   59 
   60     w := Web{}
   61     w.conf = conf
   62 
   63     lw := logWriter{}
   64     w.errLogger = golog.New(&lw, "", 0)
   65 
   66     // Initialize and run the admin Web interface
   67     box := packr.NewBox("../../build/static")
   68 
   69     // if not configured, redirect / to /install.html, otherwise redirect /install.html to /
   70     http.Handle("/", postInstallHandler(optionalAuthHandler(gziphandler.GzipHandler(http.FileServer(box)))))
   71 
   72     // add handlers for /install paths, we only need them when we're not configured yet
   73     if conf.firstRun {
   74         log.Info("This is the first launch of AdGuard Home, redirecting everything to /install.html ")
   75         http.Handle("/install.html", preInstallHandler(http.FileServer(box)))
   76         w.registerInstallHandlers()
   77     } else {
   78         registerControlHandlers()
   79     }
   80 
   81     w.httpsServer.cond = sync.NewCond(&w.httpsServer.condLock)
   82     return &w
   83 }
   84 
   85 // WebCheckPortAvailable - check if port is available
   86 // BUT: if we are already using this port, no need
   87 func WebCheckPortAvailable(port int) bool {
   88     alreadyRunning := false
   89     if Context.web.httpsServer.server != nil {
   90         alreadyRunning = true
   91     }
   92     if !alreadyRunning {
   93         err := util.CheckPortAvailable(config.BindHost, port)
   94         if err != nil {
   95             return false
   96         }
   97     }
   98     return true
   99 }
  100 
  101 // TLSConfigChanged - called when TLS configuration has changed
  102 func (web *Web) TLSConfigChanged(tlsConf tlsConfigSettings) {
  103     log.Debug("Web: applying new TLS configuration")
  104     web.conf.PortHTTPS = tlsConf.PortHTTPS
  105     web.forceHTTPS = (tlsConf.ForceHTTPS && tlsConf.Enabled && tlsConf.PortHTTPS != 0)
  106     web.portHTTPS = tlsConf.PortHTTPS
  107 
  108     enabled := tlsConf.Enabled &&
  109         tlsConf.PortHTTPS != 0 &&
  110         len(tlsConf.PrivateKeyData) != 0 &&
  111         len(tlsConf.CertificateChainData) != 0
  112     var cert tls.Certificate
  113     var err error
  114     if enabled {
  115         cert, err = tls.X509KeyPair(tlsConf.CertificateChainData, tlsConf.PrivateKeyData)
  116         if err != nil {
  117             log.Fatal(err)
  118         }
  119     }
  120 
  121     web.httpsServer.cond.L.Lock()
  122     if web.httpsServer.server != nil {
  123         _ = web.httpsServer.server.Shutdown(context.TODO())
  124     }
  125     web.httpsServer.enabled = enabled
  126     web.httpsServer.cert = cert
  127     web.httpsServer.cond.Broadcast()
  128     web.httpsServer.cond.L.Unlock()
  129 }
  130 
  131 // Start - start serving HTTP requests
  132 func (web *Web) Start() {
  133     // for https, we have a separate goroutine loop
  134     go web.tlsServerLoop()
  135 
  136     // this loop is used as an ability to change listening host and/or port
  137     for !web.httpsServer.shutdown {
  138         printHTTPAddresses("http")
  139 
  140         // we need to have new instance, because after Shutdown() the Server is not usable
  141         address := net.JoinHostPort(web.conf.BindHost, strconv.Itoa(web.conf.BindPort))
  142         web.httpServer = &http.Server{
  143             ErrorLog: web.errLogger,
  144             Addr:     address,
  145             Handler:  filterPPROF(http.DefaultServeMux),
  146         }
  147         err := web.httpServer.ListenAndServe()
  148         if err != http.ErrServerClosed {
  149             cleanupAlways()
  150             log.Fatal(err)
  151         }
  152         // We use ErrServerClosed as a sign that we need to rebind on new address, so go back to the start of the loop
  153     }
  154 }
  155 
  156 // TODO(a.garipov): We currently have to use this, because everything registers
  157 // its HTTP handlers in http.DefaultServeMux.  In the future, refactor our HTTP
  158 // API initialization process and stop using the gosh darn http.DefaultServeMux
  159 // for anything at all.  Gosh darn global variables.
  160 func filterPPROF(h http.Handler) (filtered http.Handler) {
  161     return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  162         if strings.HasPrefix(r.URL.Path, "/debug/pprof") {
  163             http.NotFound(w, r)
  164 
  165             return
  166         }
  167 
  168         h.ServeHTTP(w, r)
  169     })
  170 }
  171 
  172 // Close - stop HTTP server, possibly waiting for all active connections to be closed
  173 func (web *Web) Close() {
  174     log.Info("Stopping HTTP server...")
  175     web.httpsServer.cond.L.Lock()
  176     web.httpsServer.shutdown = true
  177     web.httpsServer.cond.L.Unlock()
  178     if web.httpsServer.server != nil {
  179         _ = web.httpsServer.server.Shutdown(context.TODO())
  180     }
  181     if web.httpServer != nil {
  182         _ = web.httpServer.Shutdown(context.TODO())
  183     }
  184 
  185     log.Info("Stopped HTTP server")
  186 }
  187 
  188 func (web *Web) tlsServerLoop() {
  189     for {
  190         web.httpsServer.cond.L.Lock()
  191         if web.httpsServer.shutdown {
  192             web.httpsServer.cond.L.Unlock()
  193             break
  194         }
  195 
  196         // this mechanism doesn't let us through until all conditions are met
  197         for !web.httpsServer.enabled { // sleep until necessary data is supplied
  198             web.httpsServer.cond.Wait()
  199             if web.httpsServer.shutdown {
  200                 web.httpsServer.cond.L.Unlock()
  201                 return
  202             }
  203         }
  204 
  205         web.httpsServer.cond.L.Unlock()
  206 
  207         // prepare HTTPS server
  208         address := net.JoinHostPort(web.conf.BindHost, strconv.Itoa(web.conf.PortHTTPS))
  209         web.httpsServer.server = &http.Server{
  210             ErrorLog: web.errLogger,
  211             Addr:     address,
  212             TLSConfig: &tls.Config{
  213                 Certificates: []tls.Certificate{web.httpsServer.cert},
  214                 MinVersion:   tls.VersionTLS12,
  215                 RootCAs:      Context.tlsRoots,
  216                 CipherSuites: Context.tlsCiphers,
  217             },
  218         }
  219 
  220         printHTTPAddresses("https")
  221         err := web.httpsServer.server.ListenAndServeTLS("", "")
  222         if err != http.ErrServerClosed {
  223             cleanupAlways()
  224             log.Fatal(err)
  225         }
  226     }
  227 }