github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/apiserverhttp/mux.go (about)

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package apiserverhttp
     5  
     6  import (
     7  	"net/http"
     8  	"sync"
     9  
    10  	"github.com/bmizerany/pat"
    11  	"github.com/juju/errors"
    12  )
    13  
    14  // Mux is a pattern-based HTTP muxer, based on top of
    15  // bmizerany/pat, adding support for dynamic registration
    16  // and deregistration of handlers. When a handler is
    17  // added or removed, the underlying pat mux is swapped
    18  // out with a new one.
    19  //
    20  // Adding and removing handlers is expensive: each of those
    21  // calls will create a new mux underneath. These operations
    22  // are not expected to be frequently occurring.
    23  type Mux struct {
    24  	pmu sync.Mutex
    25  	p   *pat.PatternServeMux
    26  
    27  	// mu protects added; added records the handlers
    28  	// added by AddHandler, so we can recreate the
    29  	// mux as necessary. The handlers are recorded
    30  	// in the order they are added, per method, as
    31  	// is done by pat.
    32  	mu    sync.Mutex
    33  	added map[string][]patternHandler
    34  
    35  	// Clients who are using the mux can add themselves to prevent the
    36  	// httpserver from stopping until they're done.
    37  	clients sync.WaitGroup
    38  }
    39  
    40  type patternHandler struct {
    41  	pat string
    42  	h   http.Handler
    43  }
    44  
    45  // NewMux returns a new, empty mux.
    46  func NewMux(opts ...muxOption) *Mux {
    47  	m := &Mux{
    48  		p:     pat.New(),
    49  		added: make(map[string][]patternHandler),
    50  	}
    51  	for _, opt := range opts {
    52  		opt(m)
    53  	}
    54  	return m
    55  }
    56  
    57  type muxOption func(*Mux)
    58  
    59  // ServeHTTP is part of the http.Handler interface.
    60  //
    61  // ServeHTTP routes the request to a handler registered with
    62  // AddHandler, according to the rules defined by bmizerany/pat.
    63  func (m *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    64  	m.pmu.Lock()
    65  	p := m.p
    66  	m.pmu.Unlock()
    67  	p.ServeHTTP(w, r)
    68  }
    69  
    70  // AddHandler adds an http.Handler for the given method and pattern.
    71  // AddHandler returns an error if there already exists a handler for
    72  // the method and pattern.
    73  //
    74  // This is safe to call concurrently with m.ServeHTTP and m.RemoveHandler.
    75  func (m *Mux) AddHandler(meth, pat string, h http.Handler) error {
    76  	m.mu.Lock()
    77  	defer m.mu.Unlock()
    78  	for _, ph := range m.added[meth] {
    79  		if ph.pat == pat {
    80  			return errors.AlreadyExistsf("handler for %s %q", meth, pat)
    81  		}
    82  	}
    83  	m.added[meth] = append(m.added[meth], patternHandler{pat, h})
    84  	m.recreate()
    85  	return nil
    86  }
    87  
    88  // RemoveHandler removes the http.Handler for the given method and pattern,
    89  // if any. If there is no handler registered with the method and pattern,
    90  // this is a no-op.
    91  //
    92  // This is safe to call concurrently with m.ServeHTTP and m.AddHandler.
    93  func (m *Mux) RemoveHandler(meth, pat string) {
    94  	m.mu.Lock()
    95  	defer m.mu.Unlock()
    96  	phs, ok := m.added[meth]
    97  	if !ok {
    98  		return
    99  	}
   100  	for i, ph := range phs {
   101  		if ph.pat != pat {
   102  			continue
   103  		}
   104  		head, tail := phs[:i], phs[i+1:]
   105  		m.added[meth] = append(head, tail...)
   106  		m.recreate()
   107  		return
   108  	}
   109  }
   110  
   111  // AddClient tells the mux there's another client that should keep it
   112  // alive.
   113  func (m *Mux) AddClient() {
   114  	m.clients.Add(1)
   115  }
   116  
   117  // ClientDone indicates that a client has finished and no longer needs
   118  // the mux.
   119  func (m *Mux) ClientDone() {
   120  	m.clients.Done()
   121  }
   122  
   123  // Wait will block until all of the clients have indicated that
   124  // they're done.
   125  func (m *Mux) Wait() {
   126  	m.clients.Wait()
   127  }
   128  
   129  func (m *Mux) recreate() {
   130  	p := pat.New()
   131  	for meth, phs := range m.added {
   132  		for _, ph := range phs {
   133  			p.Add(meth, ph.pat, ph.h)
   134  		}
   135  	}
   136  	m.pmu.Lock()
   137  	m.p = p
   138  	m.pmu.Unlock()
   139  }