refactor: cleaner cgi string handling

Introduces C-side interned string registry (frankenphp_strings) and a frankenphp_server_vars struct to bulk-register known $_SERVER variables with pre-sized hashtable capacity.
This commit is contained in:
Alexander Stecher
2026-03-04 17:20:24 +01:00
committed by GitHub
parent 27ff6b49d8
commit 356d2e1745
6 changed files with 305 additions and 244 deletions

203
cgi.go
View File

@@ -1,13 +1,13 @@
package frankenphp
// #cgo nocallback frankenphp_register_bulk
// #cgo nocallback frankenphp_register_variables_from_request_info
// #cgo nocallback frankenphp_register_server_vars
// #cgo nocallback frankenphp_register_variable_safe
// #cgo nocallback frankenphp_register_single
// #cgo noescape frankenphp_register_bulk
// #cgo noescape frankenphp_register_variables_from_request_info
// #cgo nocallback frankenphp_register_known_variable
// #cgo nocallback frankenphp_init_persistent_string
// #cgo noescape frankenphp_register_server_vars
// #cgo noescape frankenphp_register_variable_safe
// #cgo noescape frankenphp_register_single
// #cgo noescape frankenphp_register_known_variable
// #cgo noescape frankenphp_init_persistent_string
// #include "frankenphp.h"
// #include <php_variables.h>
import "C"
@@ -26,47 +26,6 @@ import (
"golang.org/x/text/search"
)
// Protocol versions, in Apache mod_ssl format: https://httpd.apache.org/docs/current/mod/mod_ssl.html
// Note that these are slightly different from SupportedProtocols in caddytls/config.go
var tlsProtocolStrings = map[uint16]string{
tls.VersionTLS10: "TLSv1",
tls.VersionTLS11: "TLSv1.1",
tls.VersionTLS12: "TLSv1.2",
tls.VersionTLS13: "TLSv1.3",
}
// Known $_SERVER keys
var knownServerKeys = []string{
"CONTENT_LENGTH",
"DOCUMENT_ROOT",
"DOCUMENT_URI",
"GATEWAY_INTERFACE",
"HTTP_HOST",
"HTTPS",
"PATH_INFO",
"PHP_SELF",
"REMOTE_ADDR",
"REMOTE_HOST",
"REMOTE_PORT",
"REQUEST_SCHEME",
"SCRIPT_FILENAME",
"SCRIPT_NAME",
"SERVER_NAME",
"SERVER_PORT",
"SERVER_PROTOCOL",
"SERVER_SOFTWARE",
"SSL_PROTOCOL",
"SSL_CIPHER",
"AUTH_TYPE",
"REMOTE_IDENT",
"CONTENT_TYPE",
"PATH_TRANSLATED",
"QUERY_STRING",
"REMOTE_USER",
"REQUEST_METHOD",
"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{
@@ -87,7 +46,6 @@ var cStringHTTPMethods = map[string]*C.char{
// Inspired by https://github.com/caddyserver/caddy/blob/master/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go
func addKnownVariablesToServer(fc *frankenPHPContext, trackVarsArray *C.zval) {
request := fc.request
keys := mainThread.knownServerKeys
// Separate remote IP and port; more lenient than net.SplitHostPort
var ip, port string
if idx := strings.LastIndex(request.RemoteAddr, ":"); idx > -1 {
@@ -102,24 +60,21 @@ func addKnownVariablesToServer(fc *frankenPHPContext, trackVarsArray *C.zval) {
ip = ip[1 : len(ip)-1]
}
var https, sslProtocol, sslCipher, rs string
var rs, https, sslProtocol *C.zend_string
var sslCipher string
if request.TLS == nil {
rs = "http"
https = ""
sslProtocol = ""
rs = C.frankenphp_strings.httpLowercase
https = C.frankenphp_strings.empty
sslProtocol = C.frankenphp_strings.empty
sslCipher = ""
} else {
rs = "https"
https = "on"
rs = C.frankenphp_strings.httpsLowercase
https = C.frankenphp_strings.on
// 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
} else {
sslProtocol = ""
}
sslProtocol = tlsProtocol(request.TLS.Version)
if request.TLS.CipherSuite != 0 {
sslCipher = tls.CipherSuiteName(request.TLS.CipherSuite)
@@ -139,9 +94,9 @@ func addKnownVariablesToServer(fc *frankenPHPContext, trackVarsArray *C.zval) {
// even if the port is the default port for the scheme and could otherwise be omitted from a URI.
// https://tools.ietf.org/html/rfc3875#section-4.1.15
switch rs {
case "https":
case C.frankenphp_strings.httpsLowercase:
reqPort = "443"
case "http":
case C.frankenphp_strings.httpLowercase:
reqPort = "80"
}
}
@@ -156,59 +111,59 @@ func addKnownVariablesToServer(fc *frankenPHPContext, trackVarsArray *C.zval) {
requestURI = fc.requestURI
}
C.frankenphp_register_bulk(
trackVarsArray,
packCgiVariable(keys["REMOTE_ADDR"], ip),
packCgiVariable(keys["REMOTE_HOST"], ip),
packCgiVariable(keys["REMOTE_PORT"], port),
packCgiVariable(keys["DOCUMENT_ROOT"], fc.documentRoot),
packCgiVariable(keys["PATH_INFO"], fc.pathInfo),
packCgiVariable(keys["PHP_SELF"], ensureLeadingSlash(request.URL.Path)),
packCgiVariable(keys["DOCUMENT_URI"], fc.docURI),
packCgiVariable(keys["SCRIPT_FILENAME"], fc.scriptFilename),
packCgiVariable(keys["SCRIPT_NAME"], fc.scriptName),
packCgiVariable(keys["HTTPS"], https),
packCgiVariable(keys["SSL_PROTOCOL"], sslProtocol),
packCgiVariable(keys["REQUEST_SCHEME"], rs),
packCgiVariable(keys["SERVER_NAME"], reqHost),
packCgiVariable(keys["SERVER_PORT"], serverPort),
// Variables defined in CGI 1.1 spec
// Some variables are unused but cleared explicitly to prevent
// the parent environment from interfering.
// These values can not be overridden
packCgiVariable(keys["CONTENT_LENGTH"], contentLength),
packCgiVariable(keys["GATEWAY_INTERFACE"], "CGI/1.1"),
packCgiVariable(keys["SERVER_PROTOCOL"], request.Proto),
packCgiVariable(keys["SERVER_SOFTWARE"], "FrankenPHP"),
packCgiVariable(keys["HTTP_HOST"], request.Host),
// These values are always empty but must be defined:
packCgiVariable(keys["AUTH_TYPE"], ""),
packCgiVariable(keys["REMOTE_IDENT"], ""),
// Request uri of the original request
packCgiVariable(keys["REQUEST_URI"], requestURI),
packCgiVariable(keys["SSL_CIPHER"], sslCipher),
)
requestPath := ensureLeadingSlash(request.URL.Path)
// These values are already present in the SG(request_info), so we'll register them from there
C.frankenphp_register_variables_from_request_info(
trackVarsArray,
keys["CONTENT_TYPE"],
keys["PATH_TRANSLATED"],
keys["QUERY_STRING"],
keys["REMOTE_USER"],
keys["REQUEST_METHOD"],
)
}
C.frankenphp_register_server_vars(trackVarsArray, C.frankenphp_server_vars{
// approximate total length to avoid array re-hashing:
// 28 CGI vars + headers + environment
total_num_vars: C.size_t(28 + len(request.Header) + len(fc.env) + lengthOfEnv),
func packCgiVariable(key *C.zend_string, value string) C.ht_key_value_pair {
return C.ht_key_value_pair{key, toUnsafeChar(value), C.size_t(len(value))}
// CGI vars with variable values
remote_addr: toUnsafeChar(ip),
remote_addr_len: C.size_t(len(ip)),
remote_host: toUnsafeChar(ip),
remote_host_len: C.size_t(len(ip)),
remote_port: toUnsafeChar(port),
remote_port_len: C.size_t(len(port)),
document_root: toUnsafeChar(fc.documentRoot),
document_root_len: C.size_t(len(fc.documentRoot)),
path_info: toUnsafeChar(fc.pathInfo),
path_info_len: C.size_t(len(fc.pathInfo)),
php_self: toUnsafeChar(requestPath),
php_self_len: C.size_t(len(requestPath)),
document_uri: toUnsafeChar(fc.docURI),
document_uri_len: C.size_t(len(fc.docURI)),
script_filename: toUnsafeChar(fc.scriptFilename),
script_filename_len: C.size_t(len(fc.scriptFilename)),
script_name: toUnsafeChar(fc.scriptName),
script_name_len: C.size_t(len(fc.scriptName)),
server_name: toUnsafeChar(reqHost),
server_name_len: C.size_t(len(reqHost)),
server_port: toUnsafeChar(serverPort),
server_port_len: C.size_t(len(serverPort)),
content_length: toUnsafeChar(contentLength),
content_length_len: C.size_t(len(contentLength)),
server_protocol: toUnsafeChar(request.Proto),
server_protocol_len: C.size_t(len(request.Proto)),
http_host: toUnsafeChar(request.Host),
http_host_len: C.size_t(len(request.Host)),
request_uri: toUnsafeChar(requestURI),
request_uri_len: C.size_t(len(requestURI)),
ssl_cipher: toUnsafeChar(sslCipher),
ssl_cipher_len: C.size_t(len(sslCipher)),
// CGI vars with known values
request_scheme: rs, // "http" or "https"
ssl_protocol: sslProtocol, // values from tlsProtocol
https: https, // "on" or empty
})
}
func addHeadersToServer(ctx context.Context, request *http.Request, trackVarsArray *C.zval) {
for field, val := range request.Header {
if k := mainThread.commonHeaders[field]; k != nil {
if k := commonHeaders[field]; k != nil {
v := strings.Join(val, ", ")
C.frankenphp_register_single(k, toUnsafeChar(v), C.size_t(len(v)), trackVarsArray)
C.frankenphp_register_known_variable(k, toUnsafeChar(v), C.size_t(len(v)), trackVarsArray)
continue
}
@@ -227,8 +182,8 @@ func addPreparedEnvToServer(fc *frankenPHPContext, trackVarsArray *C.zval) {
fc.env = nil
}
//export go_register_variables
func go_register_variables(threadIndex C.uintptr_t, trackVarsArray *C.zval) {
//export go_register_server_variables
func go_register_server_variables(threadIndex C.uintptr_t, trackVarsArray *C.zval) {
thread := phpThreads[threadIndex]
fc := thread.frankenPHPContext()
@@ -410,8 +365,32 @@ func ensureLeadingSlash(path string) string {
return "/" + path
}
// toUnsafeChar returns a *C.char pointing at the backing bytes the Go string.
// If C does not store the string, it may be passed directly in a Cgo call (most efficient).
// If C stores the string, it must be pinned explicitly instead (inefficient).
// C may never modify the string.
func toUnsafeChar(s string) *C.char {
sData := unsafe.StringData(s)
return (*C.char)(unsafe.Pointer(sData))
return (*C.char)(unsafe.Pointer(unsafe.StringData(s)))
}
// initialize a global zend_string that must never be freed and is ignored by GC
func newPersistentZendString(str string) *C.zend_string {
return C.frankenphp_init_persistent_string(toUnsafeChar(str), C.size_t(len(str)))
}
// Protocol versions, in Apache mod_ssl format: https://httpd.apache.org/docs/current/mod/mod_ssl.html
// Note that these are slightly different from SupportedProtocols in caddytls/config.go
func tlsProtocol(proto uint16) *C.zend_string {
switch proto {
case tls.VersionTLS10:
return C.frankenphp_strings.tls1
case tls.VersionTLS11:
return C.frankenphp_strings.tls11
case tls.VersionTLS12:
return C.frankenphp_strings.tls12
case tls.VersionTLS13:
return C.frankenphp_strings.tls13
default:
return C.frankenphp_strings.empty
}
}

13
env.go
View File

@@ -1,7 +1,5 @@
package frankenphp
// #cgo nocallback frankenphp_init_persistent_string
// #cgo noescape frankenphp_init_persistent_string
// #include "frankenphp.h"
// #include "types.h"
import "C"
@@ -10,12 +8,17 @@ import (
"strings"
)
var lengthOfEnv = 0
//export go_init_os_env
func go_init_os_env(mainThreadEnv *C.zend_array) {
for _, envVar := range os.Environ() {
fullEnv := os.Environ()
lengthOfEnv = len(fullEnv)
for _, envVar := range fullEnv {
key, val, _ := strings.Cut(envVar, "=")
zkey := C.frankenphp_init_persistent_string(toUnsafeChar(key), C.size_t(len(key)))
zStr := C.frankenphp_init_persistent_string(toUnsafeChar(val), C.size_t(len(val)))
zkey := newPersistentZendString(key)
zStr := newPersistentZendString(val)
C.__hash_update_string__(mainThreadEnv, zkey, zStr)
}
}

View File

@@ -80,6 +80,7 @@ frankenphp_config frankenphp_get_config() {
bool should_filter_var = 0;
bool original_user_abort_setting = 0;
frankenphp_interned_strings_t frankenphp_strings = {0};
HashTable *main_thread_env = NULL;
__thread uintptr_t thread_index;
@@ -572,6 +573,11 @@ PHP_FUNCTION(frankenphp_handle_request) {
if (zend_call_function(&fci, &fcc) == SUCCESS && Z_TYPE(retval) != IS_UNDEF) {
callback_ret = &retval;
/* pass NULL instead of the NULL zval as return value */
if (Z_TYPE(retval) == IS_NULL) {
callback_ret = NULL;
}
}
/*
@@ -802,72 +808,53 @@ static inline void frankenphp_register_trusted_var(zend_string *z_key,
}
}
void frankenphp_register_single(zend_string *z_key, char *value, size_t val_len,
zval *track_vars_array) {
HashTable *ht = Z_ARRVAL_P(track_vars_array);
frankenphp_register_trusted_var(z_key, value, val_len, ht);
}
/* Register known $_SERVER variables in bulk to avoid cgo overhead */
void frankenphp_register_bulk(
zval *track_vars_array, ht_key_value_pair remote_addr,
ht_key_value_pair remote_host, ht_key_value_pair remote_port,
ht_key_value_pair document_root, ht_key_value_pair path_info,
ht_key_value_pair php_self, ht_key_value_pair document_uri,
ht_key_value_pair script_filename, ht_key_value_pair script_name,
ht_key_value_pair https, ht_key_value_pair ssl_protocol,
ht_key_value_pair request_scheme, ht_key_value_pair server_name,
ht_key_value_pair server_port, ht_key_value_pair content_length,
ht_key_value_pair gateway_interface, ht_key_value_pair server_protocol,
ht_key_value_pair server_software, ht_key_value_pair http_host,
ht_key_value_pair auth_type, ht_key_value_pair remote_ident,
ht_key_value_pair request_uri, ht_key_value_pair ssl_cipher) {
void frankenphp_register_server_vars(zval *track_vars_array,
frankenphp_server_vars vars) {
HashTable *ht = Z_ARRVAL_P(track_vars_array);
frankenphp_register_trusted_var(remote_addr.key, remote_addr.val,
remote_addr.val_len, ht);
frankenphp_register_trusted_var(remote_host.key, remote_host.val,
remote_host.val_len, ht);
frankenphp_register_trusted_var(remote_port.key, remote_port.val,
remote_port.val_len, ht);
frankenphp_register_trusted_var(document_root.key, document_root.val,
document_root.val_len, ht);
frankenphp_register_trusted_var(path_info.key, path_info.val,
path_info.val_len, ht);
frankenphp_register_trusted_var(php_self.key, php_self.val, php_self.val_len,
ht);
frankenphp_register_trusted_var(document_uri.key, document_uri.val,
document_uri.val_len, ht);
frankenphp_register_trusted_var(script_filename.key, script_filename.val,
script_filename.val_len, ht);
frankenphp_register_trusted_var(script_name.key, script_name.val,
script_name.val_len, ht);
frankenphp_register_trusted_var(https.key, https.val, https.val_len, ht);
frankenphp_register_trusted_var(ssl_protocol.key, ssl_protocol.val,
ssl_protocol.val_len, ht);
frankenphp_register_trusted_var(ssl_cipher.key, ssl_cipher.val,
ssl_cipher.val_len, ht);
frankenphp_register_trusted_var(request_scheme.key, request_scheme.val,
request_scheme.val_len, ht);
frankenphp_register_trusted_var(server_name.key, server_name.val,
server_name.val_len, ht);
frankenphp_register_trusted_var(server_port.key, server_port.val,
server_port.val_len, ht);
frankenphp_register_trusted_var(content_length.key, content_length.val,
content_length.val_len, ht);
frankenphp_register_trusted_var(gateway_interface.key, gateway_interface.val,
gateway_interface.val_len, ht);
frankenphp_register_trusted_var(server_protocol.key, server_protocol.val,
server_protocol.val_len, ht);
frankenphp_register_trusted_var(server_software.key, server_software.val,
server_software.val_len, ht);
frankenphp_register_trusted_var(http_host.key, http_host.val,
http_host.val_len, ht);
frankenphp_register_trusted_var(auth_type.key, auth_type.val,
auth_type.val_len, ht);
frankenphp_register_trusted_var(remote_ident.key, remote_ident.val,
remote_ident.val_len, ht);
frankenphp_register_trusted_var(request_uri.key, request_uri.val,
request_uri.val_len, ht);
zend_hash_extend(ht, vars.total_num_vars, 0);
// update values with variable strings
#define FRANKENPHP_REGISTER_VAR(name) \
frankenphp_register_trusted_var(frankenphp_strings.name, vars.name, \
vars.name##_len, ht)
FRANKENPHP_REGISTER_VAR(remote_addr);
FRANKENPHP_REGISTER_VAR(remote_host);
FRANKENPHP_REGISTER_VAR(remote_port);
FRANKENPHP_REGISTER_VAR(document_root);
FRANKENPHP_REGISTER_VAR(path_info);
FRANKENPHP_REGISTER_VAR(php_self);
FRANKENPHP_REGISTER_VAR(document_uri);
FRANKENPHP_REGISTER_VAR(script_filename);
FRANKENPHP_REGISTER_VAR(script_name);
FRANKENPHP_REGISTER_VAR(ssl_cipher);
FRANKENPHP_REGISTER_VAR(server_name);
FRANKENPHP_REGISTER_VAR(server_port);
FRANKENPHP_REGISTER_VAR(content_length);
FRANKENPHP_REGISTER_VAR(server_protocol);
FRANKENPHP_REGISTER_VAR(http_host);
FRANKENPHP_REGISTER_VAR(request_uri);
#undef FRANKENPHP_REGISTER_VAR
/* update values with hard-coded zend_strings */
zval zv;
ZVAL_STR(&zv, frankenphp_strings.cgi11);
zend_hash_update_ind(ht, frankenphp_strings.gateway_interface, &zv);
ZVAL_STR(&zv, frankenphp_strings.frankenphp);
zend_hash_update_ind(ht, frankenphp_strings.server_software, &zv);
ZVAL_STR(&zv, vars.request_scheme);
zend_hash_update_ind(ht, frankenphp_strings.request_scheme, &zv);
ZVAL_STR(&zv, vars.ssl_protocol);
zend_hash_update_ind(ht, frankenphp_strings.ssl_protocol, &zv);
ZVAL_STR(&zv, vars.https);
zend_hash_update_ind(ht, frankenphp_strings.https, &zv);
/* update values with always empty strings */
ZVAL_EMPTY_STRING(&zv);
zend_hash_update_ind(ht, frankenphp_strings.auth_type, &zv);
zend_hash_update_ind(ht, frankenphp_strings.remote_ident, &zv);
}
/** Create an immutable zend_string that lasts for the whole process **/
@@ -882,7 +869,22 @@ zend_string *frankenphp_init_persistent_string(const char *string, size_t len) {
return z_string;
}
static void
/* initialize all hard-coded zend_strings once per process */
static void frankenphp_init_interned_strings(void) {
if (frankenphp_strings.remote_addr != NULL) {
return; /* already initialized */
}
#define F_INITIALIZE_FIELD(name, str) \
frankenphp_strings.name = \
frankenphp_init_persistent_string(str, sizeof(str) - 1);
FRANKENPHP_INTERNED_STRINGS_LIST(F_INITIALIZE_FIELD)
#undef F_INITIALIZE_FIELD
}
/* Register variables from SG(request_info) into $_SERVER */
static inline void
frankenphp_register_variable_from_request_info(zend_string *zKey, char *value,
bool must_be_present,
zval *track_vars_array) {
@@ -895,23 +897,31 @@ frankenphp_register_variable_from_request_info(zend_string *zKey, char *value,
}
}
void frankenphp_register_variables_from_request_info(
zval *track_vars_array, zend_string *content_type,
zend_string *path_translated, zend_string *query_string,
zend_string *auth_user, zend_string *request_method) {
static void
frankenphp_register_variables_from_request_info(zval *track_vars_array) {
frankenphp_register_variable_from_request_info(
content_type, (char *)SG(request_info).content_type, true,
frankenphp_strings.content_type, (char *)SG(request_info).content_type,
true, track_vars_array);
frankenphp_register_variable_from_request_info(
frankenphp_strings.path_translated,
(char *)SG(request_info).path_translated, false, track_vars_array);
frankenphp_register_variable_from_request_info(
frankenphp_strings.query_string, SG(request_info).query_string, true,
track_vars_array);
frankenphp_register_variable_from_request_info(
path_translated, (char *)SG(request_info).path_translated, false,
frankenphp_strings.remote_user, (char *)SG(request_info).auth_user, false,
track_vars_array);
frankenphp_register_variable_from_request_info(
query_string, SG(request_info).query_string, true, track_vars_array);
frankenphp_register_variable_from_request_info(
auth_user, (char *)SG(request_info).auth_user, false, track_vars_array);
frankenphp_register_variable_from_request_info(
request_method, (char *)SG(request_info).request_method, false,
track_vars_array);
frankenphp_strings.request_method,
(char *)SG(request_info).request_method, false, track_vars_array);
}
/* Only hard-coded keys may be registered this way */
void frankenphp_register_known_variable(zend_string *z_key, char *value,
size_t val_len,
zval *track_vars_array) {
frankenphp_register_trusted_var(z_key, value, val_len,
Z_ARRVAL_P(track_vars_array));
}
/* variables with user-defined keys must be registered safely
@@ -950,7 +960,11 @@ static void frankenphp_register_variables(zval *track_vars_array) {
*/
zend_hash_copy(Z_ARR_P(track_vars_array), main_thread_env, NULL);
go_register_variables(thread_index, track_vars_array);
/* import CGI variables from the request context in go */
go_register_server_variables(thread_index, track_vars_array);
/* Some variables are already present in SG(request_info) */
frankenphp_register_variables_from_request_info(track_vars_array);
}
static void frankenphp_log_message(const char *message, int syslog_type_int) {
@@ -1119,6 +1133,8 @@ static void *php_main(void *arg) {
frankenphp_sapi_module.ini_entries = php_ini_overrides;
}
frankenphp_init_interned_strings();
frankenphp_sapi_module.startup(&frankenphp_sapi_module);
/* check if a default filter is set in php.ini and only filter if

View File

@@ -500,11 +500,11 @@ func go_apache_request_headers(threadIndex C.uintptr_t) (*C.go_string, C.size_t)
return sd, C.size_t(len(fc.request.Header))
}
func addHeader(ctx context.Context, fc *frankenPHPContext, cString *C.char, length C.int) {
key, val := splitRawHeader(cString, int(length))
func addHeader(ctx context.Context, fc *frankenPHPContext, h *C.sapi_header_struct) {
key, val := splitRawHeader(h.header, int(h.header_len))
if key == "" {
if fc.logger.Enabled(ctx, slog.LevelDebug) {
fc.logger.LogAttrs(ctx, slog.LevelDebug, "invalid header", slog.String("header", C.GoStringN(cString, length)))
fc.logger.LogAttrs(ctx, slog.LevelDebug, "invalid header", slog.String("header", C.GoStringN(h.header, C.int(h.header_len))))
}
return
@@ -564,7 +564,7 @@ func go_write_headers(threadIndex C.uintptr_t, status C.int, headers *C.zend_lli
for current != nil {
h := (*C.sapi_header_struct)(unsafe.Pointer(&(current.data)))
addHeader(thread.context(), fc, h.header, C.int(h.header_len))
addHeader(thread.context(), fc, h)
current = current.next
}

View File

@@ -57,11 +57,96 @@ typedef struct go_string {
char *data;
} go_string;
typedef struct ht_key_value_pair {
zend_string *key;
char *val;
size_t val_len;
} ht_key_value_pair;
typedef struct frankenphp_server_vars {
size_t total_num_vars;
char *remote_addr;
size_t remote_addr_len;
char *remote_host;
size_t remote_host_len;
char *remote_port;
size_t remote_port_len;
char *document_root;
size_t document_root_len;
char *path_info;
size_t path_info_len;
char *php_self;
size_t php_self_len;
char *document_uri;
size_t document_uri_len;
char *script_filename;
size_t script_filename_len;
char *script_name;
size_t script_name_len;
char *server_name;
size_t server_name_len;
char *server_port;
size_t server_port_len;
char *content_length;
size_t content_length_len;
char *server_protocol;
size_t server_protocol_len;
char *http_host;
size_t http_host_len;
char *request_uri;
size_t request_uri_len;
char *ssl_cipher;
size_t ssl_cipher_len;
zend_string *request_scheme;
zend_string *ssl_protocol;
zend_string *https;
} frankenphp_server_vars;
/**
* Cached interned strings for memory and performance benefits
* Add more hard-coded strings here if needed
*/
#define FRANKENPHP_INTERNED_STRINGS_LIST(X) \
X(remote_addr, "REMOTE_ADDR") \
X(remote_host, "REMOTE_HOST") \
X(remote_port, "REMOTE_PORT") \
X(document_root, "DOCUMENT_ROOT") \
X(path_info, "PATH_INFO") \
X(php_self, "PHP_SELF") \
X(document_uri, "DOCUMENT_URI") \
X(script_filename, "SCRIPT_FILENAME") \
X(script_name, "SCRIPT_NAME") \
X(https, "HTTPS") \
X(httpsLowercase, "https") \
X(httpLowercase, "http") \
X(ssl_protocol, "SSL_PROTOCOL") \
X(request_scheme, "REQUEST_SCHEME") \
X(server_name, "SERVER_NAME") \
X(server_port, "SERVER_PORT") \
X(content_length, "CONTENT_LENGTH") \
X(server_protocol, "SERVER_PROTOCOL") \
X(http_host, "HTTP_HOST") \
X(request_uri, "REQUEST_URI") \
X(ssl_cipher, "SSL_CIPHER") \
X(server_software, "SERVER_SOFTWARE") \
X(frankenphp, "FrankenPHP") \
X(gateway_interface, "GATEWAY_INTERFACE") \
X(cgi11, "CGI/1.1") \
X(auth_type, "AUTH_TYPE") \
X(remote_ident, "REMOTE_IDENT") \
X(content_type, "CONTENT_TYPE") \
X(path_translated, "PATH_TRANSLATED") \
X(query_string, "QUERY_STRING") \
X(remote_user, "REMOTE_USER") \
X(request_method, "REQUEST_METHOD") \
X(tls1, "TLSv1") \
X(tls11, "TLSv1.1") \
X(tls12, "TLSv1.2") \
X(tls13, "TLSv1.3") \
X(on, "on") \
X(empty, "")
typedef struct frankenphp_interned_strings_t {
#define F_DEFINE_STRUCT_FIELD(name, str) zend_string *name;
FRANKENPHP_INTERNED_STRINGS_LIST(F_DEFINE_STRUCT_FIELD)
#undef F_DEFINE_STRUCT_FIELD
} frankenphp_interned_strings_t;
extern frankenphp_interned_strings_t frankenphp_strings;
typedef struct frankenphp_version {
unsigned char major_version;
@@ -90,32 +175,17 @@ void frankenphp_update_local_thread_context(bool is_worker);
int frankenphp_execute_script_cli(char *script, int argc, char **argv,
bool eval);
void frankenphp_register_variables_from_request_info(
zval *track_vars_array, zend_string *content_type,
zend_string *path_translated, zend_string *query_string,
zend_string *auth_user, zend_string *request_method);
void frankenphp_register_known_variable(zend_string *z_key, char *value,
size_t val_len, zval *track_vars_array);
void frankenphp_register_variable_safe(char *key, char *var, size_t val_len,
zval *track_vars_array);
void frankenphp_register_server_vars(zval *track_vars_array,
frankenphp_server_vars vars);
zend_string *frankenphp_init_persistent_string(const char *string, size_t len);
int frankenphp_reset_opcache(void);
int frankenphp_get_current_memory_limit();
void frankenphp_register_single(zend_string *z_key, char *value, size_t val_len,
zval *track_vars_array);
void frankenphp_register_bulk(
zval *track_vars_array, ht_key_value_pair remote_addr,
ht_key_value_pair remote_host, ht_key_value_pair remote_port,
ht_key_value_pair document_root, ht_key_value_pair path_info,
ht_key_value_pair php_self, ht_key_value_pair document_uri,
ht_key_value_pair script_filename, ht_key_value_pair script_name,
ht_key_value_pair https, ht_key_value_pair ssl_protocol,
ht_key_value_pair request_scheme, ht_key_value_pair server_name,
ht_key_value_pair server_port, ht_key_value_pair content_length,
ht_key_value_pair gateway_interface, ht_key_value_pair server_protocol,
ht_key_value_pair server_software, ht_key_value_pair http_host,
ht_key_value_pair auth_type, ht_key_value_pair remote_ident,
ht_key_value_pair request_uri, ht_key_value_pair ssl_cipher);
void register_extensions(zend_module_entry **m, int len);
#endif

View File

@@ -1,9 +1,7 @@
package frankenphp
// #cgo nocallback frankenphp_new_main_thread
// #cgo nocallback frankenphp_init_persistent_string
// #cgo noescape frankenphp_new_main_thread
// #cgo noescape frankenphp_init_persistent_string
// #include "frankenphp.h"
// #include <php_variables.h>
import "C"
@@ -20,18 +18,17 @@ import (
// represents the main PHP thread
// the thread needs to keep running as long as all other threads are running
type phpMainThread struct {
state *state.ThreadState
done chan struct{}
numThreads int
maxThreads int
phpIni map[string]string
commonHeaders map[string]*C.zend_string
knownServerKeys map[string]*C.zend_string
state *state.ThreadState
done chan struct{}
numThreads int
maxThreads int
phpIni map[string]string
}
var (
phpThreads []*phpThread
mainThread *phpMainThread
phpThreads []*phpThread
mainThread *phpMainThread
commonHeaders map[string]*C.zend_string
)
// initPHPThreads starts the main PHP thread,
@@ -111,15 +108,11 @@ func (mainThread *phpMainThread) start() error {
mainThread.state.WaitFor(state.Ready)
// cache common request headers as zend_strings (HTTP_ACCEPT, HTTP_USER_AGENT, etc.)
mainThread.commonHeaders = make(map[string]*C.zend_string, len(phpheaders.CommonRequestHeaders))
for key, phpKey := range phpheaders.CommonRequestHeaders {
mainThread.commonHeaders[key] = C.frankenphp_init_persistent_string(C.CString(phpKey), C.size_t(len(phpKey)))
}
// cache $_SERVER keys as zend_strings (SERVER_PROTOCOL, SERVER_SOFTWARE, etc.)
mainThread.knownServerKeys = make(map[string]*C.zend_string, len(knownServerKeys))
for _, phpKey := range knownServerKeys {
mainThread.knownServerKeys[phpKey] = C.frankenphp_init_persistent_string(toUnsafeChar(phpKey), C.size_t(len(phpKey)))
if commonHeaders == nil {
commonHeaders = make(map[string]*C.zend_string, len(phpheaders.CommonRequestHeaders))
for key, phpKey := range phpheaders.CommonRequestHeaders {
commonHeaders[key] = newPersistentZendString(phpKey)
}
}
return nil