Skip to content

extract the root from the site block (get rid of double root directive)#2311

Open
henderkes wants to merge 10 commits intomainfrom
enhancement/optional-root
Open

extract the root from the site block (get rid of double root directive)#2311
henderkes wants to merge 10 commits intomainfrom
enhancement/optional-root

Conversation

@henderkes
Copy link
Copy Markdown
Contributor

@henderkes henderkes commented Mar 26, 2026

I got tired of writing

localhost {
    root /path/to/root
    php_server {
        root /path/to/root
        worker index.php
    }
}

so here's my idea of fixing it. This accounts for multiple roots being defined with matchers, but it requires a root [*] /path/to/root directive to be defined. That's what caddy now stores in the new down-propagating Helper.BlockState.

We're still keeping the root as a deprecated setting. We need to store the field anyway, so the only thing that changes is that it shouldn't be specified manually anymore.

@francislavoie
Copy link
Copy Markdown
Contributor

I'm confused, why do all this, can't it just be fetched at runtime via GetVar() like file_server does in Caddy?

@henderkes
Copy link
Copy Markdown
Contributor Author

henderkes commented Mar 26, 2026

I'm confused, why do all this, can't it just be fetched at runtime via GetVar() like file_server does in Caddy?

We need the general root of the site block to start workers at Caddy startup. That's why we need to define it inside the php_server block. But we also need to define it for outside the php_server block so that Caddy's own file serving (such as file_server works.

It's also better for performance, as in that we don't always need to parse the current root anew. When the root is explicitly defined (currently), it resolves it once and then reuses it.

@francislavoie
Copy link
Copy Markdown
Contributor

Hmm, fair enough.

I guess we could add a thing to httpcaddyfile.Helper to carry an arbitrary key-val map, then the root directive parser could fill in the root key in that map with its known value, then later whatever other directives could pull the value. This would rely on Caddyfile parsing it in order tho, but as long as users don't override default directive order or throw root and php_server in a route with root being second, it would work pretty consistently I think.

PR welcome in Caddy if you want to wire that up.

@henderkes
Copy link
Copy Markdown
Contributor Author

Sure, I'll look into exposing a key-value map to retrieve any other values from the site config, too. Thank you!

@henderkes
Copy link
Copy Markdown
Contributor Author

State already exists shared for all instances of a site block, so just need to add a root key to it.

@henderkes
Copy link
Copy Markdown
Contributor Author

henderkes commented Mar 26, 2026

caddyserver/caddy#7594

I changed this branch to use a tagged 2.11.3.rc on my repo to let CI pass, will wait for real 2.11.3 release and then combine this with a go mod bump.

@mholt
Copy link
Copy Markdown
Contributor

mholt commented Mar 26, 2026

What if the root has a placeholder in it though? It can't always be known at startup.

@francislavoie
Copy link
Copy Markdown
Contributor

Placeholder for root almost never happens, if it does it would be an env-var if anything. Not really a relevant concern.

@henderkes
Copy link
Copy Markdown
Contributor Author

What if the root has a placeholder in it though? It can't always be known at startup.

Then it would fail us either way. Php must know the root at provision time.

@mholt
Copy link
Copy Markdown
Contributor

mholt commented Mar 26, 2026

I've seen some configs that use map to set the root dynamically (while keeping it in the site owner's control). Or matchers that simply set it to a different value if some condition is matched.

So, just be aware that if you really do piggy-back on the root directive for your PHP root, it may have a placeholder that can't be evaluated until request-time.

If you truly need the root to be 100% correct in all cases, your own root value is probably ideal.

@henderkes
Copy link
Copy Markdown
Contributor Author

That's a valid heads up. I'll try with using placeholders/env variables and test with that. That's on the consumer of the root field to safety check, though.

If anything this probably means we should keep the root setting for php_server for this case, but at the same time I don't see a situation where

localhost
root {placeholder} # resolves to != /path
php_server {
    root /path
}

wouldn't explode in the users face during runtime. At least this way it could warn (/fail with workers) on startup.

@henderkes henderkes marked this pull request as ready for review March 28, 2026 18:01
caddy/module.go Outdated
Comment on lines 38 to 40
// Deprecated: Use the `root` directive in the site block instead.
Root string `json:"root,omitempty"`
// SplitPath sets the substrings for splitting the URI into two parts. The first matching substring will be used to split the "path info" from the path. The first piece is suffixed with the matching substring and will be assumed as the actual resource (CGI script) name. The second piece will be set to PATH_INFO for the CGI script to use. Default: `.php`.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure we should necessarily deprecate this. Not using it myself, but theoretically you can have multiple php_servers per domain with different roots, same as you can have multiple file_servers with different roots (without handle {...}).

@path1 path /path1/*
php_server @app {
    root /some/root
}

@path2 path /path2/*
php_server @path2{
    root /other/root
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah that's a fair point. It needs to stay in the code anyway, so I'll revert the deprecation.

Comment on lines +475 to +483
localhost:`+testPort+` {
root ../testdata
php_server {
worker worker-with-counter.php
}
}
`, "caddyfile")

tester.AssertGetResponse("http://localhost:"+testPort+"/worker-with-counter.php", http.StatusOK, "requests:1")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this test also green on main? Maybe would make sense to test with multiple php_servers and multiple roots.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this test also green on main? Maybe would make sense to test with multiple php_servers and multiple roots.

It would fail to start because it wouldn't find the worker-with-counter.php. But yes, I'll do that!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh because the path will be relative to the working directory and not relative to the server root. I guess it makes sense then 👍

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes this a small BC break though for people expecting the working directory. I wonder if we should check both relative path to root and relative path to working directory. Or at least warn if one or the other was found.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sadly I think we can't do this at all, after all.
#2311 (comment)

Ugh, I'm so tired of double typing the root :(

@henderkes
Copy link
Copy Markdown
Contributor Author

I just realized this is a BC break, which is very unfortunate...

@henderkes
Copy link
Copy Markdown
Contributor Author

Not sure why the tests are failing in CI, they run locally and they should succeed. Perhaps the php_ini isn't taking effect in CI for some reason?

So, I think we can't do this after all. Example:

localhost

root /app/public
root /test /app/test
php_server

A request to /test will now serve /app/public/index.php.

@AlliBalliBaba
Copy link
Copy Markdown
Contributor

Hmm yeah you're right, trying to guarantee the correct root while provisioning might be too messy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants