mirrored from https://chromium.googlesource.com/infra/luci/luci-go
/
port.go
138 lines (120 loc) · 4 KB
/
port.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
// Copyright 2020 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"fmt"
"net/http"
"sync"
"go.chromium.org/luci/common/errors"
"go.chromium.org/luci/server/router"
)
// PortOptions is a configuration of a single serving HTTP port.
//
// See Server's AddPort.
type PortOptions struct {
Name string // optional logical name of the port for logs
ListenAddr string // local address to bind to or "-" for a dummy port
DisableMetrics bool // do not collect HTTP metrics for requests on this port
}
// Port is returned by Server's AddPort and used to setup the request routing.
type Port struct {
// Routes is a router for requests hitting this port.
//
// This router is used for all requests whose Host header does not match any
// specially registered per-host routers (see VirtualHost). Normally, there
// are no such per-host routers, so usually Routes is used for all requests.
//
// Should be populated before Server's ListenAndServe call.
Routes *router.Router
parent *Server // the owning server
opts PortOptions // options passed to AddPort
mu sync.Mutex
srv *http.Server // lazy-initialized in httpServer()
perHost map[string]*router.Router // routers added in VirtualHost(...)
}
// VirtualHost returns a router (registering it if necessary) used for requests
// that have the given Host header.
//
// Note that requests that match some registered virtual host router won't
// reach the default router (port.Routes), even if the virtual host router
// doesn't have a route for them. Such requests finish with HTTP 404.
//
// Should be called before Server's ListenAndServe (panics otherwise).
func (p *Port) VirtualHost(host string) *router.Router {
p.mu.Lock()
defer p.mu.Unlock()
if p.srv != nil {
p.parent.Fatal(errors.Reason("the server has already been started").Err())
}
r := p.perHost[host]
if r == nil {
r = p.parent.newRouter(p.opts)
if p.perHost == nil {
p.perHost = make(map[string]*router.Router, 1)
}
p.perHost[host] = r
}
return r
}
// nameForLog returns a string to identify this port in the server logs.
func (p *Port) nameForLog() string {
var pfx string
if p.opts.ListenAddr == "-" {
pfx = "-"
} else {
pfx = "http://" + p.opts.ListenAddr
}
if p.opts.Name != "" {
return fmt.Sprintf("%s [%s]", pfx, p.opts.Name)
}
return pfx
}
// httpServer lazy-initializes and returns http.Server for this port.
//
// Called from Server's ListenAndServe.
//
// Once this function is called, no more virtual hosts can be added to the
// server (an attempt to do so causes a panic).
func (p *Port) httpServer() *http.Server {
p.mu.Lock()
defer p.mu.Unlock()
if p.opts.ListenAddr == "-" {
panic("httpServer must not be used with dummy ports")
}
if p.srv == nil {
p.srv = &http.Server{
Addr: p.opts.ListenAddr,
Handler: p.initHandlerLocked(),
ErrorLog: nil, // TODO(vadimsh): Log via 'logging' package.
}
}
return p.srv
}
// initHandlerLocked initializes the top-level router for the server.
func (p *Port) initHandlerLocked() http.Handler {
// These are immutable once the server has started, so its fine to copy them
// by pointer and use without any locking.
mapping := p.perHost
fallback := p.Routes
if len(mapping) == 0 {
return fallback // no need for an extra layer of per-host routing at all
}
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if router, ok := mapping[r.Host]; ok {
router.ServeHTTP(rw, r)
} else {
fallback.ServeHTTP(rw, r)
}
})
}