go.uber.org/yarpc@v1.72.1/internal/net/httpserver.go (about)

     1  // Copyright (c) 2022 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package net
    22  
    23  import (
    24  	"context"
    25  	"errors"
    26  	"net"
    27  	"net/http"
    28  	"sync"
    29  
    30  	"go.uber.org/atomic"
    31  )
    32  
    33  var (
    34  	errServerStopped    = errors.New("the server has been stopped")
    35  	errAlreadyListening = errors.New("the server is already listening")
    36  )
    37  
    38  // HTTPServer wraps an http.Server to listen asynchronously and allow stopping
    39  // it.
    40  type HTTPServer struct {
    41  	*http.Server
    42  
    43  	lock     sync.RWMutex
    44  	listener net.Listener
    45  	done     chan error
    46  	stopped  atomic.Bool
    47  }
    48  
    49  // NewHTTPServer wraps the given http.Server into an HTTPServer.
    50  func NewHTTPServer(s *http.Server) *HTTPServer {
    51  	return &HTTPServer{
    52  		Server: s,
    53  		done:   make(chan error, 1),
    54  	}
    55  }
    56  
    57  // Listener returns the listener for this server or nil if the server isn't
    58  // yet listening.
    59  func (h *HTTPServer) Listener() net.Listener {
    60  	h.lock.RLock()
    61  	listener := h.listener
    62  	h.lock.RUnlock()
    63  	return listener
    64  }
    65  
    66  // ListenAndServe starts the given HTTP server up in the background and
    67  // returns immediately. The server listens on the configured Addr or ":http"
    68  // if unconfigured.
    69  //
    70  // An error is returned if the server failed to start up, if the server was
    71  // already listening, or if the server was stopped with Stop().
    72  func (h *HTTPServer) ListenAndServe() error {
    73  	if h.stopped.Load() {
    74  		return errServerStopped
    75  	}
    76  
    77  	h.lock.Lock()
    78  	defer h.lock.Unlock()
    79  
    80  	addr := h.Server.Addr
    81  	if addr == "" {
    82  		addr = ":http"
    83  	}
    84  
    85  	if h.listener != nil {
    86  		return errAlreadyListening
    87  	}
    88  
    89  	var err error
    90  	h.listener, err = net.Listen("tcp", addr)
    91  	if err != nil {
    92  		return err
    93  	}
    94  
    95  	go h.serve(h.listener)
    96  	return nil
    97  }
    98  
    99  // Serve starts the HTTP server up in the background on the given
   100  // listener and returns immediately.
   101  //
   102  // An error is returned if the server failed to start up, if the server was
   103  // already listening, or if the server was stopped with Stop().
   104  func (h *HTTPServer) Serve(lis net.Listener) error {
   105  	if h.stopped.Load() {
   106  		return errServerStopped
   107  	}
   108  
   109  	h.lock.Lock()
   110  	defer h.lock.Unlock()
   111  
   112  	if h.listener != nil {
   113  		return errAlreadyListening
   114  	}
   115  
   116  	h.listener = lis
   117  	go h.serve(h.listener)
   118  	return nil
   119  }
   120  
   121  func (h *HTTPServer) serve(listener net.Listener) {
   122  	// Serve always returns a non-nil error. For us, it's an error only if
   123  	// we didn't call Stop().
   124  	err := h.Server.Serve(listener)
   125  	if !h.stopped.Load() {
   126  		h.done <- err
   127  	} else {
   128  		h.done <- nil
   129  	}
   130  }
   131  
   132  // Shutdown stops the server. An error is returned if the server stopped
   133  // unexpectedly.
   134  //
   135  // Once a server is stopped, it cannot be started again with ListenAndServe.
   136  func (h *HTTPServer) Shutdown(ctx context.Context) error {
   137  	if h.stopped.Swap(true) {
   138  		return nil
   139  	}
   140  
   141  	wasRunning, closeErr := h.shutdownServer(ctx)
   142  	if !wasRunning {
   143  		return nil
   144  	}
   145  
   146  	serveErr := <-h.done // wait until Serve() stops
   147  	if closeErr != nil {
   148  		return closeErr
   149  	}
   150  	return serveErr
   151  }
   152  
   153  func (h *HTTPServer) shutdownServer(ctx context.Context) (wasRunning bool, _ error) {
   154  	h.lock.Lock()
   155  	defer h.lock.Unlock()
   156  
   157  	if h.listener == nil {
   158  		return false, nil
   159  	}
   160  
   161  	err := h.Server.Shutdown(ctx)
   162  
   163  	// It's possible that the serve goroutine hasn't yet started, so the server
   164  	// might not know about the listener. We ignore errors since we may b
   165  	// closing the same listener twice.
   166  	h.listener.Close()
   167  
   168  	h.listener = nil
   169  	return true, err
   170  }