Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 3 additions & 2 deletions caddy/admin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io"
"net/http"
"strings"
"sync"
"testing"

Expand Down Expand Up @@ -249,7 +250,7 @@ func TestAddModuleWorkerViaAdminApi(t *testing.T) {
initialDebugState := getDebugState(t, tester)
initialWorkerCount := 0
for _, thread := range initialDebugState.ThreadDebugStates {
if thread.Name != "" && thread.Name != "ready" {
if strings.HasPrefix(thread.Name, "Worker PHP Thread") {
initialWorkerCount++
}
}
Expand Down Expand Up @@ -286,7 +287,7 @@ func TestAddModuleWorkerViaAdminApi(t *testing.T) {
workerFound := false
filename, _ := fastabs.FastAbs("../testdata/worker-with-counter.php")
for _, thread := range updatedDebugState.ThreadDebugStates {
if thread.Name != "" && thread.Name != "ready" {
if strings.HasPrefix(thread.Name, "Worker PHP Thread") {
updatedWorkerCount++
if thread.Name == "Worker PHP Thread - "+filename {
workerFound = true
Expand Down
23 changes: 18 additions & 5 deletions caddy/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,11 @@ func (f *FrankenPHPModule) Provision(ctx caddy.Context) error {
f.ResolveRootSymlink = &rrs
}

// Always pre-compute absolute file names for fallback matching
for i := range f.Workers {
f.Workers[i].absFileName, _ = fastabs.FastAbs(f.Workers[i].FileName)
}

if !needReplacement(f.Root) {
root, err := fastabs.FastAbs(f.Root)
if err != nil {
Expand All @@ -145,13 +150,21 @@ func (f *FrankenPHPModule) Provision(ctx caddy.Context) error {

f.resolvedDocumentRoot = root

// Also resolve symlinks in worker file paths when resolve_root_symlink is true
// Resolve symlinks in worker file paths
for i, wc := range f.Workers {
if !filepath.IsAbs(wc.FileName) {
continue
if filepath.IsAbs(wc.FileName) {
resolvedPath, _ := filepath.EvalSymlinks(wc.FileName)
f.Workers[i].FileName = resolvedPath
f.Workers[i].absFileName = resolvedPath
}
resolvedPath, _ := filepath.EvalSymlinks(wc.FileName)
f.Workers[i].FileName = resolvedPath
}
}

// Pre-compute relative match paths for all workers (requires resolved document root)
docRootWithSep := f.resolvedDocumentRoot + string(filepath.Separator)
for i := range f.Workers {
if strings.HasPrefix(f.Workers[i].absFileName, docRootWithSep) {
f.Workers[i].matchRelPath = filepath.ToSlash(f.Workers[i].absFileName[len(f.resolvedDocumentRoot):])
}
}

Expand Down
26 changes: 21 additions & 5 deletions caddy/workerconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package caddy

import (
"net/http"
"path"
"path/filepath"
"strconv"

Expand Down Expand Up @@ -43,6 +44,8 @@ type workerConfig struct {

options []frankenphp.WorkerOption
requestOptions []frankenphp.RequestOption
absFileName string
matchRelPath string // pre-computed relative URL path for fast matching
}

func unmarshalWorker(d *caddyfile.Dispenser) (workerConfig, error) {
Expand Down Expand Up @@ -171,15 +174,28 @@ func (wc *workerConfig) inheritEnv(env map[string]string) {
}

func (wc *workerConfig) matchesPath(r *http.Request, documentRoot string) bool {

// try to match against a pattern if one is assigned
if len(wc.MatchPath) != 0 {
return (caddyhttp.MatchPath)(wc.MatchPath).Match(r)
}

// if there is no pattern, try to match against the actual path (in the public directory)
fullScriptPath, _ := fastabs.FastAbs(documentRoot + "/" + r.URL.Path)
absFileName, _ := fastabs.FastAbs(wc.FileName)
// fast path: compare the request URL path against the pre-computed relative path
if wc.matchRelPath != "" {
reqPath := r.URL.Path
if reqPath == wc.matchRelPath {
return true
}

// ensure leading slash for relative paths (see #2166)
if reqPath == "" || reqPath[0] != '/' {
reqPath = "/" + reqPath
}

return path.Clean(reqPath) == wc.matchRelPath
}

// fallback when documentRoot is dynamic (contains placeholders)
fullPath, _ := fastabs.FastAbs(filepath.Join(documentRoot, r.URL.Path))

return fullScriptPath == absFileName
return fullPath == wc.absFileName
}
33 changes: 26 additions & 7 deletions cgi.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,20 @@ var knownServerKeys = []string{
"REQUEST_URI",
}

// cStringHTTPMethods caches C string versions of common HTTP methods
// to avoid allocations in pinCString on every request.
var cStringHTTPMethods = map[string]*C.char{
"GET": C.CString("GET"),
"HEAD": C.CString("HEAD"),
"POST": C.CString("POST"),
"PUT": C.CString("PUT"),
"DELETE": C.CString("DELETE"),
"CONNECT": C.CString("CONNECT"),
"OPTIONS": C.CString("OPTIONS"),
"TRACE": C.CString("TRACE"),
"PATCH": C.CString("PATCH"),
}

// computeKnownVariables returns a set of CGI environment variables for the request.
//
// TODO: handle this case https://github.com/caddyserver/caddy/issues/3718
Expand All @@ -84,8 +98,9 @@ func addKnownVariablesToServer(fc *frankenPHPContext, trackVarsArray *C.zval) {
}

// Remove [] from IPv6 addresses
ip = strings.Replace(ip, "[", "", 1)
ip = strings.Replace(ip, "]", "", 1)
if len(ip) > 0 && ip[0] == '[' {
ip = ip[1 : len(ip)-1]
}

var https, sslProtocol, sslCipher, rs string

Expand All @@ -98,7 +113,7 @@ func addKnownVariablesToServer(fc *frankenPHPContext, trackVarsArray *C.zval) {
rs = "https"
https = "on"

// and pass the protocol details in a manner compatible with apache's mod_ssl
// and pass the protocol details in a manner compatible with Apache's mod_ssl
// (which is why these have an SSL_ prefix and not TLS_).
if v, ok := tlsProtocolStrings[request.TLS.Version]; ok {
sslProtocol = v
Expand Down Expand Up @@ -138,7 +153,7 @@ func addKnownVariablesToServer(fc *frankenPHPContext, trackVarsArray *C.zval) {
if fc.originalRequest != nil {
requestURI = fc.originalRequest.URL.RequestURI()
} else {
requestURI = request.URL.RequestURI()
requestURI = fc.requestURI
}

C.frankenphp_register_bulk(
Expand Down Expand Up @@ -252,7 +267,7 @@ func splitCgiPath(fc *frankenPHPContext) {
// TODO: is it possible to delay this and avoid saving everything in the context?
// SCRIPT_FILENAME is the absolute path of SCRIPT_NAME
fc.scriptFilename = sanitizedPathJoin(fc.documentRoot, fc.scriptName)
fc.worker = getWorkerByPath(fc.scriptFilename)
fc.worker = workersByPath[fc.scriptFilename]
}

var splitSearchNonASCII = search.New(language.Und, search.IgnoreCase)
Expand Down Expand Up @@ -329,7 +344,11 @@ func go_update_request_info(threadIndex C.uintptr_t, info *C.sapi_request_info)
return nil
}

info.request_method = thread.pinCString(request.Method)
if m, ok := cStringHTTPMethods[request.Method]; ok {
info.request_method = m
} else {
info.request_method = thread.pinCString(request.Method)
}
info.query_string = thread.pinCString(request.URL.RawQuery)
info.content_length = C.zend_long(request.ContentLength)

Expand All @@ -341,7 +360,7 @@ func go_update_request_info(threadIndex C.uintptr_t, info *C.sapi_request_info)
info.path_translated = thread.pinCString(sanitizedPathJoin(fc.documentRoot, fc.pathInfo)) // See: http://www.oreilly.com/openbook/cgi/ch02_04.html
}

info.request_uri = thread.pinCString(request.URL.RequestURI())
info.request_uri = thread.pinCString(fc.requestURI)

info.proto_num = C.int(request.ProtoMajor*1000 + request.ProtoMinor)

Expand Down
10 changes: 7 additions & 3 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@ type frankenPHPContext struct {
pathInfo string
scriptName string
scriptFilename string
requestURI string

// Whether the request is already closed by us
isDone bool

responseWriter http.ResponseWriter
handlerParameters any
handlerReturn any
responseWriter http.ResponseWriter
responseController *http.ResponseController
handlerParameters any
handlerReturn any

done chan any
startedAt time.Time
Expand Down Expand Up @@ -93,6 +95,8 @@ func NewRequestWithContext(r *http.Request, opts ...RequestOption) (*http.Reques
splitCgiPath(fc)
}

fc.requestURI = r.URL.RequestURI()

c := context.WithValue(r.Context(), contextKey, fc)

return r.WithContext(c), nil
Expand Down
35 changes: 17 additions & 18 deletions frankenphp.go
Original file line number Diff line number Diff line change
Expand Up @@ -611,7 +611,10 @@ func go_sapi_flush(threadIndex C.uintptr_t) bool {
return true
}

if err := http.NewResponseController(fc.responseWriter).Flush(); err != nil {
if fc.responseController == nil {
fc.responseController = http.NewResponseController(fc.responseWriter)
}
if err := fc.responseController.Flush(); err != nil {
ctx := thread.context()

if globalLogger.Enabled(ctx, slog.LevelWarn) {
Expand Down Expand Up @@ -683,34 +686,28 @@ func getLogger(threadIndex C.uintptr_t) (*slog.Logger, context.Context) {
func go_log(threadIndex C.uintptr_t, message *C.char, level C.int) {
logger, ctx := getLogger(threadIndex)

m := C.GoString(message)
le := syslogLevelInfo

if level >= C.int(syslogLevelEmerg) && level <= C.int(syslogLevelDebug) {
le = syslogLevel(level)
}

var slogLevel slog.Level
switch le {
case syslogLevelEmerg, syslogLevelAlert, syslogLevelCrit, syslogLevelErr:
if logger.Enabled(ctx, slog.LevelError) {
logger.LogAttrs(ctx, slog.LevelError, m, slog.String("syslog_level", le.String()))
}

slogLevel = slog.LevelError
case syslogLevelWarn:
if logger.Enabled(ctx, slog.LevelWarn) {
logger.LogAttrs(ctx, slog.LevelWarn, m, slog.String("syslog_level", le.String()))
}

slogLevel = slog.LevelWarn
case syslogLevelDebug:
if logger.Enabled(ctx, slog.LevelDebug) {
logger.LogAttrs(ctx, slog.LevelDebug, m, slog.String("syslog_level", le.String()))
}

slogLevel = slog.LevelDebug
default:
if logger.Enabled(ctx, slog.LevelInfo) {
logger.LogAttrs(ctx, slog.LevelInfo, m, slog.String("syslog_level", le.String()))
}
slogLevel = slog.LevelInfo
}

if !logger.Enabled(ctx, slogLevel) {
return
}

logger.LogAttrs(ctx, slogLevel, C.GoString(message), slog.String("syslog_level", le.String()))
}

//export go_log_attrs
Expand Down Expand Up @@ -805,6 +802,8 @@ func resetGlobals() {
globalCtx = context.Background()
globalLogger = slog.Default()
workers = nil
workersByName = nil
workersByPath = nil
watcherIsEnabled = false
globalMu.Unlock()
}
2 changes: 1 addition & 1 deletion internal/fastabs/filepath_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func init() {
}

canonicalWD, err := filepath.EvalSymlinks(wd)
if err != nil {
if err == nil {
wd = canonicalWD
}
}
Expand Down
Loading