go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/server/port.go (about) 1 // Copyright 2020 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package server 16 17 import ( 18 "context" 19 "fmt" 20 "net" 21 "net/http" 22 "sync" 23 24 "golang.org/x/net/http2" 25 "golang.org/x/net/http2/h2c" 26 27 "go.chromium.org/luci/common/errors" 28 29 "go.chromium.org/luci/server/router" 30 ) 31 32 // PortOptions is a configuration of a single serving HTTP port. 33 // 34 // See Server's AddPort. 35 type PortOptions struct { 36 Name string // optional logical name of the port for logs 37 ListenAddr string // local address to bind to or "-" for a dummy port 38 DisableMetrics bool // do not collect HTTP metrics for requests on this port 39 } 40 41 // Port is returned by Server's AddPort and used to setup the request routing. 42 // 43 // It represents an HTTP port with a request router. 44 type Port struct { 45 // Routes is a router for requests hitting this port. 46 // 47 // This router is used for all requests whose Host header does not match any 48 // specially registered per-host routers (see VirtualHost). Normally, there 49 // are no such per-host routers, so usually Routes is used for all requests. 50 // 51 // Should be populated before Server's Serve loop. 52 Routes *router.Router 53 54 parent *Server // the owning server 55 opts PortOptions // options passed to AddPort 56 allowH2C bool // if set allow HTTP/2 Cleartext requests 57 listener net.Listener // listening socket if ListenAddr != "-" 58 59 mu sync.Mutex 60 srv *http.Server // lazy-initialized in httpServer() 61 perHost map[string]*router.Router // routers added in VirtualHost(...) 62 } 63 64 // VirtualHost returns a router (registering it if necessary) used for requests 65 // that have the given Host header. 66 // 67 // Note that requests that match some registered virtual host router won't 68 // reach the default router (port.Routes), even if the virtual host router 69 // doesn't have a route for them. Such requests finish with HTTP 404. 70 // 71 // Should be called before Server's Serve loop (panics otherwise). 72 func (p *Port) VirtualHost(host string) *router.Router { 73 p.mu.Lock() 74 defer p.mu.Unlock() 75 76 if p.srv != nil { 77 p.parent.Fatal(errors.Reason("the server has already been started").Err()) 78 } 79 80 r := p.perHost[host] 81 if r == nil { 82 r = p.parent.newRouter(p.opts) 83 if p.perHost == nil { 84 p.perHost = make(map[string]*router.Router, 1) 85 } 86 p.perHost[host] = r 87 } 88 89 return r 90 } 91 92 // nameForLog returns a string to identify this port in the server logs. 93 // 94 // Part of the servingPort interface. 95 func (p *Port) nameForLog() string { 96 var pfx string 97 if p.listener != nil { 98 pfx = "http://" + p.listener.Addr().String() 99 if p.allowH2C { 100 pfx += " (h2c on)" 101 } 102 } else { 103 pfx = "-" 104 } 105 if p.opts.Name != "" { 106 return fmt.Sprintf("%s [%s]", pfx, p.opts.Name) 107 } 108 return pfx 109 } 110 111 // serve runs the serving loop until it is gracefully stopped. 112 // 113 // Part of the servingPort interface. 114 func (p *Port) serve(baseCtx func() context.Context) error { 115 srv := p.httpServer() 116 srv.BaseContext = func(net.Listener) context.Context { return baseCtx() } 117 err := srv.Serve(p.listener) 118 if err != nil && err != http.ErrServerClosed { 119 return err 120 } 121 return nil 122 } 123 124 // shutdown gracefully stops the server, blocking until it is closed. 125 // 126 // Does nothing if the server is not running. 127 // 128 // Part of the servingPort interface. 129 func (p *Port) shutdown(ctx context.Context) { 130 _ = p.httpServer().Shutdown(ctx) 131 } 132 133 // httpServer lazy-initializes and returns http.Server for this port. 134 // 135 // Once this function is called, no more virtual hosts can be added to the 136 // server (an attempt to do so causes a panic). 137 func (p *Port) httpServer() *http.Server { 138 p.mu.Lock() 139 defer p.mu.Unlock() 140 141 if p.listener == nil { 142 panic("httpServer must not be used with uninitialized ports") 143 } 144 145 if p.srv == nil { 146 p.srv = &http.Server{ 147 Addr: p.listener.Addr().String(), 148 Handler: p.initHandlerLocked(), 149 ErrorLog: nil, // TODO(vadimsh): Log via 'logging' package. 150 } 151 // See https://pkg.go.dev/golang.org/x/net/http2/h2c. 152 if p.allowH2C { 153 p.srv.Handler = h2c.NewHandler(p.srv.Handler, &http2.Server{}) 154 } 155 } 156 return p.srv 157 } 158 159 // initHandlerLocked initializes the top-level router for the server. 160 func (p *Port) initHandlerLocked() http.Handler { 161 // These are immutable once the server has started, so its fine to copy them 162 // by pointer and use without any locking. 163 mapping := p.perHost 164 fallback := p.Routes 165 166 if len(mapping) == 0 { 167 // No need for an extra layer of per-host routing at all. 168 return p.parent.wrapHTTPHandler(fallback) 169 } 170 171 return p.parent.wrapHTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { 172 if router, ok := mapping[r.Host]; ok { 173 router.ServeHTTP(rw, r) 174 } else { 175 fallback.ServeHTTP(rw, r) 176 } 177 })) 178 }