diff --git a/caddy/module.go b/caddy/module.go index eba65bc461..87094bc6a6 100644 --- a/caddy/module.go +++ b/caddy/module.go @@ -198,6 +198,12 @@ func (f *FrankenPHPModule) ServeHTTP(w http.ResponseWriter, r *http.Request, _ c return caddyhttp.Error(http.StatusInternalServerError, err) } + placeholders := fr.Context().Value(frankenphp.PlaceholdersContextKey).(map[string]string) + + for k, v := range placeholders { + repl.Set(k, v) + } + return nil } diff --git a/context.go b/context.go index 2e897cd5c1..1a592bf605 100644 --- a/context.go +++ b/context.go @@ -36,6 +36,9 @@ type frankenPHPContext struct { startedAt time.Time } +type placeholdersContextKeyStruct struct{} +var PlaceholdersContextKey = placeholdersContextKeyStruct{} + // fromContext extracts the frankenPHPContext from a context. func fromContext(ctx context.Context) (fctx *frankenPHPContext, ok bool) { fctx, ok = ctx.Value(contextKey).(*frankenPHPContext) @@ -80,8 +83,11 @@ func NewRequestWithContext(r *http.Request, opts ...RequestOption) (*http.Reques } c := context.WithValue(r.Context(), contextKey, fc) + c = context.WithValue(c, PlaceholdersContextKey, make(map[string]string)) + r = r.WithContext(c) + fc.request = r - return r.WithContext(c), nil + return r, nil } // newDummyContext creates a fake context from a request path diff --git a/docs/config.md b/docs/config.md index 64654a18fb..8ade842244 100644 --- a/docs/config.md +++ b/docs/config.md @@ -206,6 +206,28 @@ where the FrankenPHP process was started. You can instead also specify one or mo The file watcher is based on [e-dant/watcher](https://github.com/e-dant/watcher). +### Placeholders + +You can set Caddy placeholders from your PHP code using the `frankenphp_set_caddy_placeholder(string $key, string $value)` function. +These placeholders can then be used in Caddy directives like `log_append`. + +Example usage: + +```php +frankenphp_set_caddy_placeholder('frankenphp.custom_placeholder', 'Look at my placeholder!'); +``` + +```caddyfile +http:// { + log + route { + # ... + + log_append my_placeholder {frankenphp.custom_placeholder} + } +} +``` + ## Matching the worker to a path In traditional PHP applications, scripts are always placed in the public directory. diff --git a/frankenphp.c b/frankenphp.c index 6b84380430..f4c05e49ab 100644 --- a/frankenphp.c +++ b/frankenphp.c @@ -342,6 +342,18 @@ PHP_FUNCTION(frankenphp_getenv) { } } /* }}} */ +/* {{{ Set a placeholder on the current Caddy request */ +PHP_FUNCTION(frankenphp_set_caddy_placeholder) { + zend_string *key, *value; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_STR(key) + Z_PARAM_STR(value) + ZEND_PARSE_PARAMETERS_END(); + + go_set_caddy_placeholder(thread_index, ZSTR_VAL(key), ZSTR_VAL(value)); +} /* }}} */ + /* {{{ Fetch all HTTP request headers */ PHP_FUNCTION(frankenphp_request_headers) { ZEND_PARSE_PARAMETERS_NONE(); diff --git a/frankenphp.go b/frankenphp.go index 78d25308c3..868ec87519 100644 --- a/frankenphp.go +++ b/frankenphp.go @@ -565,6 +565,21 @@ func go_log(message *C.char, level C.int) { } } +//export go_set_caddy_placeholder +func go_set_caddy_placeholder(threadIndex C.uintptr_t, key *C.char, value *C.char) { + fc := phpThreads[threadIndex].getRequestContext() + + placeholders, _ := fc.request.Context().Value(PlaceholdersContextKey).(map[string]string) + + if placeholders == nil { + logger.LogAttrs(context.Background(), slog.LevelDebug, "frankenphp_set_caddy_placeholder() called in non-HTTP context", slog.String("worker", fc.scriptFilename)) + + return + } + + placeholders[C.GoString(key)] = C.GoString(value) +} + //export go_is_context_done func go_is_context_done(threadIndex C.uintptr_t) C.bool { return C.bool(phpThreads[threadIndex].getRequestContext().isDone) diff --git a/frankenphp.h b/frankenphp.h index c17df6061a..d72e2aa991 100644 --- a/frankenphp.h +++ b/frankenphp.h @@ -56,6 +56,9 @@ int frankenphp_execute_script(char *file_name); int frankenphp_execute_script_cli(char *script, int argc, char **argv, bool eval); +void frankenphp_set_caddy_placeholder(uintptr_t thread_index, zend_string *key, + zend_string *value); + void frankenphp_register_variables_from_request_info( zval *track_vars_array, zend_string *content_type, zend_string *path_translated, zend_string *query_string, diff --git a/frankenphp.stub.php b/frankenphp.stub.php index 6c5a71cb5c..b0f109d575 100644 --- a/frankenphp.stub.php +++ b/frankenphp.stub.php @@ -32,3 +32,4 @@ function frankenphp_response_headers(): array|bool {} */ function apache_response_headers(): array|bool {} +function frankenphp_set_caddy_placeholder(string $key, string $value): void {} diff --git a/frankenphp_arginfo.h b/frankenphp_arginfo.h index c1bd7b550a..8126b36abb 100644 --- a/frankenphp_arginfo.h +++ b/frankenphp_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 05ebde17137c559e891362fba6524fad1e0a2dfe */ + * Stub hash: c5318079b1c5629258a1f4b682c7aaf327588b71 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_frankenphp_handle_request, 0, 1, _IS_BOOL, 0) @@ -30,11 +30,18 @@ ZEND_END_ARG_INFO() #define arginfo_apache_response_headers arginfo_frankenphp_response_headers +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX( + arginfo_frankenphp_set_caddy_placeholder, 0, 2, IS_VOID, 0) +ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0) +ZEND_ARG_TYPE_INFO(0, value, IS_STRING, 0) +ZEND_END_ARG_INFO() + ZEND_FUNCTION(frankenphp_handle_request); ZEND_FUNCTION(headers_send); ZEND_FUNCTION(frankenphp_finish_request); ZEND_FUNCTION(frankenphp_request_headers); ZEND_FUNCTION(frankenphp_response_headers); +ZEND_FUNCTION(frankenphp_set_caddy_placeholder); // clang-format off static const zend_function_entry ext_functions[] = { @@ -47,6 +54,7 @@ static const zend_function_entry ext_functions[] = { ZEND_FALIAS(getallheaders, frankenphp_request_headers, arginfo_getallheaders) ZEND_FE(frankenphp_response_headers, arginfo_frankenphp_response_headers) ZEND_FALIAS(apache_response_headers, frankenphp_response_headers, arginfo_apache_response_headers) + ZEND_FE(frankenphp_set_caddy_placeholder, arginfo_frankenphp_set_caddy_placeholder) ZEND_FE_END }; // clang-format on