github.com/influx6/npkg@v0.8.8/nhttp/server.go (about)

     1  package nhttp
     2  
     3  import (
     4  	"context"
     5  	"crypto/tls"
     6  	"errors"
     7  	"log"
     8  	"net"
     9  	"net/http"
    10  	"os"
    11  	"os/signal"
    12  	"sync"
    13  	"sync/atomic"
    14  	"syscall"
    15  	"time"
    16  
    17  	"github.com/influx6/npkg/nerror"
    18  
    19  	netutils "github.com/influx6/npkg/nnet"
    20  	"golang.org/x/crypto/acme/autocert"
    21  )
    22  
    23  const (
    24  	shutdownDuration = time.Second * 30
    25  )
    26  
    27  var (
    28  	// ErrUnhealthy is returned when a server is considered unhealthy.
    29  	ErrUnhealthy = errors.New("Service is unhealthy")
    30  )
    31  
    32  // HealthPinger exposes what we expect to provide us a health check for a server.
    33  type HealthPinger interface {
    34  	Ping() error
    35  }
    36  
    37  type healthPinger struct {
    38  	healthy uint32
    39  }
    40  
    41  // Ping implements the HealthPinger interface.
    42  func (h *healthPinger) Ping() error {
    43  	if atomic.LoadUint32(&h.healthy) == 1 {
    44  		return ErrUnhealthy
    45  	}
    46  	return nil
    47  }
    48  
    49  func (h *healthPinger) setUnhealthy() {
    50  	atomic.StoreUint32(&h.healthy, 1)
    51  }
    52  func (h *healthPinger) setHealthy() {
    53  	atomic.StoreUint32(&h.healthy, 0)
    54  }
    55  
    56  // Server implements a http server wrapper.
    57  type Server struct {
    58  	http2           bool
    59  	shutdownTimeout time.Duration
    60  	handler         http.Handler
    61  	health          *healthPinger
    62  	server          *http.Server
    63  	listener        net.Listener
    64  	tlsConfig       *tls.Config
    65  	waiter          sync.WaitGroup
    66  	man             *autocert.Manager
    67  	closer          chan struct{}
    68  }
    69  
    70  // NewServer returns a new server which uses http instead of https.
    71  func NewServer(handler http.Handler, shutdown ...time.Duration) *Server {
    72  	var shutdownDur = shutdownDuration
    73  	if len(shutdown) != 0 {
    74  		shutdownDur = shutdown[0]
    75  	}
    76  
    77  	var health healthPinger
    78  	var server Server
    79  	server.http2 = false
    80  	server.health = &health
    81  	server.handler = handler
    82  	server.shutdownTimeout = shutdownDur
    83  	return &server
    84  }
    85  
    86  // NewServerWithTLS returns a new server which uses the provided tlsconfig for https connections.
    87  func NewServerWithTLS(http2 bool, tconfig *tls.Config, handler http.Handler, shutdown ...time.Duration) *Server {
    88  	var shutdownDur = shutdownDuration
    89  	if len(shutdown) != 0 {
    90  		shutdownDur = shutdown[0]
    91  	}
    92  
    93  	var health healthPinger
    94  	var server Server
    95  	server.http2 = http2
    96  	server.health = &health
    97  	server.handler = handler
    98  	server.tlsConfig = tconfig
    99  	server.shutdownTimeout = shutdownDur
   100  	return &server
   101  }
   102  
   103  // NewServerWithCertMan returns a new server which uses the provided autocert certificate
   104  // manager to provide http certificate.
   105  func NewServerWithCertMan(http2 bool, man *autocert.Manager, handler http.Handler, shutdown ...time.Duration) *Server {
   106  	var shutdownDur = shutdownDuration
   107  	if len(shutdown) != 0 {
   108  		shutdownDur = shutdown[0]
   109  	}
   110  
   111  	var health healthPinger
   112  	var server Server
   113  	server.man = man
   114  	server.http2 = http2
   115  	server.health = &health
   116  	server.handler = handler
   117  	server.shutdownTimeout = shutdownDur
   118  	return &server
   119  }
   120  
   121  // Listen creates new http listen for giving addr and returns any error
   122  // that occurs in attempt to starting the server.
   123  func (s *Server) Listen(ctx context.Context, addr string) error {
   124  	s.closer = make(chan struct{})
   125  
   126  	var tlsConfig = s.tlsConfig
   127  	if tlsConfig == nil && s.man == nil {
   128  		tlsConfig = &tls.Config{
   129  			GetCertificate: s.man.GetCertificate,
   130  		}
   131  	}
   132  
   133  	if s.http2 {
   134  		tlsConfig.NextProtos = append(tlsConfig.NextProtos, "h2")
   135  	}
   136  
   137  	var listener, err = netutils.MakeListener("tcp", addr, tlsConfig)
   138  	if err != nil {
   139  		return err
   140  	}
   141  
   142  	var server = &http.Server{
   143  		Addr:           addr,
   144  		Handler:        s.handler,
   145  		ReadTimeout:    30 * time.Second,
   146  		WriteTimeout:   30 * time.Second,
   147  		MaxHeaderBytes: 1 << 20,
   148  		TLSConfig:      tlsConfig,
   149  	}
   150  
   151  	s.health.setHealthy()
   152  
   153  	var errs = make(chan error, 1)
   154  	s.waiter.Add(1)
   155  	go func() {
   156  		defer s.waiter.Done()
   157  		if err := server.Serve(netutils.NewKeepAliveListener(listener)); err != nil {
   158  			s.health.setUnhealthy()
   159  			errs <- err
   160  		}
   161  	}()
   162  
   163  	var signals = make(chan os.Signal, 1)
   164  	signal.Notify(signals,
   165  		syscall.SIGTERM,
   166  		os.Interrupt,
   167  		syscall.SIGINT,
   168  		syscall.SIGTERM,
   169  		syscall.SIGQUIT,
   170  		syscall.SIGKILL,
   171  	)
   172  
   173  	s.waiter.Add(1)
   174  	go func() {
   175  		defer s.waiter.Done()
   176  		select {
   177  		case <-ctx.Done():
   178  			// server was called to close.
   179  			s.gracefulShutdown(server)
   180  		case <-s.closer:
   181  			// server was closed intentionally.
   182  			s.gracefulShutdown(server)
   183  		case <-signals:
   184  			// server received signal to close entirely.
   185  			s.gracefulShutdown(server)
   186  		}
   187  	}()
   188  
   189  	return <-errs
   190  }
   191  
   192  func (s *Server) gracefulShutdown(server *http.Server) {
   193  	s.health.setUnhealthy()
   194  	time.Sleep(20 * time.Second)
   195  	var ctx, cancel = context.WithTimeout(context.Background(), s.shutdownTimeout)
   196  	if err := server.Shutdown(ctx); err != nil {
   197  		log.Printf("Close server returned error: %+q", nerror.WrapOnly(err))
   198  	}
   199  	cancel()
   200  }
   201  
   202  // Close closes giving server
   203  func (s *Server) Close() {
   204  	select {
   205  	case <-s.closer:
   206  		return
   207  	default:
   208  		close(s.closer)
   209  	}
   210  }
   211  
   212  // Wait blocks till server is closed.
   213  func (s *Server) Wait(after ...func()) {
   214  	s.waiter.Wait()
   215  	for _, cb := range after {
   216  		cb()
   217  	}
   218  }
   219  
   220  // TLSManager returns the autocert.Manager associated with the giving server
   221  // for its tls certificates.
   222  func (s *Server) TLSManager() *autocert.Manager {
   223  	return s.man
   224  }
   225  
   226  // Health returns the HealthPinger for giving server.
   227  func (s *Server) Health() HealthPinger {
   228  	return s.health
   229  }