github.com/prebid/prebid-server@v0.275.0/server/server.go (about)

     1  package server
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net"
     7  	"net/http"
     8  	"os"
     9  	"os/signal"
    10  	"strconv"
    11  	"syscall"
    12  	"time"
    13  
    14  	"github.com/NYTimes/gziphandler"
    15  	"github.com/golang/glog"
    16  	"github.com/prebid/prebid-server/config"
    17  	"github.com/prebid/prebid-server/metrics"
    18  	metricsconfig "github.com/prebid/prebid-server/metrics/config"
    19  )
    20  
    21  // Listen blocks forever, serving PBS requests on the given port. This will block forever, until the process is shut down.
    22  func Listen(cfg *config.Configuration, handler http.Handler, adminHandler http.Handler, metrics *metricsconfig.DetailedMetricsEngine) (err error) {
    23  	stopSignals := make(chan os.Signal, 1)
    24  	signal.Notify(stopSignals, syscall.SIGTERM, syscall.SIGINT)
    25  
    26  	// Run the servers. Fan any process-stopper signals out to each server for graceful shutdowns.
    27  	stopAdmin := make(chan os.Signal)
    28  	stopMain := make(chan os.Signal)
    29  	stopPrometheus := make(chan os.Signal)
    30  	done := make(chan struct{})
    31  
    32  	adminServer := newAdminServer(cfg, adminHandler)
    33  	go shutdownAfterSignals(adminServer, stopAdmin, done)
    34  
    35  	if cfg.UnixSocketEnable && len(cfg.UnixSocketName) > 0 { // start the unix_socket server if config enable-it.
    36  		var (
    37  			socketListener net.Listener
    38  			mainServer     = newSocketServer(cfg, handler)
    39  		)
    40  		go shutdownAfterSignals(mainServer, stopMain, done)
    41  		if socketListener, err = newUnixListener(mainServer.Addr, metrics); err != nil {
    42  			glog.Errorf("Error listening for Unix-Socket connections on path %s: %v for socket server", mainServer.Addr, err)
    43  			return
    44  		}
    45  		go runServer(mainServer, "UnixSocket", socketListener)
    46  	} else { // start the TCP server
    47  		var (
    48  			mainListener net.Listener
    49  			mainServer   = newMainServer(cfg, handler)
    50  		)
    51  		go shutdownAfterSignals(mainServer, stopMain, done)
    52  		if mainListener, err = newTCPListener(mainServer.Addr, metrics); err != nil {
    53  			glog.Errorf("Error listening for TCP connections on %s: %v for main server", mainServer.Addr, err)
    54  			return
    55  		}
    56  		go runServer(mainServer, "Main", mainListener)
    57  	}
    58  
    59  	var adminListener net.Listener
    60  	if adminListener, err = newTCPListener(adminServer.Addr, nil); err != nil {
    61  		glog.Errorf("Error listening for TCP connections on %s: %v for admin server", adminServer.Addr, err)
    62  		return
    63  	}
    64  	go runServer(adminServer, "Admin", adminListener)
    65  
    66  	if cfg.Metrics.Prometheus.Port != 0 {
    67  		var (
    68  			prometheusListener net.Listener
    69  			prometheusServer   = newPrometheusServer(cfg, metrics)
    70  		)
    71  		go shutdownAfterSignals(prometheusServer, stopPrometheus, done)
    72  		if prometheusListener, err = newTCPListener(prometheusServer.Addr, nil); err != nil {
    73  			glog.Errorf("Error listening for TCP connections on %s: %v for prometheus server", adminServer.Addr, err)
    74  			return
    75  		}
    76  
    77  		go runServer(prometheusServer, "Prometheus", prometheusListener)
    78  		wait(stopSignals, done, stopMain, stopAdmin, stopPrometheus)
    79  	} else {
    80  		wait(stopSignals, done, stopMain, stopAdmin)
    81  	}
    82  
    83  	return
    84  }
    85  
    86  func newAdminServer(cfg *config.Configuration, handler http.Handler) *http.Server {
    87  	return &http.Server{
    88  		Addr:    cfg.Host + ":" + strconv.Itoa(cfg.AdminPort),
    89  		Handler: handler,
    90  	}
    91  }
    92  
    93  func newMainServer(cfg *config.Configuration, handler http.Handler) *http.Server {
    94  	serverHandler := getCompressionEnabledHandler(handler, cfg.Compression.Response)
    95  
    96  	return &http.Server{
    97  		Addr:         cfg.Host + ":" + strconv.Itoa(cfg.Port),
    98  		Handler:      serverHandler,
    99  		ReadTimeout:  15 * time.Second,
   100  		WriteTimeout: 15 * time.Second,
   101  	}
   102  
   103  }
   104  
   105  func newSocketServer(cfg *config.Configuration, handler http.Handler) *http.Server {
   106  	serverHandler := getCompressionEnabledHandler(handler, cfg.Compression.Response)
   107  
   108  	return &http.Server{
   109  		Addr:         cfg.UnixSocketName,
   110  		Handler:      serverHandler,
   111  		ReadTimeout:  15 * time.Second,
   112  		WriteTimeout: 15 * time.Second,
   113  	}
   114  }
   115  
   116  func getCompressionEnabledHandler(h http.Handler, compressionInfo config.CompressionInfo) http.Handler {
   117  	if compressionInfo.GZIP {
   118  		h = gziphandler.GzipHandler(h)
   119  	}
   120  	return h
   121  }
   122  
   123  func runServer(server *http.Server, name string, listener net.Listener) (err error) {
   124  	if server == nil {
   125  		err = fmt.Errorf(">> Server is a nil_ptr.")
   126  		glog.Errorf("%s server quit with error: %v", name, err)
   127  		return
   128  	} else if listener == nil {
   129  		err = fmt.Errorf(">> Listener is a nil.")
   130  		glog.Errorf("%s server quit with error: %v", name, err)
   131  		return
   132  	}
   133  
   134  	glog.Infof("%s server starting on: %s", name, server.Addr)
   135  	if err = server.Serve(listener); err != nil {
   136  		glog.Errorf("%s server quit with error: %v", name, err)
   137  	}
   138  	return
   139  }
   140  
   141  func newTCPListener(address string, metrics metrics.MetricsEngine) (net.Listener, error) {
   142  	ln, err := net.Listen("tcp", address)
   143  	if err != nil {
   144  		return nil, fmt.Errorf("Error listening for TCP connections on %s: %v", address, err)
   145  	}
   146  
   147  	// This cast is in Go's core libs as Server.ListenAndServe(), so it _should_ be safe, but just in case it changes in a future version...
   148  	if casted, ok := ln.(*net.TCPListener); ok {
   149  		ln = &tcpKeepAliveListener{casted}
   150  	} else {
   151  		glog.Warning("net.Listen(\"tcp\", \"addr\") didn't return a TCPListener as it did in Go 1.9. Things will probably work fine... but this should be investigated.")
   152  	}
   153  
   154  	if metrics != nil {
   155  		ln = &monitorableListener{ln, metrics}
   156  	}
   157  
   158  	return ln, nil
   159  }
   160  
   161  func newUnixListener(address string, metrics metrics.MetricsEngine) (net.Listener, error) {
   162  	ln, err := net.Listen("unix", address)
   163  	if err != nil {
   164  		return nil, fmt.Errorf("Error listening for Unix-Socket connections on path %s: %v", address, err)
   165  	}
   166  
   167  	if casted, ok := ln.(*net.UnixListener); ok {
   168  		ln = &unixListener{casted}
   169  	} else {
   170  		glog.Warning("net.Listen(\"unix\", \"addr\") didn't return an UnixListener.")
   171  	}
   172  
   173  	if metrics != nil {
   174  		ln = &monitorableListener{ln, metrics}
   175  	}
   176  
   177  	return ln, nil
   178  }
   179  
   180  func wait(inbound <-chan os.Signal, done <-chan struct{}, outbound ...chan<- os.Signal) {
   181  	sig := <-inbound
   182  
   183  	for i := 0; i < len(outbound); i++ {
   184  		go sendSignal(outbound[i], sig)
   185  	}
   186  
   187  	for i := 0; i < len(outbound); i++ {
   188  		<-done
   189  	}
   190  }
   191  
   192  func shutdownAfterSignals(server *http.Server, stopper <-chan os.Signal, done chan<- struct{}) {
   193  	sig := <-stopper
   194  
   195  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   196  	defer cancel()
   197  
   198  	var s struct{}
   199  	glog.Infof("Stopping %s because of signal: %s", server.Addr, sig.String())
   200  	if err := server.Shutdown(ctx); err != nil {
   201  		glog.Errorf("Failed to shutdown %s: %v", server.Addr, err)
   202  	}
   203  	done <- s
   204  }
   205  
   206  func sendSignal(to chan<- os.Signal, sig os.Signal) {
   207  	to <- sig
   208  }