Files
frankenphp/request_options.go
Marc 1d74b2caa8 feat: define domain specific workers in php_server and php blocks (#1509)
* add module (php_server directive) based workers

* refactor moduleID to uintptr for faster comparisons

* let workers inherit environment variables and root from php_server

* caddy can shift FrankenPHPModules in memory for some godforsaken reason, can't rely on them staying the same

* remove debugging statement

* fix tests

* refactor moduleID to uint64 for faster comparisons

* actually allow multiple workers per script filename

* remove logging

* utility function

* reuse existing worker with same filename and environment when calling newWorker with a filepath that already has a suitable worker, simply add number of threads

* no cleanup happens between tests, so restore old global worker overwriting logic

* add test, use getWorker(ForContext) function in frankenphp.go as well

* bring error on second global worker with the same filename again

* refactor to using name instead of moduleID

* nicer name

* nicer name

* add more tests

* remove test case already covered by previous test

* revert back to single variable, moduleIDs no longer relevant

* update comment

* figure out the worker to use in FrankenPHPModule::ServeHTTP

* add caddy/config_tests, add --retry 5 to download

* add caddy/config_tests

* sum up logic a bit, put worker thread addition into moduleWorkers parsing, before workers are actually created

* implement suggestions as far as possible

* fixup

* remove tags

* feat: download the mostly static binary when possible (#1467)

* feat: download the mostly static binary when possible

* cs

* docs: remove wildcard matcher from root directive (#1513)

* docs: update README with additional documentation links

Add link to classic mode, efficiently serving large static files and monitoring FrankenPHP

Signed-off-by: Romain Bastide <romain.bastide@orange.com>

* ci: combine dependabot updates for one group to 1 pull-request

* feat: compatibility with libphp.dylib on macOS

* feat: upgrade to Caddy 2.10

* feat: upgrade to Caddy 2.10

* chore: run prettier

* fix: build-static.sh consecutive builds (#1496)

* fix consecutive builds

* use minor version in PHP_VERSION

* install jq in centos container

* fix "arm64" download arch for spc binary

* jq is not available as a rpm download

* linter

* specify php 8.4 default

specify 8.4 so we manually switch to 8.5 when we make sure it works
allows to run without jq installed

* Apply suggestions from code review

Co-authored-by: Kévin Dunglas <kevin@dunglas.fr>

---------

Co-authored-by: Kévin Dunglas <kevin@dunglas.fr>

* chore: update Go and toolchain version (#1526)

* apply suggestions one be one - scriptpath only

* generate unique worker names by filename and number

* support worker config from embedded apps

* rename back to make sure we don't accidentally add FrankenPHPApp workers to the slice

* fix test after changing error message

* use 🧩 for module workers

* use 🌍 for global workers :)

* revert 1c414cebbc

* revert 4cc8893ced

* apply suggestions

* add dynamic config loading test of module worker

* fix test

* minor changes

---------

Signed-off-by: Romain Bastide <romain.bastide@orange.com>
Co-authored-by: Kévin Dunglas <kevin@dunglas.fr>
Co-authored-by: Indra Gunawan <hello@indra.my.id>
Co-authored-by: Romain Bastide <romain.bastide@orange.com>
2025-05-05 16:14:19 +02:00

135 lines
3.6 KiB
Go

package frankenphp
import (
"log/slog"
"net/http"
"path/filepath"
"sync"
"sync/atomic"
"github.com/dunglas/frankenphp/internal/fastabs"
)
// RequestOption instances allow to configure a FrankenPHP Request.
type RequestOption func(h *frankenPHPContext) error
var (
documentRootCache sync.Map
documentRootCacheLen atomic.Uint32
)
// WithRequestDocumentRoot sets the root directory of the PHP application.
// if resolveSymlink is true, oath declared as root directory will be resolved
// to its absolute value after the evaluation of any symbolic links.
// Due to the nature of PHP opcache, root directory path is cached: when
// using a symlinked directory as root this could generate errors when
// symlink is changed without PHP being restarted; enabling this
// directive will set $_SERVER['DOCUMENT_ROOT'] to the real directory path.
func WithRequestDocumentRoot(documentRoot string, resolveSymlink bool) RequestOption {
return func(o *frankenPHPContext) (err error) {
v, ok := documentRootCache.Load(documentRoot)
if !ok {
// make sure file root is absolute
v, err = fastabs.FastAbs(documentRoot)
if err != nil {
return err
}
// prevent the cache to grow forever, this is a totally arbitrary value
if documentRootCacheLen.Load() < 1024 {
documentRootCache.LoadOrStore(documentRoot, v)
documentRootCacheLen.Add(1)
}
}
if resolveSymlink {
if v, err = filepath.EvalSymlinks(v.(string)); err != nil {
return err
}
}
o.documentRoot = v.(string)
return nil
}
}
// WithRequestResolvedDocumentRoot is similar to WithRequestDocumentRoot
// but doesn't do any checks or resolving on the path to improve performance.
func WithRequestResolvedDocumentRoot(documentRoot string) RequestOption {
return func(o *frankenPHPContext) error {
o.documentRoot = documentRoot
return nil
}
}
// WithRequestSplitPath contains a list of split path strings.
//
// The path in the URL will be split into two, with the first piece ending
// with the value of splitPath. The first piece will be assumed as the
// actual resource (CGI script) name, and the second piece will be set to
// PATH_INFO for the CGI script to use.
//
// Future enhancements should be careful to avoid CVE-2019-11043,
// which can be mitigated with use of a try_files-like behavior
// that 404s if the FastCGI path info is not found.
func WithRequestSplitPath(splitPath []string) RequestOption {
return func(o *frankenPHPContext) error {
o.splitPath = splitPath
return nil
}
}
type PreparedEnv = map[string]string
func PrepareEnv(env map[string]string) PreparedEnv {
preparedEnv := make(PreparedEnv, len(env))
for k, v := range env {
preparedEnv[k+"\x00"] = v
}
return preparedEnv
}
// WithRequestEnv set CGI-like environment variables that will be available in $_SERVER.
// Values set with WithEnv always have priority over automatically populated values.
func WithRequestEnv(env map[string]string) RequestOption {
return WithRequestPreparedEnv(PrepareEnv(env))
}
func WithRequestPreparedEnv(env PreparedEnv) RequestOption {
return func(o *frankenPHPContext) error {
o.env = env
return nil
}
}
func WithOriginalRequest(r *http.Request) RequestOption {
return func(o *frankenPHPContext) error {
o.originalRequest = r
return nil
}
}
// WithRequestLogger sets the logger associated with the current request
func WithRequestLogger(logger *slog.Logger) RequestOption {
return func(o *frankenPHPContext) error {
o.logger = logger
return nil
}
}
// WithWorkerName sets the worker that should handle the request
func WithWorkerName(name string) RequestOption {
return func(o *frankenPHPContext) error {
o.workerName = name
return nil
}
}