diff --git a/cgi.go b/cgi.go index 527f4e97c..e0b185731 100644 --- a/cgi.go +++ b/cgi.go @@ -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 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), - ) - - // 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"], - ) -} - -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))} + requestPath := ensureLeadingSlash(request.URL.Path) + + 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), + + // 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(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))) +} - return (*C.char)(unsafe.Pointer(sData)) +// 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 + } } diff --git a/env.go b/env.go index 6e99f1769..fbf6c86e6 100644 --- a/env.go +++ b/env.go @@ -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) } } diff --git a/frankenphp.c b/frankenphp.c index a2e2f6481..73bfd7cf7 100644 --- a/frankenphp.c +++ b/frankenphp.c @@ -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, - track_vars_array); + frankenphp_strings.content_type, (char *)SG(request_info).content_type, + true, track_vars_array); frankenphp_register_variable_from_request_info( - path_translated, (char *)SG(request_info).path_translated, false, - track_vars_array); + frankenphp_strings.path_translated, + (char *)SG(request_info).path_translated, 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_strings.query_string, SG(request_info).query_string, true, + track_vars_array); frankenphp_register_variable_from_request_info( - request_method, (char *)SG(request_info).request_method, false, + frankenphp_strings.remote_user, (char *)SG(request_info).auth_user, false, track_vars_array); + frankenphp_register_variable_from_request_info( + 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) { @@ -1093,6 +1107,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 diff --git a/frankenphp.go b/frankenphp.go index 3d571b226..501c7d64d 100644 --- a/frankenphp.go +++ b/frankenphp.go @@ -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 } diff --git a/frankenphp.h b/frankenphp.h index 51833cf6f..f25cb8512 100644 --- a/frankenphp.h +++ b/frankenphp.h @@ -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 diff --git a/phpmainthread.go b/phpmainthread.go index b02cb3170..ba3917e84 100644 --- a/phpmainthread.go +++ b/phpmainthread.go @@ -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 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