Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
fba37f6
new phpserver
AlliBalliBaba Jul 1, 2026
39db699
more cleanup
AlliBalliBaba Jul 1, 2026
8e12812
linting
AlliBalliBaba Jul 1, 2026
7e1ddb8
removes unnecessary code.
AlliBalliBaba Jul 1, 2026
346887e
naming.
AlliBalliBaba Jul 1, 2026
2ab4772
more unnecessary code
AlliBalliBaba Jul 1, 2026
300e2e7
corerctly orders autoscaling chan registration
AlliBalliBaba Jul 1, 2026
2c4f818
go fmt
AlliBalliBaba Jul 1, 2026
ddd6245
reuses split-path normalization logic.
AlliBalliBaba Jul 1, 2026
19069bc
removes unnecessary locking.
AlliBalliBaba Jul 1, 2026
53e30a8
improves assertions.
AlliBalliBaba Jul 1, 2026
9b38f1d
pre-computes worker matches.
AlliBalliBaba Jul 1, 2026
178df07
naming.
AlliBalliBaba Jul 2, 2026
c3a340f
removes unnecessary matchrelpath
AlliBalliBaba Jul 2, 2026
8066a30
adds dedicated phpserver tests.
AlliBalliBaba Jul 2, 2026
adf2fed
refines tests.
AlliBalliBaba Jul 2, 2026
48bfab4
adds missing test file.
AlliBalliBaba Jul 2, 2026
8810352
fixes worker match logic and disallows duplicates again
AlliBalliBaba Jul 2, 2026
7384a57
Merge branch 'main' into refactor/phpserver
AlliBalliBaba Jul 2, 2026
e0896d8
prepared env merge.
AlliBalliBaba Jul 2, 2026
82eb1b4
fixes env logic
AlliBalliBaba Jul 2, 2026
3b1bc73
suggestions by @dunglas
AlliBalliBaba Jul 4, 2026
c7ea1f7
fixes the module
AlliBalliBaba Jul 4, 2026
4871ea4
cleanup
AlliBalliBaba Jul 4, 2026
0401c62
closes admin body immediately
AlliBalliBaba Jul 4, 2026
9b7967f
removes ServerOption.
AlliBalliBaba Jul 4, 2026
6987c1e
fixes tests.
AlliBalliBaba Jul 4, 2026
ea35f2a
preallocates the fallback server
AlliBalliBaba Jul 4, 2026
47f45ce
precomputes the fallback server
AlliBalliBaba Jul 4, 2026
ce095d4
error cleanup
AlliBalliBaba Jul 4, 2026
48ac7d2
context cleanup.
AlliBalliBaba Jul 4, 2026
bb8ce0b
naming
AlliBalliBaba Jul 4, 2026
94fe75e
linting
AlliBalliBaba Jul 4, 2026
e56326c
ensures context and logger must be defined
AlliBalliBaba Jul 5, 2026
081ef2a
more context cleanup.
AlliBalliBaba Jul 5, 2026
e2a1e9d
makes original request logic more consistent.
AlliBalliBaba Jul 5, 2026
a81552d
pipeline test
AlliBalliBaba Jul 5, 2026
17c2823
ensures $_ENV is populated for the test
AlliBalliBaba Jul 5, 2026
9c987cc
refactors server public api and fixes restart configuration
AlliBalliBaba Jul 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 48 additions & 4 deletions caddy/admin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,6 @@ func TestRestartWorkerViaAdminApi(t *testing.T) {
}
`, "caddyfile")

// make sure workers are not still running from any previous tests
assertAdminResponse(t, tester, "POST", "workers/restart", http.StatusOK, "workers restarted successfully\n")

tester.AssertGetResponse("http://localhost:"+testPort+"/", http.StatusOK, "requests:1")
tester.AssertGetResponse("http://localhost:"+testPort+"/", http.StatusOK, "requests:2")

Expand Down Expand Up @@ -79,6 +76,7 @@ func TestShowTheCorrectThreadDebugStatus(t *testing.T) {
debugState := getDebugState(t, tester)

// assert that the correct threads are present in the thread info
assert.Len(t, debugState.ThreadDebugStates, 3)
assert.Equal(t, debugState.ThreadDebugStates[0].State, "ready")
assert.Contains(t, debugState.ThreadDebugStates[1].Name, "worker-with-counter.php")
assert.Contains(t, debugState.ThreadDebugStates[2].Name, "index.php")
Expand Down Expand Up @@ -325,7 +323,7 @@ func TestAddModuleWorkerViaAdminApi(t *testing.T) {
assert.NoError(t, err)
r.Header.Set("Content-Type", "text/caddyfile")
resp := tester.AssertResponseCode(r, http.StatusOK)
defer func() { require.NoError(t, resp.Body.Close()) }()
require.NoError(t, resp.Body.Close())

// Get the updated debug state to check if the worker was added
updatedDebugState := getDebugState(t, tester)
Expand All @@ -348,3 +346,49 @@ func TestAddModuleWorkerViaAdminApi(t *testing.T) {
// Make a request to the worker to verify it's working
tester.AssertGetResponse("http://localhost:"+testPort+"/worker-with-counter.php", http.StatusOK, "requests:1")
}

func TestRegisteredModuleWorkerPoolsMustBeCorrect(t *testing.T) {
tester := caddytest.NewTester(t)
initServer(t, tester, `
{
skip_install_trust
admin localhost:2999
frankenphp {
num_threads 4
worker ../testdata/worker-with-env.php 1
}
}
http://localhost:`+testPort+` {
route {
php {
root ../testdata
worker worker-with-counter.php 1 {
match /matched*
}
}
php {
root ../testdata
worker worker.php 1
}
}
}
`, "caddyfile")

debugState := getDebugState(t, tester)

worker1Path, _ := fastabs.FastAbs("../testdata/worker-with-env.php")
worker2Path, _ := fastabs.FastAbs("../testdata/worker-with-counter.php")
worker3Path, _ := fastabs.FastAbs("../testdata/worker.php")
receivedThreadNames := make([]string, 0)
for _, thread := range debugState.ThreadDebugStates {
receivedThreadNames = append(receivedThreadNames, thread.Name)
}

assert.Len(t, receivedThreadNames, 4, "expected 4 threads to be present")
assert.Contains(t, receivedThreadNames, "Regular PHP Thread", "expected a regular thread to be present")
assert.Contains(t, receivedThreadNames, "Worker PHP Thread - "+worker1Path, "expected global worker to be present")
assert.Contains(t, receivedThreadNames, "Worker PHP Thread - "+worker2Path, "expected module worker with \"match\" directive to be present")
assert.Contains(t, receivedThreadNames, "Worker PHP Thread - "+worker3Path, "expected module worker without \"match\" directive to be present")
}
121 changes: 40 additions & 81 deletions caddy/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import (
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/dunglas/frankenphp"
"github.com/dunglas/frankenphp/internal/fastabs"
)

var (
Expand Down Expand Up @@ -64,6 +63,7 @@ type FrankenPHPApp struct {
metrics frankenphp.Metrics
ctx context.Context
logger *slog.Logger
modules map[int]*FrankenPHPModule
}

var errIni = errors.New(`"php_ini" must be in the format: php_ini "<key>" "<value>"`)
Expand Down Expand Up @@ -101,74 +101,6 @@ func (f *FrankenPHPApp) Provision(ctx caddy.Context) error {
return nil
}

func (f *FrankenPHPApp) generateUniqueModuleWorkerName(filepath string) string {
var i uint
filepath, _ = fastabs.FastAbs(filepath)
name := "m#" + filepath

retry:
for _, wc := range f.Workers {
if wc.Name == name {
name = fmt.Sprintf("m#%s_%d", filepath, i)
i++

goto retry
}
}

return name
}

func (f *FrankenPHPApp) addModuleWorkers(workers ...workerConfig) ([]workerConfig, error) {
for i := range workers {
w := &workers[i]

if frankenphp.EmbeddedAppPath != "" && filepath.IsLocal(w.FileName) {
w.FileName = filepath.Join(frankenphp.EmbeddedAppPath, w.FileName)
}
}

// A php_server directive is provisioned once per route it's embedded in. Only the first embed
// registers its pools; later embeds reuse them by position, never touching other directives (#2477).
var registered []workerConfig
if len(workers) > 0 && workers[0].routeGroup != "" {
registered = f.moduleWorkersInRouteGroup(workers[0].routeGroup)
}

for i := range workers {
if i < len(registered) {
workers[i].Name = registered[i].Name
continue
}

f.registerModuleWorker(&workers[i])
}

return workers, nil
}

func (f *FrankenPHPApp) registerModuleWorker(w *workerConfig) {
if w.Name == "" {
w.Name = f.generateUniqueModuleWorkerName(w.FileName)
} else if !strings.HasPrefix(w.Name, "m#") {
w.Name = "m#" + w.Name
}

f.Workers = append(f.Workers, *w)
}

// moduleWorkersInRouteGroup returns the registered workers of one directive, in registration order.
func (f *FrankenPHPApp) moduleWorkersInRouteGroup(routeGroup string) []workerConfig {
var group []workerConfig
for _, w := range f.Workers {
if w.routeGroup == routeGroup {
group = append(group, w)
}
}

return group
}

func (f *FrankenPHPApp) Start() error {
repl := caddy.NewReplacer()

Expand All @@ -188,23 +120,33 @@ func (f *FrankenPHPApp) Start() error {
frankenphp.WithMaxRequests(f.MaxRequests),
)

// register global workers
for _, w := range f.Workers {
w.options = append(w.options,
frankenphp.WithWorkerEnv(w.Env),
frankenphp.WithWorkerWatchMode(w.Watch),
frankenphp.WithWorkerMaxFailures(w.MaxConsecutiveFailures),
frankenphp.WithWorkerMaxThreads(w.MaxThreads),
frankenphp.WithWorkerRequestOptions(w.requestOptions...),
)

f.opts = append(f.opts, frankenphp.WithWorkers(w.Name, repl.ReplaceKnown(w.FileName, ""), w.Num, w.options...))
w.FileName = repl.ReplaceKnown(w.FileName, "")
f.opts = append(f.opts, frankenphp.WithWorkers(w.Name, w.FileName, w.Num, w.toWorkerOptions()...))
}

// register module workers
for _, module := range f.modules {
for _, w := range module.Workers {
w.FileName = repl.ReplaceKnown(w.FileName, "")
if module.server == nil {
return fmt.Errorf("module %d has no server", module.ServerIdx)
}
workerOptions := append(w.toWorkerOptions(), frankenphp.WithWorkerServerScope(module.server))
f.opts = append(f.opts, frankenphp.WithWorkers(w.Name, w.FileName, w.Num, workerOptions...))
}
}

frankenphp.Shutdown()
if err := frankenphp.Init(f.opts...); err != nil {
return err
}

// after startup, reset all configuration for future reloads or tests
// it is necessary to do this here since caddy will re-use the app instance
f.reset()

return nil
}

Expand All @@ -220,18 +162,35 @@ func (f *FrankenPHPApp) Stop() error {
frankenphp.Shutdown()
}

// reset the configuration so it doesn't bleed into later tests
return nil
}

func (f *FrankenPHPApp) reset() {
f.Workers = nil
f.NumThreads = 0
f.MaxWaitTime = 0
f.MaxIdleTime = 0
f.MaxRequests = 0

f.PhpIni = nil
f.modules = nil
f.opts = nil
f.ctx = nil
f.metrics = nil
optionsMU.Lock()
options = nil
optionsMU.Unlock()
}

return nil
func (f *FrankenPHPApp) registerModule(m *FrankenPHPModule, serverOpt frankenphp.Option) {
if f.modules == nil {
f.modules = make(map[int]*FrankenPHPModule)
}

if _, ok := f.modules[m.ServerIdx]; !ok {
// only register the module if it's not already registered
f.modules[m.ServerIdx] = m
f.opts = append(f.opts, serverOpt)
}
}

// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
Expand Down
10 changes: 5 additions & 5 deletions caddy/caddy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@ func TestCustomCaddyVariablesInEnv(t *testing.T) {
tester.AssertGetResponse("http://localhost:"+testPort+"/worker-env.php", http.StatusOK, "hello world")
}

func TestPHPServerDirective(t *testing.T) {
func TestServerDirective(t *testing.T) {
tester := caddytest.NewTester(t)
initServer(t, tester, `
{
Expand All @@ -463,7 +463,7 @@ func TestPHPServerDirective(t *testing.T) {
tester.AssertGetResponse("http://localhost:"+testPort+"/not-found.txt", http.StatusOK, "I am by birth a Genevese (i not set)")
}

func TestPHPServerDirectiveDisableFileServer(t *testing.T) {
func TestServerDirectiveDisableFileServer(t *testing.T) {
tester := caddytest.NewTester(t)
initServer(t, tester, `
{
Expand All @@ -487,7 +487,7 @@ func TestPHPServerDirectiveDisableFileServer(t *testing.T) {
tester.AssertGetResponse("http://localhost:"+testPort+"/not-found.txt", http.StatusOK, "I am by birth a Genevese (i not set)")
}

func TestPHPServerGlobals(t *testing.T) {
func TestServerGlobals(t *testing.T) {
documentRoot, _ := filepath.Abs("../testdata")
scriptFilename := filepath.Join(documentRoot, "server-globals.php")

Expand Down Expand Up @@ -539,7 +539,7 @@ REQUEST_URI: /server-globals.php/en
)
}

func TestWorkerPHPServerGlobals(t *testing.T) {
func TestWorkerServerGlobals(t *testing.T) {
documentRoot, _ := filepath.Abs("../testdata")
documentRoot2, _ := filepath.Abs("../caddy")
scriptFilename := documentRoot + string(filepath.Separator) + "server-globals.php"
Expand Down Expand Up @@ -859,7 +859,7 @@ func TestWorkerMetrics(t *testing.T) {
}

// #2477: verify one pool per worker, no "_0" duplicates.
func TestPhpServerWorkerMatchPoolCount(t *testing.T) {
func TestServerWorkerMatchPoolCount(t *testing.T) {
tester := caddytest.NewTester(t)
initServer(t, tester, `
{
Expand Down
Loading
Loading