github.com/anycable/anycable-go@v1.5.1/server/server.go (about)

     1  package server
     2  
     3  import (
     4  	"context"
     5  	"crypto/tls"
     6  	"fmt"
     7  	"log/slog"
     8  	"net"
     9  	"net/http"
    10  	"sync"
    11  	"time"
    12  
    13  	"github.com/go-chi/chi/v5"
    14  	"github.com/joomcode/errorx"
    15  	"golang.org/x/net/netutil"
    16  )
    17  
    18  // HTTPServer is wrapper over http.Server
    19  type HTTPServer struct {
    20  	server   *http.Server
    21  	addr     string
    22  	secured  bool
    23  	shutdown bool
    24  	started  bool
    25  	maxConn  int
    26  	mu       sync.Mutex
    27  	log      *slog.Logger
    28  
    29  	shutdownCtx context.Context
    30  	shutdownFn  context.CancelFunc
    31  
    32  	mux *chi.Mux
    33  }
    34  
    35  var (
    36  	allServers   map[string]*HTTPServer = make(map[string]*HTTPServer)
    37  	allServersMu sync.Mutex
    38  	// Host is a default bind address for HTTP servers
    39  	Host string = "localhost"
    40  	// SSL is a default configuration for HTTP servers
    41  	SSL *SSLConfig
    42  	// MaxConn is a default configuration for maximum connections
    43  	MaxConn int
    44  	// Default logger
    45  	Logger *slog.Logger = slog.Default()
    46  )
    47  
    48  // ForPort creates new or returns the existing server for the specified port
    49  func ForPort(port string) (*HTTPServer, error) {
    50  	allServersMu.Lock()
    51  	defer allServersMu.Unlock()
    52  
    53  	if _, ok := allServers[port]; !ok {
    54  		server, err := NewServer(Host, port, SSL, MaxConn)
    55  		if err != nil {
    56  			return nil, err
    57  		}
    58  		allServers[port] = server
    59  	}
    60  
    61  	return allServers[port], nil
    62  }
    63  
    64  // NewServer builds HTTPServer from config params
    65  func NewServer(host string, port string, ssl *SSLConfig, maxConn int) (*HTTPServer, error) {
    66  	router := chi.NewRouter()
    67  	addr := net.JoinHostPort(host, port)
    68  
    69  	server := &http.Server{Addr: addr, Handler: router, ReadHeaderTimeout: 5 * time.Second}
    70  
    71  	secured := (ssl != nil) && ssl.Available()
    72  
    73  	if secured {
    74  		cer, err := tls.LoadX509KeyPair(ssl.CertPath, ssl.KeyPath)
    75  		if err != nil {
    76  			return nil, errorx.Decorate(err, "failed to load SSL certificate")
    77  		}
    78  
    79  		server.TLSConfig = &tls.Config{Certificates: []tls.Certificate{cer}, MinVersion: tls.VersionTLS12}
    80  	}
    81  
    82  	shutdownCtx, shutdownFn := context.WithCancel(context.Background())
    83  
    84  	return &HTTPServer{
    85  		server:      server,
    86  		addr:        addr,
    87  		mux:         router,
    88  		secured:     secured,
    89  		shutdown:    false,
    90  		started:     false,
    91  		shutdownCtx: shutdownCtx,
    92  		shutdownFn:  shutdownFn,
    93  		maxConn:     maxConn,
    94  		log:         Logger.With("context", "http"),
    95  	}, nil
    96  }
    97  
    98  // Start server
    99  func (s *HTTPServer) Start() error {
   100  	s.mu.Lock()
   101  	if s.Running() {
   102  		s.mu.Unlock()
   103  		return nil
   104  	}
   105  
   106  	s.started = true
   107  	s.mu.Unlock()
   108  
   109  	ln, err := net.Listen("tcp", s.addr)
   110  	if err != nil {
   111  		return err
   112  	}
   113  	defer ln.Close()
   114  
   115  	if s.maxConn > 0 {
   116  		ln = netutil.LimitListener(ln, s.maxConn)
   117  	}
   118  
   119  	if s.secured {
   120  		return s.server.ServeTLS(ln, "", "")
   121  	}
   122  
   123  	return s.server.Serve(ln)
   124  }
   125  
   126  // StartAndAnnounce prints server info and starts server
   127  func (s *HTTPServer) StartAndAnnounce(name string) error {
   128  	s.mu.Lock()
   129  	if s.Running() {
   130  		s.mu.Unlock()
   131  		s.log.Debug("HTTP server has been already started", "name", name, "addr", s.Address())
   132  		return nil
   133  	}
   134  
   135  	s.log.Debug("starting HTTP server", "name", name, "addr", s.Address())
   136  	s.mu.Unlock()
   137  
   138  	return s.Start()
   139  }
   140  
   141  // Running returns true if server has been started
   142  func (s *HTTPServer) Running() bool {
   143  	return s.started
   144  }
   145  
   146  // SetupHandler adds new handler to mux
   147  func (s *HTTPServer) SetupHandler(path string, handler http.Handler) {
   148  	s.mux.Handle(path, handler)
   149  }
   150  
   151  // Shutdown shuts down server gracefully.
   152  func (s *HTTPServer) Shutdown(ctx context.Context) error {
   153  	s.mu.Lock()
   154  	if s.shutdown {
   155  		s.mu.Unlock()
   156  		return nil
   157  	}
   158  	s.shutdown = true
   159  	s.mu.Unlock()
   160  
   161  	s.shutdownFn()
   162  
   163  	return s.server.Shutdown(ctx)
   164  }
   165  
   166  // ShutdownCtx returns context for graceful shutdown.
   167  // It must be used by HTTP handlers to termniates long-running requests (SSE, long-polling).
   168  func (s *HTTPServer) ShutdownCtx() context.Context {
   169  	return s.shutdownCtx
   170  }
   171  
   172  // Stopped return true iff server has been stopped by user
   173  func (s *HTTPServer) Stopped() bool {
   174  	s.mu.Lock()
   175  	val := s.shutdown
   176  	s.mu.Unlock()
   177  	return val
   178  }
   179  
   180  // Address returns server scheme://host:port
   181  func (s *HTTPServer) Address() string {
   182  	var scheme string
   183  
   184  	if s.secured {
   185  		scheme = "https://"
   186  	} else {
   187  		scheme = "http://"
   188  	}
   189  
   190  	return fmt.Sprintf("%s%s", scheme, s.addr)
   191  }