Browse Source
* httpcaddyfile: First pass at implementing server options * httpcaddyfile: Add listener wrapper support * httpcaddyfile: Sort sbaddrs to make adapt output more deterministic * httpcaddyfile: Add server options adapt tests * httpcaddyfile: Windows line endings lol * caddytest: More windows line endings lol (sorry Matt) * Update caddyconfig/httpcaddyfile/serveroptions.go Co-authored-by: Matt Holt <mholt@users.noreply.github.com> * httpcaddyfile: Reword listener address "matcher" * Apply suggestions from code review Co-authored-by: Matt Holt <mholt@users.noreply.github.com> * httpcaddyfile: Deprecate experimental_http3 option (moved to servers) * httpcaddyfile: Remove validation step, no longer needed Co-authored-by: Matt Holt <mholt@users.noreply.github.com>master
committed by
GitHub
15 changed files with 1084 additions and 658 deletions
@ -0,0 +1,235 @@ |
|||
// Copyright 2015 Matthew Holt and The Caddy 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 httpcaddyfile |
|||
|
|||
import ( |
|||
"encoding/json" |
|||
"fmt" |
|||
|
|||
"github.com/caddyserver/caddy/v2" |
|||
"github.com/caddyserver/caddy/v2/caddyconfig" |
|||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" |
|||
"github.com/caddyserver/caddy/v2/modules/caddyhttp" |
|||
"github.com/dustin/go-humanize" |
|||
) |
|||
|
|||
// serverOptions collects server config overrides parsed from Caddyfile global options
|
|||
type serverOptions struct { |
|||
// If set, will only apply these options to servers that contain a
|
|||
// listener address that matches exactly. If empty, will apply to all
|
|||
// servers that were not already matched by another serverOptions.
|
|||
ListenerAddress string |
|||
|
|||
// These will all map 1:1 to the caddyhttp.Server struct
|
|||
ListenerWrappersRaw []json.RawMessage |
|||
ReadTimeout caddy.Duration |
|||
ReadHeaderTimeout caddy.Duration |
|||
WriteTimeout caddy.Duration |
|||
IdleTimeout caddy.Duration |
|||
MaxHeaderBytes int |
|||
AllowH2C bool |
|||
ExperimentalHTTP3 bool |
|||
StrictSNIHost *bool |
|||
} |
|||
|
|||
func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (interface{}, error) { |
|||
serverOpts := serverOptions{} |
|||
for d.Next() { |
|||
if d.NextArg() { |
|||
serverOpts.ListenerAddress = d.Val() |
|||
if d.NextArg() { |
|||
return nil, d.ArgErr() |
|||
} |
|||
} |
|||
for nesting := d.Nesting(); d.NextBlock(nesting); { |
|||
switch d.Val() { |
|||
case "listener_wrappers": |
|||
for nesting := d.Nesting(); d.NextBlock(nesting); { |
|||
mod, err := caddy.GetModule("caddy.listeners." + d.Val()) |
|||
if err != nil { |
|||
return nil, fmt.Errorf("finding listener module '%s': %v", d.Val(), err) |
|||
} |
|||
unm, ok := mod.New().(caddyfile.Unmarshaler) |
|||
if !ok { |
|||
return nil, fmt.Errorf("listener module '%s' is not a Caddyfile unmarshaler", mod) |
|||
} |
|||
err = unm.UnmarshalCaddyfile(d.NewFromNextSegment()) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
listenerWrapper, ok := unm.(caddy.ListenerWrapper) |
|||
if !ok { |
|||
return nil, fmt.Errorf("module %s is not a listener wrapper", mod) |
|||
} |
|||
jsonListenerWrapper := caddyconfig.JSONModuleObject( |
|||
listenerWrapper, |
|||
"wrapper", |
|||
listenerWrapper.(caddy.Module).CaddyModule().ID.Name(), |
|||
nil, |
|||
) |
|||
serverOpts.ListenerWrappersRaw = append(serverOpts.ListenerWrappersRaw, jsonListenerWrapper) |
|||
} |
|||
|
|||
case "timeouts": |
|||
for nesting := d.Nesting(); d.NextBlock(nesting); { |
|||
switch d.Val() { |
|||
case "read_body": |
|||
if !d.NextArg() { |
|||
return nil, d.ArgErr() |
|||
} |
|||
dur, err := caddy.ParseDuration(d.Val()) |
|||
if err != nil { |
|||
return nil, d.Errf("parsing read_body timeout duration: %v", err) |
|||
} |
|||
serverOpts.ReadTimeout = caddy.Duration(dur) |
|||
|
|||
case "read_header": |
|||
if !d.NextArg() { |
|||
return nil, d.ArgErr() |
|||
} |
|||
dur, err := caddy.ParseDuration(d.Val()) |
|||
if err != nil { |
|||
return nil, d.Errf("parsing read_header timeout duration: %v", err) |
|||
} |
|||
serverOpts.ReadHeaderTimeout = caddy.Duration(dur) |
|||
|
|||
case "write": |
|||
if !d.NextArg() { |
|||
return nil, d.ArgErr() |
|||
} |
|||
dur, err := caddy.ParseDuration(d.Val()) |
|||
if err != nil { |
|||
return nil, d.Errf("parsing write timeout duration: %v", err) |
|||
} |
|||
serverOpts.WriteTimeout = caddy.Duration(dur) |
|||
|
|||
case "idle": |
|||
if !d.NextArg() { |
|||
return nil, d.ArgErr() |
|||
} |
|||
dur, err := caddy.ParseDuration(d.Val()) |
|||
if err != nil { |
|||
return nil, d.Errf("parsing idle timeout duration: %v", err) |
|||
} |
|||
serverOpts.IdleTimeout = caddy.Duration(dur) |
|||
|
|||
default: |
|||
return nil, d.Errf("unrecognized timeouts option '%s'", d.Val()) |
|||
} |
|||
} |
|||
|
|||
case "max_header_size": |
|||
var sizeStr string |
|||
if !d.AllArgs(&sizeStr) { |
|||
return nil, d.ArgErr() |
|||
} |
|||
size, err := humanize.ParseBytes(sizeStr) |
|||
if err != nil { |
|||
return nil, d.Errf("parsing max_header_size: %v", err) |
|||
} |
|||
serverOpts.MaxHeaderBytes = int(size) |
|||
|
|||
case "protocol": |
|||
for nesting := d.Nesting(); d.NextBlock(nesting); { |
|||
switch d.Val() { |
|||
case "allow_h2c": |
|||
if d.NextArg() { |
|||
return nil, d.ArgErr() |
|||
} |
|||
serverOpts.AllowH2C = true |
|||
|
|||
case "experimental_http3": |
|||
if d.NextArg() { |
|||
return nil, d.ArgErr() |
|||
} |
|||
serverOpts.ExperimentalHTTP3 = true |
|||
|
|||
case "strict_sni_host": |
|||
if d.NextArg() { |
|||
return nil, d.ArgErr() |
|||
} |
|||
trueBool := true |
|||
serverOpts.StrictSNIHost = &trueBool |
|||
|
|||
default: |
|||
return nil, d.Errf("unrecognized protocol option '%s'", d.Val()) |
|||
} |
|||
} |
|||
|
|||
default: |
|||
return nil, d.Errf("unrecognized servers option '%s'", d.Val()) |
|||
} |
|||
} |
|||
} |
|||
return serverOpts, nil |
|||
} |
|||
|
|||
// applyServerOptions sets the server options on the appropriate servers
|
|||
func applyServerOptions( |
|||
servers map[string]*caddyhttp.Server, |
|||
options map[string]interface{}, |
|||
warnings *[]caddyconfig.Warning, |
|||
) error { |
|||
// If experimental HTTP/3 is enabled, enable it on each server.
|
|||
// We already know there won't be a conflict with serverOptions because
|
|||
// we validated earlier that "experimental_http3" cannot be set at the same
|
|||
// time as "servers"
|
|||
if enableH3, ok := options["experimental_http3"].(bool); ok && enableH3 { |
|||
*warnings = append(*warnings, caddyconfig.Warning{Message: "the 'experimental_http3' global option is deprecated, please use the 'servers > protocol > experimental_http3' option instead"}) |
|||
for _, srv := range servers { |
|||
srv.ExperimentalHTTP3 = true |
|||
} |
|||
} |
|||
|
|||
serverOpts, ok := options["servers"].([]serverOptions) |
|||
if !ok { |
|||
return nil |
|||
} |
|||
|
|||
for _, server := range servers { |
|||
// find the options that apply to this server
|
|||
opts := func() *serverOptions { |
|||
for _, entry := range serverOpts { |
|||
if entry.ListenerAddress == "" { |
|||
return &entry |
|||
} |
|||
for _, listener := range server.Listen { |
|||
if entry.ListenerAddress == listener { |
|||
return &entry |
|||
} |
|||
} |
|||
} |
|||
return nil |
|||
}() |
|||
|
|||
// if none apply, then move to the next server
|
|||
if opts == nil { |
|||
continue |
|||
} |
|||
|
|||
// set all the options
|
|||
server.ListenerWrappersRaw = opts.ListenerWrappersRaw |
|||
server.ReadTimeout = opts.ReadTimeout |
|||
server.ReadHeaderTimeout = opts.ReadHeaderTimeout |
|||
server.WriteTimeout = opts.WriteTimeout |
|||
server.IdleTimeout = opts.IdleTimeout |
|||
server.MaxHeaderBytes = opts.MaxHeaderBytes |
|||
server.AllowH2C = opts.AllowH2C |
|||
server.ExperimentalHTTP3 = opts.ExperimentalHTTP3 |
|||
server.StrictSNIHost = opts.StrictSNIHost |
|||
} |
|||
|
|||
return nil |
|||
} |
@ -0,0 +1,83 @@ |
|||
{ |
|||
servers { |
|||
timeouts { |
|||
idle 90s |
|||
} |
|||
} |
|||
servers :80 { |
|||
timeouts { |
|||
idle 60s |
|||
} |
|||
} |
|||
servers :443 { |
|||
timeouts { |
|||
idle 30s |
|||
} |
|||
} |
|||
} |
|||
|
|||
foo.com { |
|||
} |
|||
|
|||
http://bar.com { |
|||
} |
|||
|
|||
:8080 { |
|||
} |
|||
|
|||
---------- |
|||
{ |
|||
"apps": { |
|||
"http": { |
|||
"servers": { |
|||
"srv0": { |
|||
"listen": [ |
|||
":443" |
|||
], |
|||
"idle_timeout": 30000000000, |
|||
"routes": [ |
|||
{ |
|||
"match": [ |
|||
{ |
|||
"host": [ |
|||
"foo.com" |
|||
] |
|||
} |
|||
], |
|||
"terminal": true |
|||
} |
|||
] |
|||
}, |
|||
"srv1": { |
|||
"listen": [ |
|||
":80" |
|||
], |
|||
"idle_timeout": 60000000000, |
|||
"routes": [ |
|||
{ |
|||
"match": [ |
|||
{ |
|||
"host": [ |
|||
"bar.com" |
|||
] |
|||
} |
|||
], |
|||
"terminal": true |
|||
} |
|||
], |
|||
"automatic_https": { |
|||
"skip": [ |
|||
"bar.com" |
|||
] |
|||
} |
|||
}, |
|||
"srv2": { |
|||
"listen": [ |
|||
":8080" |
|||
], |
|||
"idle_timeout": 90000000000 |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,62 @@ |
|||
{ |
|||
servers { |
|||
listener_wrappers { |
|||
tls |
|||
} |
|||
timeouts { |
|||
read_body 30s |
|||
read_header 30s |
|||
write 30s |
|||
idle 30s |
|||
} |
|||
max_header_size 100MB |
|||
protocol { |
|||
allow_h2c |
|||
experimental_http3 |
|||
strict_sni_host |
|||
} |
|||
} |
|||
} |
|||
|
|||
foo.com { |
|||
} |
|||
|
|||
---------- |
|||
{ |
|||
"apps": { |
|||
"http": { |
|||
"servers": { |
|||
"srv0": { |
|||
"listen": [ |
|||
":443" |
|||
], |
|||
"listener_wrappers": [ |
|||
{ |
|||
"wrapper": "tls" |
|||
} |
|||
], |
|||
"read_timeout": 30000000000, |
|||
"read_header_timeout": 30000000000, |
|||
"write_timeout": 30000000000, |
|||
"idle_timeout": 30000000000, |
|||
"max_header_bytes": 100000000, |
|||
"routes": [ |
|||
{ |
|||
"match": [ |
|||
{ |
|||
"host": [ |
|||
"foo.com" |
|||
] |
|||
} |
|||
], |
|||
"terminal": true |
|||
} |
|||
], |
|||
"strict_sni_host": true, |
|||
"experimental_http3": true, |
|||
"allow_h2c": true |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
Loading…
Reference in new issue