77
// repetition may be undesirable, so call consolidateAddrMappings() to map
78
// multiple addresses to the same lists of server blocks (a many:many mapping).
79
// (Doing this is essentially a map-reduce technique.)
80
-func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []serverBlock,
80
+func (st *ServerType) mapAddressToProtocolToServerBlocks(originalServerBlocks []serverBlock,
81
options map[string]any,
82
-) (map[string][]serverBlock, error) {
83
- sbmap := make(map[string][]serverBlock)
82
+) (map[string]map[string][]serverBlock, error) {
83
+ addrToProtocolToServerBlocks := map[string]map[string][]serverBlock{}
84
+
85
+ type keyWithParsedKey struct {
86
+ key caddyfile.Token
87
+ parsedKey Address
88
+ }
89
90
for i, sblock := range originalServerBlocks {
91
// within a server block, we need to map all the listener addresses
93
// will be served by them; this has the effect of treating each
94
// key of a server block as its own, but without having to repeat its
95
// contents in cases where multiple keys really can be served together
91
- addrToKeys := make(map[string][]caddyfile.Token)
96
+ addrToProtocolToKeyWithParsedKeys := map[string]map[string][]keyWithParsedKey{}
97
for j, key := range sblock.block.Keys {
98
+ parsedKey, err := ParseAddress(key.Text)
99
+ if err != nil {
100
+ return nil, fmt.Errorf("parsing key: %v", err)
101
+ }
102
+ parsedKey = parsedKey.Normalize()
103
+
104
// a key can have multiple listener addresses if there are multiple
105
// arguments to the 'bind' directive (although they will all have
106
// the same port, since the port is defined by the key or is implicit
107
// through automatic HTTPS)
97
- addrs, err := st.listenerAddrsForServerBlockKey(sblock, key.Text, options)
108
+ listeners, err := st.listenersForServerBlockAddress(sblock, parsedKey, options)
109
if err != nil {
110
return nil, fmt.Errorf("server block %d, key %d (%s): determining listener address: %v", i, j, key.Text, err)
111
}
112
102
- // associate this key with each listener address it is served on
103
- for _, addr := range addrs {
104
- addrToKeys[addr] = append(addrToKeys[addr], key)
113
+ // associate this key with its protocols and each listener address served with them
114
+ kwpk := keyWithParsedKey{key, parsedKey}
115
+ for addr, protocols := range listeners {
116
+ protocolToKeyWithParsedKeys, ok := addrToProtocolToKeyWithParsedKeys[addr]
117
+ if !ok {
118
+ protocolToKeyWithParsedKeys = map[string][]keyWithParsedKey{}
119
+ addrToProtocolToKeyWithParsedKeys[addr] = protocolToKeyWithParsedKeys
120
+ }
121
+
122
+ // an empty protocol indicates the default, a nil or empty value in the ListenProtocols array
123
+ if len(protocols) == 0 {
124
+ protocols[""] = struct{}{}
125
+ }
126
+ for prot := range protocols {
127
+ protocolToKeyWithParsedKeys[prot] = append(
128
+ protocolToKeyWithParsedKeys[prot],
129
+ kwpk)
130
+ }
131
}
132
}
133
134
// make a slice of the map keys so we can iterate in sorted order
109
- addrs := make([]string, 0, len(addrToKeys))
110
- for k := range addrToKeys {
111
- addrs = append(addrs, k)
135
+ addrs := make([]string, 0, len(addrToProtocolToKeyWithParsedKeys))
136
+ for addr := range addrToProtocolToKeyWithParsedKeys {
137
+ addrs = append(addrs, addr)
138
}
139
sort.Strings(addrs)
140
144
// server block are only the ones which use the address; but
145
// the contents (tokens) are of course the same
146
for _, addr := range addrs {
121
- keys := addrToKeys[addr]
122
- // parse keys so that we only have to do it once
123
- parsedKeys := make([]Address, 0, len(keys))
124
- for _, key := range keys {
125
- addr, err := ParseAddress(key.Text)
126
- if err != nil {
127
- return nil, fmt.Errorf("parsing key '%s': %v", key.Text, err)
147
+ protocolToKeyWithParsedKeys := addrToProtocolToKeyWithParsedKeys[addr]
148
+
149
+ prots := make([]string, 0, len(protocolToKeyWithParsedKeys))
150
+ for prot := range protocolToKeyWithParsedKeys {
151
+ prots = append(prots, prot)
152
+ }
153
+ sort.Strings(prots)
154
+
155
+ protocolToServerBlocks, ok := addrToProtocolToServerBlocks[addr]
156
+ if !ok {
157
+ protocolToServerBlocks = map[string][]serverBlock{}
158
+ addrToProtocolToServerBlocks[addr] = protocolToServerBlocks
159
+ }
160
+
161
+ for _, prot := range prots {
162
+ keyWithParsedKeys := protocolToKeyWithParsedKeys[prot]
163
+
164
+ keys := make([]caddyfile.Token, len(keyWithParsedKeys))
165
+ parsedKeys := make([]Address, len(keyWithParsedKeys))
166
+
167
+ for k, keyWithParsedKey := range keyWithParsedKeys {
168
+ keys[k] = keyWithParsedKey.key
169
+ parsedKeys[k] = keyWithParsedKey.parsedKey
170
}
129
- parsedKeys = append(parsedKeys, addr.Normalize())
171
+
172
+ protocolToServerBlocks[prot] = append(protocolToServerBlocks[prot], serverBlock{
173
+ block: caddyfile.ServerBlock{
174
+ Keys: keys,
175
+ Segments: sblock.block.Segments,
176
+ },
177
+ pile: sblock.pile,
178
+ parsedKeys: parsedKeys,
179
+ })
180
}
131
- sbmap[addr] = append(sbmap[addr], serverBlock{
132
- block: caddyfile.ServerBlock{
133
- Keys: keys,
134
- Segments: sblock.block.Segments,
135
- },
136
- pile: sblock.pile,
137
- keys: parsedKeys,
138
- })
181
}
182
}
183
142
- return sbmap, nil
184
+ return addrToProtocolToServerBlocks, nil
185
}
186
187
// consolidateAddrMappings eliminates repetition of identical server blocks in a mapping of
146
-// single listener addresses to lists of server blocks. Since multiple addresses may serve
147
-// identical sites (server block contents), this function turns a 1:many mapping into a
148
-// many:many mapping. Server block contents (tokens) must be exactly identical so that
149
-// reflect.DeepEqual returns true in order for the addresses to be combined. Identical
150
-// entries are deleted from the addrToServerBlocks map. Essentially, each pairing (each
188
+// single listener addresses to protocols to lists of server blocks. Since multiple addresses
189
+// may serve multiple protocols to identical sites (server block contents), this function turns
190
+// a 1:many mapping into a many:many mapping. Server block contents (tokens) must be
191
+// exactly identical so that reflect.DeepEqual returns true in order for the addresses to be combined.
192
+// Identical entries are deleted from the addrToServerBlocks map. Essentially, each pairing (each
193
// association from multiple addresses to multiple server blocks; i.e. each element of
194
// the returned slice) becomes a server definition in the output JSON.
153
-func (st *ServerType) consolidateAddrMappings(addrToServerBlocks map[string][]serverBlock) []sbAddrAssociation {
154
- sbaddrs := make([]sbAddrAssociation, 0, len(addrToServerBlocks))
155
- for addr, sblocks := range addrToServerBlocks {
156
- // we start with knowing that at least this address
157
- // maps to these server blocks
158
- a := sbAddrAssociation{
159
- addresses: []string{addr},
160
- serverBlocks: sblocks,
195
+func (st *ServerType) consolidateAddrMappings(addrToProtocolToServerBlocks map[string]map[string][]serverBlock) []sbAddrAssociation {
196
+ sbaddrs := make([]sbAddrAssociation, 0, len(addrToProtocolToServerBlocks))
197
+
198
+ addrs := make([]string, 0, len(addrToProtocolToServerBlocks))
199
+ for addr := range addrToProtocolToServerBlocks {
200
+ addrs = append(addrs, addr)
201
+ }
202
+ sort.Strings(addrs)
203
+
204
+ for _, addr := range addrs {
205
+ protocolToServerBlocks := addrToProtocolToServerBlocks[addr]
206
+
207
+ prots := make([]string, 0, len(protocolToServerBlocks))
208
+ for prot := range protocolToServerBlocks {
209
+ prots = append(prots, prot)
210
}
211
+ sort.Strings(prots)
212
+
213
+ for _, prot := range prots {
214
+ serverBlocks := protocolToServerBlocks[prot]
215
+
216
+ // now find other addresses that map to identical
217
+ // server blocks and add them to our map of listener
218
+ // addresses and protocols, while removing them from
219
+ // the original map
220
+ listeners := map[string]map[string]struct{}{}
221
+
222
+ for otherAddr, otherProtocolToServerBlocks := range addrToProtocolToServerBlocks {
223
+ for otherProt, otherServerBlocks := range otherProtocolToServerBlocks {
224
+ if addr == otherAddr && prot == otherProt || reflect.DeepEqual(serverBlocks, otherServerBlocks) {
225
+ listener, ok := listeners[otherAddr]
226
+ if !ok {
227
+ listener = map[string]struct{}{}
228
+ listeners[otherAddr] = listener
229
+ }
230
+ listener[otherProt] = struct{}{}
231
+ delete(otherProtocolToServerBlocks, otherProt)
232
+ }
233
+ }
234
+ }
235
163
- // now find other addresses that map to identical
164
- // server blocks and add them to our list of
165
- // addresses, while removing them from the map
166
- for otherAddr, otherSblocks := range addrToServerBlocks {
167
- if addr == otherAddr {
168
- continue
236
+ addresses := make([]string, 0, len(listeners))
237
+ for lnAddr := range listeners {
238
+ addresses = append(addresses, lnAddr)
239
}
170
- if reflect.DeepEqual(sblocks, otherSblocks) {
171
- a.addresses = append(a.addresses, otherAddr)
172
- delete(addrToServerBlocks, otherAddr)
240
+ sort.Strings(addresses)
241
+
242
+ addressesWithProtocols := make([]addressWithProtocols, 0, len(listeners))
243
+
244
+ for _, lnAddr := range addresses {
245
+ lnProts := listeners[lnAddr]
246
+ prots := make([]string, 0, len(lnProts))
247
+ for prot := range lnProts {
248
+ prots = append(prots, prot)
249
+ }
250
+ sort.Strings(prots)
251
+
252
+ addressesWithProtocols = append(addressesWithProtocols, addressWithProtocols{
253
+ address: lnAddr,
254
+ protocols: prots,
255
+ })
256
}
174
- }
175
- sort.Strings(a.addresses)
257
177
- sbaddrs = append(sbaddrs, a)
258
+ sbaddrs = append(sbaddrs, sbAddrAssociation{
259
+ addressesWithProtocols: addressesWithProtocols,
260
+ serverBlocks: serverBlocks,
261
+ })
262
+ }
263
}
264
180
- // sort them by their first address (we know there will always be at least one)
181
- // to avoid problems with non-deterministic ordering (makes tests flaky)
182
- sort.Slice(sbaddrs, func(i, j int) bool {
183
- return sbaddrs[i].addresses[0] < sbaddrs[j].addresses[0]
184
- })
185
-
265
return sbaddrs
266
}
267
189
-// listenerAddrsForServerBlockKey essentially converts the Caddyfile
190
-// site addresses to Caddy listener addresses for each server block.
191
-func (st *ServerType) listenerAddrsForServerBlockKey(sblock serverBlock, key string,
268
+// listenersForServerBlockAddress essentially converts the Caddyfile site addresses to a map from
269
+// Caddy listener addresses and the protocols to serve them with to the parsed address for each server block.
270
+func (st *ServerType) listenersForServerBlockAddress(sblock serverBlock, addr Address,
271
options map[string]any,
193
-) ([]string, error) {
194
- addr, err := ParseAddress(key)
195
- if err != nil {
196
- return nil, fmt.Errorf("parsing key: %v", err)
197
- }
198
- addr = addr.Normalize()
199
-
272
+) (map[string]map[string]struct{}, error) {
273
switch addr.Scheme {
274
case "wss":
275
return nil, fmt.Errorf("the scheme wss:// is only supported in browsers; use https:// instead")
303
304
// error if scheme and port combination violate convention
305
if (addr.Scheme == "http" && lnPort == httpsPort) || (addr.Scheme == "https" && lnPort == httpPort) {
233
- return nil, fmt.Errorf("[%s] scheme and port violate convention", key)
306
+ return nil, fmt.Errorf("[%s] scheme and port violate convention", addr.String())
307
}
308
236
- // the bind directive specifies hosts (and potentially network), but is optional
237
- lnHosts := make([]string, 0, len(sblock.pile["bind"]))
309
+ // the bind directive specifies hosts (and potentially network), and the protocols to serve them with, but is optional
310
+ lnCfgVals := make([]addressesWithProtocols, 0, len(sblock.pile["bind"]))
311
for _, cfgVal := range sblock.pile["bind"] {
239
- lnHosts = append(lnHosts, cfgVal.Value.([]string)...)
312
+ if val, ok := cfgVal.Value.(addressesWithProtocols); ok {
313
+ lnCfgVals = append(lnCfgVals, val)
314
+ }
315
}
241
- if len(lnHosts) == 0 {
242
- if defaultBind, ok := options["default_bind"].([]string); ok {
243
- lnHosts = defaultBind
316
+ if len(lnCfgVals) == 0 {
317
+ if defaultBindValues, ok := options["default_bind"].([]ConfigValue); ok {
318
+ for _, defaultBindValue := range defaultBindValues {
319
+ lnCfgVals = append(lnCfgVals, defaultBindValue.Value.(addressesWithProtocols))
320
+ }
321
} else {
245
- lnHosts = []string{""}
322
+ lnCfgVals = []addressesWithProtocols{{
323
+ addresses: []string{""},
324
+ protocols: nil,
325
+ }}
326
}
327
}
328
329
// use a map to prevent duplication
250
- listeners := make(map[string]struct{})
251
- for _, lnHost := range lnHosts {
252
- // normally we would simply append the port,
253
- // but if lnHost is IPv6, we need to ensure it
254
- // is enclosed in [ ]; net.JoinHostPort does
255
- // this for us, but lnHost might also have a
256
- // network type in front (e.g. "tcp/") leading
257
- // to "[tcp/::1]" which causes parsing failures
258
- // later; what we need is "tcp/[::1]", so we have
259
- // to split the network and host, then re-combine
260
- network, host, ok := strings.Cut(lnHost, "/")
261
- if !ok {
262
- host = network
263
- network = ""
264
- }
265
- host = strings.Trim(host, "[]") // IPv6
266
- networkAddr := caddy.JoinNetworkAddress(network, host, lnPort)
267
- addr, err := caddy.ParseNetworkAddress(networkAddr)
268
- if err != nil {
269
- return nil, fmt.Errorf("parsing network address: %v", err)
330
+ listeners := map[string]map[string]struct{}{}
331
+ for _, lnCfgVal := range lnCfgVals {
332
+ for _, lnHost := range lnCfgVal.addresses {
333
+ networkAddr, err := caddy.ParseNetworkAddressFromHostPort(lnHost, lnPort)
334
+ if err != nil {
335
+ return nil, fmt.Errorf("parsing network address: %v", err)
336
+ }
337
+ if _, ok := listeners[addr.String()]; !ok {
338
+ listeners[networkAddr.String()] = map[string]struct{}{}
339
+ }
340
+ for _, protocol := range lnCfgVal.protocols {
341
+ listeners[networkAddr.String()][protocol] = struct{}{}
342
+ }
343
}
271
- listeners[addr.String()] = struct{}{}
344
}
345
274
- // now turn map into list
275
- listenersList := make([]string, 0, len(listeners))
276
- for lnStr := range listeners {
277
- listenersList = append(listenersList, lnStr)
278
- }
279
- sort.Strings(listenersList)
346
+ return listeners, nil
347
+}
348
281
- return listenersList, nil
349
+// addressesWithProtocols associates a list of listen addresses
350
+// with a list of protocols to serve them with
351
+type addressesWithProtocols struct {
352
+ addresses []string
353
+ protocols []string
354
}
355
356
// Address represents a site address. It contains