Compare commits
No commits in common. "b9ecf19980f0c7ad6772da154e638d6af843c3b0" and "f11c780fdc2e4e5298a64ef88d110dd392060a36" have entirely different histories.
b9ecf19980
...
f11c780fdc
21 changed files with 209 additions and 494 deletions
7
.github/workflows/ci.yml
vendored
7
.github/workflows/ci.yml
vendored
|
|
@ -13,7 +13,6 @@ on:
|
||||||
- 2.*
|
- 2.*
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GOFLAGS: '-tags=nobadger,nomysql,nopgx'
|
|
||||||
# https://github.com/actions/setup-go/issues/491
|
# https://github.com/actions/setup-go/issues/491
|
||||||
GOTOOLCHAIN: local
|
GOTOOLCHAIN: local
|
||||||
|
|
||||||
|
|
@ -111,7 +110,7 @@ jobs:
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
run: |
|
run: |
|
||||||
go build -trimpath -ldflags="-w -s" -v
|
go build -tags nobadger,nomysql,nopgx -trimpath -ldflags="-w -s" -v
|
||||||
|
|
||||||
- name: Smoke test Caddy
|
- name: Smoke test Caddy
|
||||||
working-directory: ./cmd/caddy
|
working-directory: ./cmd/caddy
|
||||||
|
|
@ -134,7 +133,7 @@ jobs:
|
||||||
# continue-on-error: true
|
# continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
# (go test -v -coverprofile=cover-profile.out -race ./... 2>&1) > test-results/test-result.out
|
# (go test -v -coverprofile=cover-profile.out -race ./... 2>&1) > test-results/test-result.out
|
||||||
go test -v -coverprofile="cover-profile.out" -short -race ./...
|
go test -tags nobadger,nomysql,nopgx -v -coverprofile="cover-profile.out" -short -race ./...
|
||||||
# echo "status=$?" >> $GITHUB_OUTPUT
|
# echo "status=$?" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
# Relevant step if we reinvestigate publishing test/coverage reports
|
# Relevant step if we reinvestigate publishing test/coverage reports
|
||||||
|
|
@ -191,7 +190,7 @@ jobs:
|
||||||
retries=3
|
retries=3
|
||||||
exit_code=0
|
exit_code=0
|
||||||
while ((retries > 0)); do
|
while ((retries > 0)); do
|
||||||
CGO_ENABLED=0 go test -p 1 -v ./...
|
CGO_ENABLED=0 go test -p 1 -tags nobadger,nomysql,nopgx -v ./...
|
||||||
exit_code=$?
|
exit_code=$?
|
||||||
if ((exit_code == 0)); then
|
if ((exit_code == 0)); then
|
||||||
break
|
break
|
||||||
|
|
|
||||||
6
.github/workflows/cross-build.yml
vendored
6
.github/workflows/cross-build.yml
vendored
|
|
@ -11,8 +11,6 @@ on:
|
||||||
- 2.*
|
- 2.*
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GOFLAGS: '-tags=nobadger,nomysql,nopgx'
|
|
||||||
CGO_ENABLED: '0'
|
|
||||||
# https://github.com/actions/setup-go/issues/491
|
# https://github.com/actions/setup-go/issues/491
|
||||||
GOTOOLCHAIN: local
|
GOTOOLCHAIN: local
|
||||||
|
|
||||||
|
|
@ -76,9 +74,11 @@ jobs:
|
||||||
|
|
||||||
- name: Run Build
|
- name: Run Build
|
||||||
env:
|
env:
|
||||||
|
CGO_ENABLED: 0
|
||||||
GOOS: ${{ matrix.goos }}
|
GOOS: ${{ matrix.goos }}
|
||||||
GOARCH: ${{ matrix.goos == 'aix' && 'ppc64' || 'amd64' }}
|
GOARCH: ${{ matrix.goos == 'aix' && 'ppc64' || 'amd64' }}
|
||||||
shell: bash
|
shell: bash
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
working-directory: ./cmd/caddy
|
working-directory: ./cmd/caddy
|
||||||
run: go build -trimpath -o caddy-"$GOOS"-$GOARCH 2> /dev/null
|
run: |
|
||||||
|
GOOS=$GOOS GOARCH=$GOARCH go build -tags=nobadger,nomysql,nopgx -trimpath -o caddy-"$GOOS"-$GOARCH 2> /dev/null
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,6 @@ version: "2"
|
||||||
run:
|
run:
|
||||||
issues-exit-code: 1
|
issues-exit-code: 1
|
||||||
tests: false
|
tests: false
|
||||||
build-tags:
|
|
||||||
- nobadger
|
|
||||||
- nomysql
|
|
||||||
- nopgx
|
|
||||||
output:
|
output:
|
||||||
formats:
|
formats:
|
||||||
text:
|
text:
|
||||||
|
|
|
||||||
|
|
@ -308,9 +308,9 @@ func (d *Dispenser) CountRemainingArgs() int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemainingArgs loads any more arguments (tokens on the same line)
|
// RemainingArgs loads any more arguments (tokens on the same line)
|
||||||
// into a slice of strings and returns them. Open curly brace tokens
|
// into a slice and returns them. Open curly brace tokens also indicate
|
||||||
// also indicate the end of arguments, and the curly brace is not
|
// the end of arguments, and the curly brace is not included in
|
||||||
// included in the return value nor is it loaded.
|
// the return value nor is it loaded.
|
||||||
func (d *Dispenser) RemainingArgs() []string {
|
func (d *Dispenser) RemainingArgs() []string {
|
||||||
var args []string
|
var args []string
|
||||||
for d.NextArg() {
|
for d.NextArg() {
|
||||||
|
|
@ -320,9 +320,9 @@ func (d *Dispenser) RemainingArgs() []string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemainingArgsRaw loads any more arguments (tokens on the same line,
|
// RemainingArgsRaw loads any more arguments (tokens on the same line,
|
||||||
// retaining quotes) into a slice of strings and returns them.
|
// retaining quotes) into a slice and returns them. Open curly brace
|
||||||
// Open curly brace tokens also indicate the end of arguments,
|
// tokens also indicate the end of arguments, and the curly brace is
|
||||||
// and the curly brace is not included in the return value nor is it loaded.
|
// not included in the return value nor is it loaded.
|
||||||
func (d *Dispenser) RemainingArgsRaw() []string {
|
func (d *Dispenser) RemainingArgsRaw() []string {
|
||||||
var args []string
|
var args []string
|
||||||
for d.NextArg() {
|
for d.NextArg() {
|
||||||
|
|
@ -331,18 +331,6 @@ func (d *Dispenser) RemainingArgsRaw() []string {
|
||||||
return args
|
return args
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemainingArgsAsTokens loads any more arguments (tokens on the same line)
|
|
||||||
// into a slice of Token-structs and returns them. Open curly brace tokens
|
|
||||||
// also indicate the end of arguments, and the curly brace is not included
|
|
||||||
// in the return value nor is it loaded.
|
|
||||||
func (d *Dispenser) RemainingArgsAsTokens() []Token {
|
|
||||||
var args []Token
|
|
||||||
for d.NextArg() {
|
|
||||||
args = append(args, d.Token())
|
|
||||||
}
|
|
||||||
return args
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewFromNextSegment returns a new dispenser with a copy of
|
// NewFromNextSegment returns a new dispenser with a copy of
|
||||||
// the tokens from the current token until the end of the
|
// the tokens from the current token until the end of the
|
||||||
// "directive" whether that be to the end of the line or
|
// "directive" whether that be to the end of the line or
|
||||||
|
|
|
||||||
|
|
@ -274,66 +274,6 @@ func TestDispenser_RemainingArgs(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDispenser_RemainingArgsAsTokens(t *testing.T) {
|
|
||||||
input := `dir1 arg1 arg2 arg3
|
|
||||||
dir2 arg4 arg5
|
|
||||||
dir3 arg6 { arg7
|
|
||||||
dir4`
|
|
||||||
d := NewTestDispenser(input)
|
|
||||||
|
|
||||||
d.Next() // dir1
|
|
||||||
|
|
||||||
args := d.RemainingArgsAsTokens()
|
|
||||||
|
|
||||||
tokenTexts := make([]string, 0, len(args))
|
|
||||||
for _, arg := range args {
|
|
||||||
tokenTexts = append(tokenTexts, arg.Text)
|
|
||||||
}
|
|
||||||
|
|
||||||
if expected := []string{"arg1", "arg2", "arg3"}; !reflect.DeepEqual(tokenTexts, expected) {
|
|
||||||
t.Errorf("RemainingArgsAsTokens(): Expected %v, got %v", expected, tokenTexts)
|
|
||||||
}
|
|
||||||
|
|
||||||
d.Next() // dir2
|
|
||||||
|
|
||||||
args = d.RemainingArgsAsTokens()
|
|
||||||
|
|
||||||
tokenTexts = tokenTexts[:0]
|
|
||||||
for _, arg := range args {
|
|
||||||
tokenTexts = append(tokenTexts, arg.Text)
|
|
||||||
}
|
|
||||||
|
|
||||||
if expected := []string{"arg4", "arg5"}; !reflect.DeepEqual(tokenTexts, expected) {
|
|
||||||
t.Errorf("RemainingArgsAsTokens(): Expected %v, got %v", expected, tokenTexts)
|
|
||||||
}
|
|
||||||
|
|
||||||
d.Next() // dir3
|
|
||||||
|
|
||||||
args = d.RemainingArgsAsTokens()
|
|
||||||
tokenTexts = tokenTexts[:0]
|
|
||||||
for _, arg := range args {
|
|
||||||
tokenTexts = append(tokenTexts, arg.Text)
|
|
||||||
}
|
|
||||||
|
|
||||||
if expected := []string{"arg6"}; !reflect.DeepEqual(tokenTexts, expected) {
|
|
||||||
t.Errorf("RemainingArgsAsTokens(): Expected %v, got %v", expected, tokenTexts)
|
|
||||||
}
|
|
||||||
|
|
||||||
d.Next() // {
|
|
||||||
d.Next() // arg7
|
|
||||||
d.Next() // dir4
|
|
||||||
|
|
||||||
args = d.RemainingArgsAsTokens()
|
|
||||||
tokenTexts = tokenTexts[:0]
|
|
||||||
for _, arg := range args {
|
|
||||||
tokenTexts = append(tokenTexts, arg.Text)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(args) != 0 {
|
|
||||||
t.Errorf("RemainingArgsAsTokens(): Expected %v, got %v", []string{}, tokenTexts)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDispenser_ArgErr_Err(t *testing.T) {
|
func TestDispenser_ArgErr_Err(t *testing.T) {
|
||||||
input := `dir1 {
|
input := `dir1 {
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -379,23 +379,28 @@ func (p *parser) doImport(nesting int) error {
|
||||||
if len(blockTokens) > 0 {
|
if len(blockTokens) > 0 {
|
||||||
// use such tokens to create a new dispenser, and then use it to parse each block
|
// use such tokens to create a new dispenser, and then use it to parse each block
|
||||||
bd := NewDispenser(blockTokens)
|
bd := NewDispenser(blockTokens)
|
||||||
|
|
||||||
// one iteration processes one sub-block inside the import
|
|
||||||
for bd.Next() {
|
for bd.Next() {
|
||||||
currentMappingKey := bd.Val()
|
// see if we can grab a key
|
||||||
|
var currentMappingKey string
|
||||||
if currentMappingKey == "{" {
|
if bd.Val() == "{" {
|
||||||
return p.Err("anonymous blocks are not supported")
|
return p.Err("anonymous blocks are not supported")
|
||||||
}
|
}
|
||||||
|
currentMappingKey = bd.Val()
|
||||||
// load up all arguments (if there even are any)
|
currentMappingTokens := []Token{}
|
||||||
currentMappingTokens := bd.RemainingArgsAsTokens()
|
// read all args until end of line / {
|
||||||
|
if bd.NextArg() {
|
||||||
// load up the entire block
|
currentMappingTokens = append(currentMappingTokens, bd.Token())
|
||||||
|
for bd.NextArg() {
|
||||||
|
currentMappingTokens = append(currentMappingTokens, bd.Token())
|
||||||
|
}
|
||||||
|
// TODO(elee1766): we don't enter another mapping here because it's annoying to extract the { and } properly.
|
||||||
|
// maybe someone can do that in the future
|
||||||
|
} else {
|
||||||
|
// attempt to enter a block and add tokens to the currentMappingTokens
|
||||||
for mappingNesting := bd.Nesting(); bd.NextBlock(mappingNesting); {
|
for mappingNesting := bd.Nesting(); bd.NextBlock(mappingNesting); {
|
||||||
currentMappingTokens = append(currentMappingTokens, bd.Token())
|
currentMappingTokens = append(currentMappingTokens, bd.Token())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
blockMapping[currentMappingKey] = currentMappingTokens
|
blockMapping[currentMappingKey] = currentMappingTokens
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -885,51 +884,6 @@ func TestRejectsGlobalMatcher(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRejectAnonymousImportBlock(t *testing.T) {
|
|
||||||
p := testParser(`
|
|
||||||
(site) {
|
|
||||||
http://{args[0]} https://{args[0]} {
|
|
||||||
{block}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
import site test.domain {
|
|
||||||
{
|
|
||||||
header_up Host {host}
|
|
||||||
header_up X-Real-IP {remote_host}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
_, err := p.parseAll()
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("Expected an error, but got nil")
|
|
||||||
}
|
|
||||||
expected := "anonymous blocks are not supported"
|
|
||||||
if !strings.HasPrefix(err.Error(), "anonymous blocks are not supported") {
|
|
||||||
t.Errorf("Expected error to start with '%s' but got '%v'", expected, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAcceptSiteImportWithBraces(t *testing.T) {
|
|
||||||
p := testParser(`
|
|
||||||
(site) {
|
|
||||||
http://{args[0]} https://{args[0]} {
|
|
||||||
{block}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
import site test.domain {
|
|
||||||
reverse_proxy http://192.168.1.1:8080 {
|
|
||||||
header_up Host {host}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
_, err := p.parseAll()
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Expected error to be nil but got '%v'", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testParser(input string) parser {
|
func testParser(input string) parser {
|
||||||
return parser{Dispenser: NewTestDispenser(input)}
|
return parser{Dispenser: NewTestDispenser(input)}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -564,23 +564,22 @@ func fillInGlobalACMEDefaults(issuer certmagic.Issuer, options map[string]any) e
|
||||||
if globalACMECARoot != nil && !slices.Contains(acmeIssuer.TrustedRootsPEMFiles, globalACMECARoot.(string)) {
|
if globalACMECARoot != nil && !slices.Contains(acmeIssuer.TrustedRootsPEMFiles, globalACMECARoot.(string)) {
|
||||||
acmeIssuer.TrustedRootsPEMFiles = append(acmeIssuer.TrustedRootsPEMFiles, globalACMECARoot.(string))
|
acmeIssuer.TrustedRootsPEMFiles = append(acmeIssuer.TrustedRootsPEMFiles, globalACMECARoot.(string))
|
||||||
}
|
}
|
||||||
if globalACMEDNSok {
|
if globalACMEDNSok && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.DNS == nil) {
|
||||||
globalDNS := options["dns"]
|
if globalACMEDNS == nil {
|
||||||
if globalDNS != nil {
|
globalACMEDNS = options["dns"]
|
||||||
// If global `dns` is set, do NOT set provider in issuer, just set empty dns config
|
if globalACMEDNS == nil {
|
||||||
|
return fmt.Errorf("acme_dns specified without DNS provider config, but no provider specified with 'dns' global option")
|
||||||
|
}
|
||||||
|
}
|
||||||
acmeIssuer.Challenges = &caddytls.ChallengesConfig{
|
acmeIssuer.Challenges = &caddytls.ChallengesConfig{
|
||||||
DNS: &caddytls.DNSChallengeConfig{},
|
DNS: new(caddytls.DNSChallengeConfig),
|
||||||
}
|
}
|
||||||
} else if globalACMEDNS != nil {
|
} else if globalACMEDNS != nil {
|
||||||
// Set a global DNS provider if `acme_dns` is set and `dns` is NOT set
|
|
||||||
acmeIssuer.Challenges = &caddytls.ChallengesConfig{
|
acmeIssuer.Challenges = &caddytls.ChallengesConfig{
|
||||||
DNS: &caddytls.DNSChallengeConfig{
|
DNS: &caddytls.DNSChallengeConfig{
|
||||||
ProviderRaw: caddyconfig.JSONModuleObject(globalACMEDNS, "name", globalACMEDNS.(caddy.Module).CaddyModule().ID.Name(), nil),
|
ProviderRaw: caddyconfig.JSONModuleObject(globalACMEDNS, "name", globalACMEDNS.(caddy.Module).CaddyModule().ID.Name(), nil),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return fmt.Errorf("acme_dns specified without DNS provider config, but no provider specified with 'dns' global option")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if globalACMEEAB != nil && acmeIssuer.ExternalAccount == nil {
|
if globalACMEEAB != nil && acmeIssuer.ExternalAccount == nil {
|
||||||
acmeIssuer.ExternalAccount = globalACMEEAB.(*acme.EAB)
|
acmeIssuer.ExternalAccount = globalACMEEAB.(*acme.EAB)
|
||||||
|
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
{
|
|
||||||
acme_dns mock foo
|
|
||||||
}
|
|
||||||
|
|
||||||
example.com {
|
|
||||||
respond "Hello World"
|
|
||||||
}
|
|
||||||
----------
|
|
||||||
{
|
|
||||||
"apps": {
|
|
||||||
"http": {
|
|
||||||
"servers": {
|
|
||||||
"srv0": {
|
|
||||||
"listen": [
|
|
||||||
":443"
|
|
||||||
],
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"host": [
|
|
||||||
"example.com"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "subroute",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"body": "Hello World",
|
|
||||||
"handler": "static_response"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"terminal": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tls": {
|
|
||||||
"automation": {
|
|
||||||
"policies": [
|
|
||||||
{
|
|
||||||
"issuers": [
|
|
||||||
{
|
|
||||||
"challenges": {
|
|
||||||
"dns": {
|
|
||||||
"provider": {
|
|
||||||
"name": "mock"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"module": "acme"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
{
|
|
||||||
acme_dns
|
|
||||||
}
|
|
||||||
|
|
||||||
example.com {
|
|
||||||
respond "Hello World"
|
|
||||||
}
|
|
||||||
----------
|
|
||||||
acme_dns specified without DNS provider config, but no provider specified with 'dns' global option
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
(site) {
|
|
||||||
http://{args[0]} https://{args[0]} {
|
|
||||||
{block}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
import site test.domain {
|
|
||||||
{
|
|
||||||
header_up Host {host}
|
|
||||||
header_up X-Real-IP {remote_host}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
----------
|
|
||||||
anonymous blocks are not supported
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
(site) {
|
|
||||||
https://{args[0]} {
|
|
||||||
{block}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
import site test.domain {
|
|
||||||
reverse_proxy http://192.168.1.1:8080 {
|
|
||||||
header_up Host {host}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
----------
|
|
||||||
{
|
|
||||||
"apps": {
|
|
||||||
"http": {
|
|
||||||
"servers": {
|
|
||||||
"srv0": {
|
|
||||||
"listen": [
|
|
||||||
":443"
|
|
||||||
],
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"host": [
|
|
||||||
"test.domain"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "subroute",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "reverse_proxy",
|
|
||||||
"headers": {
|
|
||||||
"request": {
|
|
||||||
"set": {
|
|
||||||
"Host": [
|
|
||||||
"{http.request.host}"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"upstreams": [
|
|
||||||
{
|
|
||||||
"dial": "192.168.1.1:8080"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"terminal": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
2
go.mod
2
go.mod
|
|
@ -8,7 +8,7 @@ require (
|
||||||
github.com/Masterminds/sprig/v3 v3.3.0
|
github.com/Masterminds/sprig/v3 v3.3.0
|
||||||
github.com/alecthomas/chroma/v2 v2.20.0
|
github.com/alecthomas/chroma/v2 v2.20.0
|
||||||
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b
|
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b
|
||||||
github.com/caddyserver/certmagic v0.24.0
|
github.com/caddyserver/certmagic v0.23.0
|
||||||
github.com/caddyserver/zerossl v0.1.3
|
github.com/caddyserver/zerossl v0.1.3
|
||||||
github.com/cloudflare/circl v1.6.1
|
github.com/cloudflare/circl v1.6.1
|
||||||
github.com/dustin/go-humanize v1.0.1
|
github.com/dustin/go-humanize v1.0.1
|
||||||
|
|
|
||||||
4
go.sum
4
go.sum
|
|
@ -91,8 +91,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
||||||
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||||
github.com/caddyserver/certmagic v0.24.0 h1:EfXTWpxHAUKgDfOj6MHImJN8Jm4AMFfMT6ITuKhrDF0=
|
github.com/caddyserver/certmagic v0.23.0 h1:CfpZ/50jMfG4+1J/u2LV6piJq4HOfO6ppOnOf7DkFEU=
|
||||||
github.com/caddyserver/certmagic v0.24.0/go.mod h1:xPT7dC1DuHHnS2yuEQCEyks+b89sUkMENh8dJF+InLE=
|
github.com/caddyserver/certmagic v0.23.0/go.mod h1:9mEZIWqqWoI+Gf+4Trh04MOVPD0tGSxtqsxg87hAIH4=
|
||||||
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
|
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
|
||||||
github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
|
github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
|
||||||
github.com/ccoveille/go-safecast v1.6.1 h1:Nb9WMDR8PqhnKCVs2sCB+OqhohwO5qaXtCviZkIff5Q=
|
github.com/ccoveille/go-safecast v1.6.1 h1:Nb9WMDR8PqhnKCVs2sCB+OqhohwO5qaXtCviZkIff5Q=
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import (
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
|
"golang.org/x/net/http2/h2c"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddyevents"
|
"github.com/caddyserver/caddy/v2/modules/caddyevents"
|
||||||
|
|
@ -170,15 +171,13 @@ func (App) CaddyModule() caddy.ModuleInfo {
|
||||||
// Provision sets up the app.
|
// Provision sets up the app.
|
||||||
func (app *App) Provision(ctx caddy.Context) error {
|
func (app *App) Provision(ctx caddy.Context) error {
|
||||||
// store some references
|
// store some references
|
||||||
app.logger = ctx.Logger()
|
|
||||||
app.ctx = ctx
|
|
||||||
|
|
||||||
// provision TLS and events apps
|
|
||||||
tlsAppIface, err := ctx.App("tls")
|
tlsAppIface, err := ctx.App("tls")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("getting tls app: %v", err)
|
return fmt.Errorf("getting tls app: %v", err)
|
||||||
}
|
}
|
||||||
app.tlsApp = tlsAppIface.(*caddytls.TLS)
|
app.tlsApp = tlsAppIface.(*caddytls.TLS)
|
||||||
|
app.ctx = ctx
|
||||||
|
app.logger = ctx.Logger()
|
||||||
|
|
||||||
eventsAppIface, err := ctx.App("events")
|
eventsAppIface, err := ctx.App("events")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -237,6 +236,15 @@ func (app *App) Provision(ctx caddy.Context) error {
|
||||||
for _, srvProtocol := range srv.Protocols {
|
for _, srvProtocol := range srv.Protocols {
|
||||||
srvProtocolsUnique[srvProtocol] = struct{}{}
|
srvProtocolsUnique[srvProtocol] = struct{}{}
|
||||||
}
|
}
|
||||||
|
_, h1ok := srvProtocolsUnique["h1"]
|
||||||
|
_, h2ok := srvProtocolsUnique["h2"]
|
||||||
|
_, h2cok := srvProtocolsUnique["h2c"]
|
||||||
|
|
||||||
|
// the Go standard library does not let us serve only HTTP/2 using
|
||||||
|
// http.Server; we would probably need to write our own server
|
||||||
|
if !h1ok && (h2ok || h2cok) {
|
||||||
|
return fmt.Errorf("server %s: cannot enable HTTP/2 or H2C without enabling HTTP/1.1; add h1 to protocols or remove h2/h2c", srvName)
|
||||||
|
}
|
||||||
|
|
||||||
if srv.ListenProtocols != nil {
|
if srv.ListenProtocols != nil {
|
||||||
if len(srv.ListenProtocols) != len(srv.Listen) {
|
if len(srv.ListenProtocols) != len(srv.Listen) {
|
||||||
|
|
@ -270,6 +278,19 @@ func (app *App) Provision(ctx caddy.Context) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lnProtocolsIncludeUnique := map[string]struct{}{}
|
||||||
|
for _, lnProtocol := range lnProtocolsInclude {
|
||||||
|
lnProtocolsIncludeUnique[lnProtocol] = struct{}{}
|
||||||
|
}
|
||||||
|
_, h1ok := lnProtocolsIncludeUnique["h1"]
|
||||||
|
_, h2ok := lnProtocolsIncludeUnique["h2"]
|
||||||
|
_, h2cok := lnProtocolsIncludeUnique["h2c"]
|
||||||
|
|
||||||
|
// check if any listener protocols contain h2 or h2c without h1
|
||||||
|
if !h1ok && (h2ok || h2cok) {
|
||||||
|
return fmt.Errorf("server %s, listener %d: cannot enable HTTP/2 or H2C without enabling HTTP/1.1; add h1 to protocols or remove h2/h2c", srvName, i)
|
||||||
|
}
|
||||||
|
|
||||||
srv.ListenProtocols[i] = lnProtocolsInclude
|
srv.ListenProtocols[i] = lnProtocolsInclude
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -427,25 +448,6 @@ func (app *App) Validate() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeTLSALPN(srv *Server, target string) {
|
|
||||||
for _, cp := range srv.TLSConnPolicies {
|
|
||||||
// the TLSConfig was already provisioned, so... manually remove it
|
|
||||||
for i, np := range cp.TLSConfig.NextProtos {
|
|
||||||
if np == target {
|
|
||||||
cp.TLSConfig.NextProtos = append(cp.TLSConfig.NextProtos[:i], cp.TLSConfig.NextProtos[i+1:]...)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// remove it from the parent connection policy too, just to keep things tidy
|
|
||||||
for i, alpn := range cp.ALPN {
|
|
||||||
if alpn == target {
|
|
||||||
cp.ALPN = append(cp.ALPN[:i], cp.ALPN[i+1:]...)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start runs the app. It finishes automatic HTTPS if enabled,
|
// Start runs the app. It finishes automatic HTTPS if enabled,
|
||||||
// including management of certificates.
|
// including management of certificates.
|
||||||
func (app *App) Start() error {
|
func (app *App) Start() error {
|
||||||
|
|
@ -464,37 +466,32 @@ func (app *App) Start() error {
|
||||||
MaxHeaderBytes: srv.MaxHeaderBytes,
|
MaxHeaderBytes: srv.MaxHeaderBytes,
|
||||||
Handler: srv,
|
Handler: srv,
|
||||||
ErrorLog: serverLogger,
|
ErrorLog: serverLogger,
|
||||||
Protocols: new(http.Protocols),
|
|
||||||
ConnContext: func(ctx context.Context, c net.Conn) context.Context {
|
ConnContext: func(ctx context.Context, c net.Conn) context.Context {
|
||||||
return context.WithValue(ctx, ConnCtxKey, c)
|
return context.WithValue(ctx, ConnCtxKey, c)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
h2server := new(http2.Server)
|
||||||
|
|
||||||
// disable HTTP/2, which we enabled by default during provisioning
|
// disable HTTP/2, which we enabled by default during provisioning
|
||||||
if !srv.protocol("h2") {
|
if !srv.protocol("h2") {
|
||||||
srv.server.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler))
|
srv.server.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler))
|
||||||
removeTLSALPN(srv, "h2")
|
for _, cp := range srv.TLSConnPolicies {
|
||||||
|
// the TLSConfig was already provisioned, so... manually remove it
|
||||||
|
for i, np := range cp.TLSConfig.NextProtos {
|
||||||
|
if np == "h2" {
|
||||||
|
cp.TLSConfig.NextProtos = append(cp.TLSConfig.NextProtos[:i], cp.TLSConfig.NextProtos[i+1:]...)
|
||||||
|
break
|
||||||
}
|
}
|
||||||
if !srv.protocol("h1") {
|
|
||||||
removeTLSALPN(srv, "http/1.1")
|
|
||||||
}
|
}
|
||||||
|
// remove it from the parent connection policy too, just to keep things tidy
|
||||||
// configure the http versions the server will serve
|
for i, alpn := range cp.ALPN {
|
||||||
if srv.protocol("h1") {
|
if alpn == "h2" {
|
||||||
srv.server.Protocols.SetHTTP1(true)
|
cp.ALPN = append(cp.ALPN[:i], cp.ALPN[i+1:]...)
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if srv.protocol("h2") || srv.protocol("h2c") {
|
}
|
||||||
// skip setting h2 because if NextProtos is present, it's list of alpn versions will take precedence.
|
} else {
|
||||||
// it will always be present because http2.ConfigureServer will populate that field
|
|
||||||
// enabling h2c because some listener wrapper will wrap the connection that is no longer *tls.Conn
|
|
||||||
// However, we need to handle the case that if the connection is h2c but h2c is not enabled. We identify
|
|
||||||
// this type of connection by checking if it's behind a TLS listener wrapper or if it implements tls.ConnectionState.
|
|
||||||
srv.server.Protocols.SetUnencryptedHTTP2(true)
|
|
||||||
// when h2c is enabled but h2 disabled, we already removed h2 from NextProtos
|
|
||||||
// the handshake will never succeed with h2
|
|
||||||
// http2.ConfigureServer will enable the server to handle both h2 and h2c
|
|
||||||
h2server := new(http2.Server)
|
|
||||||
//nolint:errcheck
|
//nolint:errcheck
|
||||||
http2.ConfigureServer(srv.server, h2server)
|
http2.ConfigureServer(srv.server, h2server)
|
||||||
}
|
}
|
||||||
|
|
@ -504,6 +501,11 @@ func (app *App) Start() error {
|
||||||
tlsCfg := srv.TLSConnPolicies.TLSConfig(app.ctx)
|
tlsCfg := srv.TLSConnPolicies.TLSConfig(app.ctx)
|
||||||
srv.configureServer(srv.server)
|
srv.configureServer(srv.server)
|
||||||
|
|
||||||
|
// enable H2C if configured
|
||||||
|
if srv.protocol("h2c") {
|
||||||
|
srv.server.Handler = h2c.NewHandler(srv, h2server)
|
||||||
|
}
|
||||||
|
|
||||||
for lnIndex, lnAddr := range srv.Listen {
|
for lnIndex, lnAddr := range srv.Listen {
|
||||||
listenAddr, err := caddy.ParseNetworkAddress(lnAddr)
|
listenAddr, err := caddy.ParseNetworkAddress(lnAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -568,13 +570,15 @@ func (app *App) Start() error {
|
||||||
ln = srv.listenerWrappers[i].WrapListener(ln)
|
ln = srv.listenerWrappers[i].WrapListener(ln)
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if the connection is h2c
|
// handle http2 if use tls listener wrapper
|
||||||
ln = &http2Listener{
|
if h2ok {
|
||||||
useTLS: useTLS,
|
http2lnWrapper := &http2Listener{
|
||||||
useH1: h1ok,
|
|
||||||
useH2: h2ok || h2cok,
|
|
||||||
Listener: ln,
|
Listener: ln,
|
||||||
logger: app.logger,
|
server: srv.server,
|
||||||
|
h2server: h2server,
|
||||||
|
}
|
||||||
|
srv.h2listeners = append(srv.h2listeners, http2lnWrapper)
|
||||||
|
ln = http2lnWrapper
|
||||||
}
|
}
|
||||||
|
|
||||||
// if binding to port 0, the OS chooses a port for us;
|
// if binding to port 0, the OS chooses a port for us;
|
||||||
|
|
@ -592,9 +596,12 @@ func (app *App) Start() error {
|
||||||
|
|
||||||
srv.listeners = append(srv.listeners, ln)
|
srv.listeners = append(srv.listeners, ln)
|
||||||
|
|
||||||
|
// enable HTTP/1 if configured
|
||||||
|
if h1ok {
|
||||||
//nolint:errcheck
|
//nolint:errcheck
|
||||||
go srv.server.Serve(ln)
|
go srv.server.Serve(ln)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if h2ok && !useTLS {
|
if h2ok && !useTLS {
|
||||||
// Can only serve h2 with TLS enabled
|
// Can only serve h2 with TLS enabled
|
||||||
|
|
@ -749,12 +756,25 @@ func (app *App) Stop() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
stopH2Listener := func(server *Server) {
|
||||||
|
defer finishedShutdown.Done()
|
||||||
|
startedShutdown.Done()
|
||||||
|
|
||||||
|
for i, s := range server.h2listeners {
|
||||||
|
if err := s.Shutdown(ctx); err != nil {
|
||||||
|
app.logger.Error("http2 listener shutdown",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.Int("index", i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, server := range app.Servers {
|
for _, server := range app.Servers {
|
||||||
startedShutdown.Add(2)
|
startedShutdown.Add(3)
|
||||||
finishedShutdown.Add(2)
|
finishedShutdown.Add(3)
|
||||||
go stopServer(server)
|
go stopServer(server)
|
||||||
go stopH3Server(server)
|
go stopH3Server(server)
|
||||||
|
go stopH2Listener(server)
|
||||||
}
|
}
|
||||||
|
|
||||||
// block until all the goroutines have been run by the scheduler;
|
// block until all the goroutines have been run by the scheduler;
|
||||||
|
|
|
||||||
|
|
@ -167,8 +167,6 @@ type FileServer struct {
|
||||||
// If set, file Etags will be read from sidecar files
|
// If set, file Etags will be read from sidecar files
|
||||||
// with any of these suffixes, instead of generating
|
// with any of these suffixes, instead of generating
|
||||||
// our own Etag.
|
// our own Etag.
|
||||||
// Keep in mind that the Etag values in the files have to be quoted as per RFC7232.
|
|
||||||
// See https://datatracker.ietf.org/doc/html/rfc7232#section-2.3 for a few examples.
|
|
||||||
EtagFileExtensions []string `json:"etag_file_extensions,omitempty"`
|
EtagFileExtensions []string `json:"etag_file_extensions,omitempty"`
|
||||||
|
|
||||||
fsmap caddy.FileSystems
|
fsmap caddy.FileSystems
|
||||||
|
|
@ -457,14 +455,7 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
respHeader.Set("Content-Encoding", ae)
|
respHeader.Set("Content-Encoding", ae)
|
||||||
|
respHeader.Del("Accept-Ranges")
|
||||||
// stdlib won't set Content-Length if Content-Encoding is set.
|
|
||||||
// set Range header if it's not present will force Content-Length to be set
|
|
||||||
if r.Header.Get("Range") == "" {
|
|
||||||
r.Header.Set("Range", "bytes=0-")
|
|
||||||
// remove this header, because it is not part of the request
|
|
||||||
defer r.Header.Del("Range")
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to get the etag from pre computed files if an etag suffix list was provided
|
// try to get the etag from pre computed files if an etag suffix list was provided
|
||||||
if etag == "" && fsrv.EtagFileExtensions != nil {
|
if etag == "" && fsrv.EtagFileExtensions != nil {
|
||||||
|
|
|
||||||
|
|
@ -1,110 +1,102 @@
|
||||||
package caddyhttp
|
package caddyhttp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"io"
|
weakrand "math/rand"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type connectionStater interface {
|
// http2Listener wraps the listener to solve the following problems:
|
||||||
|
// 1. server h2 natively without using h2c hack when listener handles tls connection but
|
||||||
|
// don't return *tls.Conn
|
||||||
|
// 2. graceful shutdown. the shutdown logic is copied from stdlib http.Server, it's an extra maintenance burden but
|
||||||
|
// whatever, the shutdown logic maybe extracted to be used with h2c graceful shutdown. http2.Server supports graceful shutdown
|
||||||
|
// sending GO_AWAY frame to connected clients, but doesn't track connection status. It requires explicit call of http2.ConfigureServer
|
||||||
|
type http2Listener struct {
|
||||||
|
cnt uint64
|
||||||
|
net.Listener
|
||||||
|
server *http.Server
|
||||||
|
h2server *http2.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
type connectionStateConn interface {
|
||||||
|
net.Conn
|
||||||
ConnectionState() tls.ConnectionState
|
ConnectionState() tls.ConnectionState
|
||||||
}
|
}
|
||||||
|
|
||||||
// http2Listener wraps the listener to solve the following problems:
|
|
||||||
// 1. prevent genuine h2c connections from succeeding if h2c is not enabled
|
|
||||||
// and the connection doesn't implment connectionStater or the resulting NegotiatedProtocol
|
|
||||||
// isn't http2.
|
|
||||||
// This does allow a connection to pass as tls enabled even if it's not, listener wrappers
|
|
||||||
// can do this.
|
|
||||||
// 2. After wrapping the connection doesn't implement connectionStater, emit a warning so that listener
|
|
||||||
// wrapper authors will hopefully implement it.
|
|
||||||
// 3. check if the connection matches a specific http version. h2/h2c has a distinct preface.
|
|
||||||
type http2Listener struct {
|
|
||||||
useTLS bool
|
|
||||||
useH1 bool
|
|
||||||
useH2 bool
|
|
||||||
net.Listener
|
|
||||||
logger *zap.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *http2Listener) Accept() (net.Conn, error) {
|
func (h *http2Listener) Accept() (net.Conn, error) {
|
||||||
|
for {
|
||||||
conn, err := h.Listener.Accept()
|
conn, err := h.Listener.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, isConnectionStater := conn.(connectionStater)
|
if csc, ok := conn.(connectionStateConn); ok {
|
||||||
// emit a warning
|
// *tls.Conn will return empty string because it's only populated after handshake is complete
|
||||||
if h.useTLS && !isConnectionStater {
|
if csc.ConnectionState().NegotiatedProtocol == http2.NextProtoTLS {
|
||||||
h.logger.Warn("tls is enabled, but listener wrapper returns a connection that doesn't implement connectionStater")
|
go h.serveHttp2(csc)
|
||||||
} else if !h.useTLS && isConnectionStater {
|
continue
|
||||||
h.logger.Warn("tls is disabled, but listener wrapper returns a connection that implements connectionStater")
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if both h1 and h2 are enabled, we don't need to check the preface
|
|
||||||
if h.useH1 && h.useH2 {
|
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// impossible both are false, either useH1 or useH2 must be true,
|
|
||||||
// or else the listener wouldn't be created
|
|
||||||
h2Conn := &http2Conn{
|
|
||||||
h2Expected: h.useH2,
|
|
||||||
Conn: conn,
|
|
||||||
}
|
|
||||||
if isConnectionStater {
|
|
||||||
return http2StateConn{h2Conn}, nil
|
|
||||||
}
|
|
||||||
return h2Conn, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type http2StateConn struct {
|
func (h *http2Listener) serveHttp2(csc connectionStateConn) {
|
||||||
*http2Conn
|
atomic.AddUint64(&h.cnt, 1)
|
||||||
|
h.runHook(csc, http.StateNew)
|
||||||
|
defer func() {
|
||||||
|
csc.Close()
|
||||||
|
atomic.AddUint64(&h.cnt, ^uint64(0))
|
||||||
|
h.runHook(csc, http.StateClosed)
|
||||||
|
}()
|
||||||
|
h.h2server.ServeConn(csc, &http2.ServeConnOpts{
|
||||||
|
Context: h.server.ConnContext(context.Background(), csc),
|
||||||
|
BaseConfig: h.server,
|
||||||
|
Handler: h.server.Handler,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conn http2StateConn) ConnectionState() tls.ConnectionState {
|
const shutdownPollIntervalMax = 500 * time.Millisecond
|
||||||
return conn.Conn.(connectionStater).ConnectionState()
|
|
||||||
|
func (h *http2Listener) Shutdown(ctx context.Context) error {
|
||||||
|
pollIntervalBase := time.Millisecond
|
||||||
|
nextPollInterval := func() time.Duration {
|
||||||
|
// Add 10% jitter.
|
||||||
|
//nolint:gosec
|
||||||
|
interval := pollIntervalBase + time.Duration(weakrand.Intn(int(pollIntervalBase/10)))
|
||||||
|
// Double and clamp for next time.
|
||||||
|
pollIntervalBase *= 2
|
||||||
|
if pollIntervalBase > shutdownPollIntervalMax {
|
||||||
|
pollIntervalBase = shutdownPollIntervalMax
|
||||||
|
}
|
||||||
|
return interval
|
||||||
}
|
}
|
||||||
|
|
||||||
type http2Conn struct {
|
timer := time.NewTimer(nextPollInterval())
|
||||||
// current index where the preface should match,
|
defer timer.Stop()
|
||||||
// no matching is done if idx is >= len(http2.ClientPreface)
|
for {
|
||||||
idx int
|
if atomic.LoadUint64(&h.cnt) == 0 {
|
||||||
// whether the connection is expected to be h2/h2c
|
return nil
|
||||||
h2Expected bool
|
}
|
||||||
// log if one such connection is detected
|
select {
|
||||||
logger *zap.Logger
|
case <-ctx.Done():
|
||||||
net.Conn
|
return ctx.Err()
|
||||||
|
case <-timer.C:
|
||||||
|
timer.Reset(nextPollInterval())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *http2Conn) Read(p []byte) (int, error) {
|
func (h *http2Listener) runHook(conn net.Conn, state http.ConnState) {
|
||||||
if c.idx >= len(http2.ClientPreface) {
|
if h.server.ConnState != nil {
|
||||||
return c.Conn.Read(p)
|
h.server.ConnState(conn, state)
|
||||||
}
|
|
||||||
n, err := c.Conn.Read(p)
|
|
||||||
for i := range n {
|
|
||||||
// first mismatch
|
|
||||||
if p[i] != http2.ClientPreface[c.idx] {
|
|
||||||
// close the connection if h2 is expected
|
|
||||||
if c.h2Expected {
|
|
||||||
c.logger.Debug("h1 connection detected, but h1 is not enabled")
|
|
||||||
_ = c.Conn.Close()
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
// no need to continue matching anymore
|
|
||||||
c.idx = len(http2.ClientPreface)
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
c.idx++
|
|
||||||
// matching complete
|
|
||||||
if c.idx == len(http2.ClientPreface) && !c.h2Expected {
|
|
||||||
c.logger.Debug("h2/h2c connection detected, but h2/h2c is not enabled")
|
|
||||||
_ = c.Conn.Close()
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -428,19 +428,7 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e
|
||||||
}
|
}
|
||||||
|
|
||||||
if h.KeepAlive != nil {
|
if h.KeepAlive != nil {
|
||||||
// according to https://pkg.go.dev/net#Dialer.KeepAliveConfig,
|
|
||||||
// KeepAlive is ignored if KeepAliveConfig.Enable is true.
|
|
||||||
// If configured to 0, a system-dependent default is used.
|
|
||||||
// To disable tcp keepalive, choose a negative value,
|
|
||||||
// so KeepAliveConfig.Enable is false and KeepAlive is negative.
|
|
||||||
|
|
||||||
// This is different from http keepalive where a tcp connection
|
|
||||||
// can transfer multiple http requests/responses.
|
|
||||||
dialer.KeepAlive = time.Duration(h.KeepAlive.ProbeInterval)
|
dialer.KeepAlive = time.Duration(h.KeepAlive.ProbeInterval)
|
||||||
dialer.KeepAliveConfig = net.KeepAliveConfig{
|
|
||||||
Enable: h.KeepAlive.ProbeInterval > 0,
|
|
||||||
Interval: time.Duration(h.KeepAlive.ProbeInterval),
|
|
||||||
}
|
|
||||||
if h.KeepAlive.Enabled != nil {
|
if h.KeepAlive.Enabled != nil {
|
||||||
rt.DisableKeepAlives = !*h.KeepAlive.Enabled
|
rt.DisableKeepAlives = !*h.KeepAlive.Enabled
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -248,6 +248,7 @@ type Server struct {
|
||||||
|
|
||||||
server *http.Server
|
server *http.Server
|
||||||
h3server *http3.Server
|
h3server *http3.Server
|
||||||
|
h2listeners []*http2Listener
|
||||||
addresses []caddy.NetworkAddress
|
addresses []caddy.NetworkAddress
|
||||||
|
|
||||||
trustedProxies IPRangeSource
|
trustedProxies IPRangeSource
|
||||||
|
|
@ -265,11 +266,11 @@ type Server struct {
|
||||||
// ServeHTTP is the entry point for all HTTP requests.
|
// ServeHTTP is the entry point for all HTTP requests.
|
||||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
// If there are listener wrappers that process tls connections but don't return a *tls.Conn, this field will be nil.
|
// If there are listener wrappers that process tls connections but don't return a *tls.Conn, this field will be nil.
|
||||||
// TODO: Scheduled to be removed later because https://github.com/golang/go/pull/56110 has been merged.
|
// TODO: Can be removed if https://github.com/golang/go/pull/56110 is ever merged.
|
||||||
if r.TLS == nil {
|
if r.TLS == nil {
|
||||||
// not all requests have a conn (like virtual requests) - see #5698
|
// not all requests have a conn (like virtual requests) - see #5698
|
||||||
if conn, ok := r.Context().Value(ConnCtxKey).(net.Conn); ok {
|
if conn, ok := r.Context().Value(ConnCtxKey).(net.Conn); ok {
|
||||||
if csc, ok := conn.(connectionStater); ok {
|
if csc, ok := conn.(connectionStateConn); ok {
|
||||||
r.TLS = new(tls.ConnectionState)
|
r.TLS = new(tls.ConnectionState)
|
||||||
*r.TLS = csc.ConnectionState()
|
*r.TLS = csc.ConnectionState()
|
||||||
}
|
}
|
||||||
|
|
@ -1082,8 +1083,6 @@ const (
|
||||||
OriginalRequestCtxKey caddy.CtxKey = "original_request"
|
OriginalRequestCtxKey caddy.CtxKey = "original_request"
|
||||||
|
|
||||||
// For referencing underlying net.Conn
|
// For referencing underlying net.Conn
|
||||||
// This will eventually be deprecated and not used. To refer to the underlying connection, implement a middleware plugin
|
|
||||||
// that RegisterConnContext during provisioning.
|
|
||||||
ConnCtxKey caddy.CtxKey = "conn"
|
ConnCtxKey caddy.CtxKey = "conn"
|
||||||
|
|
||||||
// For tracking whether the client is a trusted proxy
|
// For tracking whether the client is a trusted proxy
|
||||||
|
|
|
||||||
|
|
@ -139,7 +139,7 @@ func (cp ConnectionPolicies) TLSConfig(ctx caddy.Context) *tls.Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsCfg := &tls.Config{
|
tlsCfg := &tls.Config{
|
||||||
MinVersion: tls.VersionTLS10,
|
MinVersion: tls.VersionTLS12,
|
||||||
GetConfigForClient: getConfigForClient,
|
GetConfigForClient: getConfigForClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -117,10 +117,7 @@ var defaultCurves = []tls.CurveID{
|
||||||
}
|
}
|
||||||
|
|
||||||
// SupportedProtocols is a map of supported protocols.
|
// SupportedProtocols is a map of supported protocols.
|
||||||
// Note that HTTP/2 only supports TLS 1.2 and higher.
|
|
||||||
var SupportedProtocols = map[string]uint16{
|
var SupportedProtocols = map[string]uint16{
|
||||||
"tls1.0": tls.VersionTLS10,
|
|
||||||
"tls1.1": tls.VersionTLS11,
|
|
||||||
"tls1.2": tls.VersionTLS12,
|
"tls1.2": tls.VersionTLS12,
|
||||||
"tls1.3": tls.VersionTLS13,
|
"tls1.3": tls.VersionTLS13,
|
||||||
}
|
}
|
||||||
|
|
@ -130,6 +127,8 @@ var SupportedProtocols = map[string]uint16{
|
||||||
var unsupportedProtocols = map[string]uint16{
|
var unsupportedProtocols = map[string]uint16{
|
||||||
//nolint:staticcheck
|
//nolint:staticcheck
|
||||||
"ssl3.0": tls.VersionSSL30,
|
"ssl3.0": tls.VersionSSL30,
|
||||||
|
"tls1.0": tls.VersionTLS10,
|
||||||
|
"tls1.1": tls.VersionTLS11,
|
||||||
}
|
}
|
||||||
|
|
||||||
// publicKeyAlgorithms is the map of supported public key algorithms.
|
// publicKeyAlgorithms is the map of supported public key algorithms.
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue