Compare commits
10 commits
f11c780fdc
...
b9ecf19980
| Author | SHA1 | Date | |
|---|---|---|---|
| b9ecf19980 | |||
|
|
551f793700 | ||
|
|
4564261d83 | ||
|
|
16fe83c7af | ||
|
|
3723e89585 | ||
|
|
14a63a26b9 | ||
|
|
67debd0e11 | ||
|
|
b9710c6af4 | ||
|
|
493898d9bd | ||
|
|
1c596e3c5a |
21 changed files with 496 additions and 211 deletions
7
.github/workflows/ci.yml
vendored
7
.github/workflows/ci.yml
vendored
|
|
@ -13,6 +13,7 @@ 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
|
||||||
|
|
||||||
|
|
@ -110,7 +111,7 @@ jobs:
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
run: |
|
run: |
|
||||||
go build -tags nobadger,nomysql,nopgx -trimpath -ldflags="-w -s" -v
|
go build -trimpath -ldflags="-w -s" -v
|
||||||
|
|
||||||
- name: Smoke test Caddy
|
- name: Smoke test Caddy
|
||||||
working-directory: ./cmd/caddy
|
working-directory: ./cmd/caddy
|
||||||
|
|
@ -133,7 +134,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 -tags nobadger,nomysql,nopgx -v -coverprofile="cover-profile.out" -short -race ./...
|
go test -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
|
||||||
|
|
@ -190,7 +191,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 -tags nobadger,nomysql,nopgx -v ./...
|
CGO_ENABLED=0 go test -p 1 -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,6 +11,8 @@ 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
|
||||||
|
|
||||||
|
|
@ -74,11 +76,9 @@ 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: |
|
run: go build -trimpath -o caddy-"$GOOS"-$GOARCH 2> /dev/null
|
||||||
GOOS=$GOOS GOARCH=$GOARCH go build -tags=nobadger,nomysql,nopgx -trimpath -o caddy-"$GOOS"-$GOARCH 2> /dev/null
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,10 @@ 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 and returns them. Open curly brace tokens also indicate
|
// into a slice of strings and returns them. Open curly brace tokens
|
||||||
// the end of arguments, and the curly brace is not included in
|
// also indicate the end of arguments, and the curly brace is not
|
||||||
// the return value nor is it loaded.
|
// included in 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 and returns them. Open curly brace
|
// retaining quotes) into a slice of strings and returns them.
|
||||||
// tokens also indicate the end of arguments, and the curly brace is
|
// Open curly brace tokens also indicate the end of arguments,
|
||||||
// not included in the return value nor is it loaded.
|
// and the curly brace is 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,6 +331,18 @@ 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,6 +274,66 @@ 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,28 +379,23 @@ 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() {
|
||||||
// see if we can grab a key
|
currentMappingKey := bd.Val()
|
||||||
var currentMappingKey string
|
|
||||||
if bd.Val() == "{" {
|
if currentMappingKey == "{" {
|
||||||
return p.Err("anonymous blocks are not supported")
|
return p.Err("anonymous blocks are not supported")
|
||||||
}
|
}
|
||||||
currentMappingKey = bd.Val()
|
|
||||||
currentMappingTokens := []Token{}
|
// load up all arguments (if there even are any)
|
||||||
// read all args until end of line / {
|
currentMappingTokens := bd.RemainingArgsAsTokens()
|
||||||
if bd.NextArg() {
|
|
||||||
currentMappingTokens = append(currentMappingTokens, bd.Token())
|
// load up the entire block
|
||||||
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,6 +18,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -884,6 +885,51 @@ 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,22 +564,23 @@ 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 && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.DNS == nil) {
|
if globalACMEDNSok {
|
||||||
if globalACMEDNS == nil {
|
globalDNS := options["dns"]
|
||||||
globalACMEDNS = options["dns"]
|
if globalDNS != nil {
|
||||||
if globalACMEDNS == nil {
|
// If global `dns` is set, do NOT set provider in issuer, just set empty dns config
|
||||||
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: new(caddytls.DNSChallengeConfig),
|
DNS: &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)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
{
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
acme_dns
|
||||||
|
}
|
||||||
|
|
||||||
|
example.com {
|
||||||
|
respond "Hello World"
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
acme_dns specified without DNS provider config, but no provider specified with 'dns' global option
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
(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
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
(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.23.0
|
github.com/caddyserver/certmagic v0.24.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.23.0 h1:CfpZ/50jMfG4+1J/u2LV6piJq4HOfO6ppOnOf7DkFEU=
|
github.com/caddyserver/certmagic v0.24.0 h1:EfXTWpxHAUKgDfOj6MHImJN8Jm4AMFfMT6ITuKhrDF0=
|
||||||
github.com/caddyserver/certmagic v0.23.0/go.mod h1:9mEZIWqqWoI+Gf+4Trh04MOVPD0tGSxtqsxg87hAIH4=
|
github.com/caddyserver/certmagic v0.24.0/go.mod h1:xPT7dC1DuHHnS2yuEQCEyks+b89sUkMENh8dJF+InLE=
|
||||||
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,7 +28,6 @@ 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"
|
||||||
|
|
@ -171,13 +170,15 @@ 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 {
|
||||||
|
|
@ -236,15 +237,6 @@ 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) {
|
||||||
|
|
@ -278,19 +270,6 @@ 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -448,6 +427,25 @@ 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 {
|
||||||
|
|
@ -466,32 +464,37 @@ 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))
|
||||||
for _, cp := range srv.TLSConnPolicies {
|
removeTLSALPN(srv, "h2")
|
||||||
// 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
|
|
||||||
for i, alpn := range cp.ALPN {
|
// configure the http versions the server will serve
|
||||||
if alpn == "h2" {
|
if srv.protocol("h1") {
|
||||||
cp.ALPN = append(cp.ALPN[:i], cp.ALPN[i+1:]...)
|
srv.server.Protocols.SetHTTP1(true)
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
if srv.protocol("h2") || srv.protocol("h2c") {
|
||||||
} else {
|
// skip setting h2 because if NextProtos is present, it's list of alpn versions will take precedence.
|
||||||
|
// 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)
|
||||||
}
|
}
|
||||||
|
|
@ -501,11 +504,6 @@ 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 {
|
||||||
|
|
@ -570,15 +568,13 @@ func (app *App) Start() error {
|
||||||
ln = srv.listenerWrappers[i].WrapListener(ln)
|
ln = srv.listenerWrappers[i].WrapListener(ln)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle http2 if use tls listener wrapper
|
// check if the connection is h2c
|
||||||
if h2ok {
|
ln = &http2Listener{
|
||||||
http2lnWrapper := &http2Listener{
|
useTLS: useTLS,
|
||||||
|
useH1: h1ok,
|
||||||
|
useH2: h2ok || h2cok,
|
||||||
Listener: ln,
|
Listener: ln,
|
||||||
server: srv.server,
|
logger: app.logger,
|
||||||
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;
|
||||||
|
|
@ -596,12 +592,9 @@ 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
|
||||||
|
|
@ -756,25 +749,12 @@ 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(3)
|
startedShutdown.Add(2)
|
||||||
finishedShutdown.Add(3)
|
finishedShutdown.Add(2)
|
||||||
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,6 +167,8 @@ 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
|
||||||
|
|
@ -455,7 +457,14 @@ 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,102 +1,110 @@
|
||||||
package caddyhttp
|
package caddyhttp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
weakrand "math/rand"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// http2Listener wraps the listener to solve the following problems:
|
type connectionStater interface {
|
||||||
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
if csc, ok := conn.(connectionStateConn); ok {
|
_, isConnectionStater := conn.(connectionStater)
|
||||||
// *tls.Conn will return empty string because it's only populated after handshake is complete
|
// emit a warning
|
||||||
if csc.ConnectionState().NegotiatedProtocol == http2.NextProtoTLS {
|
if h.useTLS && !isConnectionStater {
|
||||||
go h.serveHttp2(csc)
|
h.logger.Warn("tls is enabled, but listener wrapper returns a connection that doesn't implement connectionStater")
|
||||||
continue
|
} else if !h.useTLS && isConnectionStater {
|
||||||
}
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *http2Listener) serveHttp2(csc connectionStateConn) {
|
type http2StateConn struct {
|
||||||
atomic.AddUint64(&h.cnt, 1)
|
*http2Conn
|
||||||
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,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const shutdownPollIntervalMax = 500 * time.Millisecond
|
func (conn http2StateConn) ConnectionState() tls.ConnectionState {
|
||||||
|
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
timer := time.NewTimer(nextPollInterval())
|
type http2Conn struct {
|
||||||
defer timer.Stop()
|
// current index where the preface should match,
|
||||||
for {
|
// no matching is done if idx is >= len(http2.ClientPreface)
|
||||||
if atomic.LoadUint64(&h.cnt) == 0 {
|
idx int
|
||||||
return nil
|
// whether the connection is expected to be h2/h2c
|
||||||
}
|
h2Expected bool
|
||||||
select {
|
// log if one such connection is detected
|
||||||
case <-ctx.Done():
|
logger *zap.Logger
|
||||||
return ctx.Err()
|
net.Conn
|
||||||
case <-timer.C:
|
|
||||||
timer.Reset(nextPollInterval())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *http2Listener) runHook(conn net.Conn, state http.ConnState) {
|
func (c *http2Conn) Read(p []byte) (int, error) {
|
||||||
if h.server.ConnState != nil {
|
if c.idx >= len(http2.ClientPreface) {
|
||||||
h.server.ConnState(conn, state)
|
return c.Conn.Read(p)
|
||||||
|
}
|
||||||
|
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,7 +428,19 @@ 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,7 +248,6 @@ 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
|
||||||
|
|
@ -266,11 +265,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: Can be removed if https://github.com/golang/go/pull/56110 is ever merged.
|
// TODO: Scheduled to be removed later because https://github.com/golang/go/pull/56110 has been 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.(connectionStateConn); ok {
|
if csc, ok := conn.(connectionStater); ok {
|
||||||
r.TLS = new(tls.ConnectionState)
|
r.TLS = new(tls.ConnectionState)
|
||||||
*r.TLS = csc.ConnectionState()
|
*r.TLS = csc.ConnectionState()
|
||||||
}
|
}
|
||||||
|
|
@ -1083,6 +1082,8 @@ 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.VersionTLS12,
|
MinVersion: tls.VersionTLS10,
|
||||||
GetConfigForClient: getConfigForClient,
|
GetConfigForClient: getConfigForClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,10 @@ 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,
|
||||||
}
|
}
|
||||||
|
|
@ -127,8 +130,6 @@ 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