@@ -60,15 +60,15 @@ type FrankenPHPApp struct {
6060 // EXPERIMENTAL: MaxRequests sets the maximum number of requests a PHP thread handles before restarting (0 = unlimited)
6161 MaxRequests int `json:"max_requests,omitempty"`
6262
63- opts []frankenphp.Option
64- metrics frankenphp.Metrics
65- ctx context. Context
66- logger * slog. Logger
67- modules [] * FrankenPHPModule
68- usedWorkerNames map [ string ] bool
69- httpApp * caddyhttp.App
70- hasStarted atomic.Bool
71- started chan any
63+ opts []frankenphp.Option
64+ provisionOpts [] frankenphp.Option
65+ metrics frankenphp. Metrics
66+ ctx context. Context
67+ logger * slog. Logger
68+ modules [] * FrankenPHPModule
69+ httpApp * caddyhttp.App
70+ hasStarted atomic.Bool
71+ started chan any
7272}
7373
7474var errIni = errors .New (`"php_ini" must be in the format: php_ini "<key>" "<value>"` )
@@ -107,18 +107,29 @@ func (f *FrankenPHPApp) Provision(ctx caddy.Context) error {
107107 return nil
108108}
109109
110- func (f * FrankenPHPApp ) Start () error {
111- defer func () {
112- close (f .started )
113- }()
110+ // Validate is called by Caddy before Start() to validate before shutting down the currently running config
111+ func (f * FrankenPHPApp ) Validate () error {
112+ opts , err := f .collectOptions (caddy .NewReplacer (), false )
113+ if err != nil {
114+ return err
115+ }
114116
115- repl := caddy .NewReplacer ()
117+ return frankenphp .Validate (opts ... )
118+ }
116119
120+ // collectOptions turns the configuration into the options Init() takes.
121+ // keep is for the configuration that starts, whose servers the modules
122+ // serve from; Validate() collects those of one that may never start.
123+ func (f * FrankenPHPApp ) collectOptions (repl * caddy.Replacer , keep bool ) ([]frankenphp.Option , error ) {
117124 optionsMU .RLock ()
118- f .opts = append (f .opts , options ... )
125+ // We have at least 9 hardcoded options
126+ opts := make ([]frankenphp.Option , 0 , 9 + len (options )+ len (f .provisionOpts ))
127+ opts = append (opts , options ... )
119128 optionsMU .RUnlock ()
120129
121- f .opts = append (f .opts ,
130+ opts = append (opts , f .provisionOpts ... )
131+
132+ opts = append (opts ,
122133 frankenphp .WithContext (f .ctx ),
123134 frankenphp .WithLogger (f .logger ),
124135 frankenphp .WithNumThreads (f .NumThreads ),
@@ -130,18 +141,42 @@ func (f *FrankenPHPApp) Start() error {
130141 frankenphp .WithMaxRequests (f .MaxRequests ),
131142 )
132143
144+ usedWorkerNames := make (map [string ]bool , len (f .Workers ))
145+
133146 // register global workers
134147 for _ , w := range f .Workers {
135148 w .FileName = repl .ReplaceKnown (w .FileName , "" )
136- w .Name = f . createUniqueWorkerName (w , "" )
137- opts , err := w .toWorkerOptions ()
149+ w .Name = createUniqueWorkerName (usedWorkerNames , w , "" )
150+ workerOptions , err := w .toWorkerOptions ()
138151 if err != nil {
139- return err
152+ return nil , err
140153 }
141- f .opts = append (f .opts , frankenphp .WithWorkers (w .Name , w .FileName , w .Num , opts ... ))
154+ opts = append (opts , frankenphp .WithWorkers (w .Name , w .FileName , w .Num , workerOptions ... ))
155+ }
156+
157+ moduleOpts , err := f .collectModuleOptions (repl , usedWorkerNames , keep )
158+ if err != nil {
159+ return nil , err
160+ }
161+
162+ return append (opts , moduleOpts ... ), nil
163+ }
164+
165+ func (f * FrankenPHPApp ) Start () error {
166+ defer func () {
167+ close (f .started )
168+ }()
169+
170+ opts , err := f .collectOptions (caddy .NewReplacer (), true )
171+ if err != nil {
172+ return err
142173 }
174+ f .opts = opts
143175
144- if err := f .registerModules (repl ); err != nil {
176+ // Validate() ran before the modules provisioned, so it saw the global
177+ // block alone: what a php_server declares is checked here, in time for
178+ // the configuration in place to survive a refusal
179+ if err := frankenphp .Validate (f .opts ... ); err != nil {
145180 return err
146181 }
147182
@@ -178,35 +213,42 @@ func (f *FrankenPHPApp) Stop() error {
178213}
179214
180215// register workers and servers for "php" and "php_server" modules
181- func (f * FrankenPHPApp ) registerModules (repl * caddy.Replacer ) error {
182- modulesByIndex := make (map [int ]* FrankenPHPModule , len (f .modules ))
183- for _ , module := range f .modules {
184- if module .ServerIndex == 0 {
185- if err := f .registerModule (repl , module ); err != nil {
186- return err
187- }
188- continue
189- }
216+ func (f * FrankenPHPApp ) collectModuleOptions (repl * caddy.Replacer , usedWorkerNames map [string ]bool , keep bool ) ([]frankenphp.Option , error ) {
217+ opts := make ([]frankenphp.Option , 0 , len (f .modules ))
218+ serversByIndex := make (map [int ]* frankenphp.Server , len (f .modules ))
190219
220+ for _ , module := range f .modules {
191221 // modules with the same server_idx should share the same server instance
192222 // example: the worker { match * } rule adds 2 "php" subroutes to the caddy handler
193223 // the 2 handlers belong to the same "php_server" and must therefore share workers
194- if existingModule , ok := modulesByIndex [module .ServerIndex ]; ok {
195- module .server = existingModule .server
196- continue
224+ if module .ServerIndex != 0 {
225+ if server , ok := serversByIndex [module .ServerIndex ]; ok {
226+ if keep {
227+ module .server = server
228+ }
229+
230+ continue
231+ }
232+ }
233+
234+ server , moduleOpts , err := f .collectModule (repl , module , usedWorkerNames )
235+ if err != nil {
236+ return nil , err
197237 }
198238
199- modulesByIndex [module .ServerIndex ] = module
200- if err := f .registerModule (repl , module ); err != nil {
201- return err
239+ if keep {
240+ module .server = server
241+ }
242+ if module .ServerIndex != 0 {
243+ serversByIndex [module .ServerIndex ] = server
202244 }
245+ opts = append (opts , moduleOpts ... )
203246 }
204247
205- return nil
248+ return opts , nil
206249}
207250
208- // register a server instance and its workers for a single Caddy module
209- func (f * FrankenPHPApp ) registerModule (repl * caddy.Replacer , module * FrankenPHPModule ) error {
251+ func (f * FrankenPHPApp ) collectModule (repl * caddy.Replacer , module * FrankenPHPModule , usedWorkerNames map [string ]bool ) (* frankenphp.Server , []frankenphp.Option , error ) {
210252 serverName := f .resolveServerName (module )
211253 server , err := frankenphp .NewServer (
212254 module .resolvedDocumentRoot ,
@@ -216,43 +258,38 @@ func (f *FrankenPHPApp) registerModule(repl *caddy.Replacer, module *FrankenPHPM
216258 frankenphp .WithServerLogger (module .logger ),
217259 )
218260 if err != nil {
219- return err
261+ return nil , nil , err
220262 }
221263
222- module .server = server
223- f .opts = append (f .opts , frankenphp .WithServer (server ))
264+ opts := []frankenphp.Option {frankenphp .WithServer (server )}
224265
225266 for _ , w := range module .Workers {
226267 w .FileName = repl .ReplaceKnown (w .FileName , "" )
227- w .Name = f . createUniqueWorkerName (w , serverName )
268+ w .Name = createUniqueWorkerName (usedWorkerNames , w , serverName )
228269 workerOptions , err := w .toWorkerOptions ()
229270 if err != nil {
230- return err
271+ return nil , nil , err
231272 }
232273 workerOptions = append (workerOptions , frankenphp .WithWorkerServerScope (server ))
233- f . opts = append (f . opts , frankenphp .WithWorkers (w .Name , w .FileName , w .Num , workerOptions ... ))
274+ opts = append (opts , frankenphp .WithWorkers (w .Name , w .FileName , w .Num , workerOptions ... ))
234275 }
235276
236- return nil
277+ return server , opts , nil
237278}
238279
239280// avoid name collisions for workers
240281// on collision, a name is first qualified with the server name
241282// ("<serverName>:<name>") before falling back to a numeric postfix
242- func (f * FrankenPHPApp ) createUniqueWorkerName (wc workerConfig , serverName string ) string {
243- if f .usedWorkerNames == nil {
244- f .usedWorkerNames = make (map [string ]bool )
245- }
246-
283+ func createUniqueWorkerName (usedWorkerNames map [string ]bool , wc workerConfig , serverName string ) string {
247284 if wc .Name == "" {
248285 wc .Name , _ = fastabs .FastAbs (wc .FileName )
249286 }
250287
251288 name := wc .Name
252289 suffix := 0
253290 for {
254- if _ , ok := f . usedWorkerNames [name ]; ! ok {
255- f . usedWorkerNames [name ] = true
291+ if _ , ok := usedWorkerNames [name ]; ! ok {
292+ usedWorkerNames [name ] = true
256293 break
257294 }
258295 if serverName != "" {
0 commit comments