mirror of
https://github.com/php/frankenphp.git
synced 2026-03-24 00:52:11 +01:00
perf: optimized request headers (#1335)
* Optimizes header registration. * Adds malformed cookie tests. * Sets key to NULL (releasing them is unnecessary) * Adjusts test. * Sanitizes null bytes anyways. * Sorts headers. * trigger * clang-format * More clang-format. * Updates headers and tests. * Adds header test. * Adds more headers. * Updates headers again. * ?Removes comments. * ?Reformats headers * ?Reformats headers * renames header files. * ?Renames test. * ?Fixes assertion. * test * test * test * Moves headers test to main package. * Properly capitalizes headers. * Allows and tests multiple cookie headers. * Fixes comment. * Adds otter back in. * Verifies correct capitalization. * Resets package version. * Removes debug log. * Makes persistent strings also interned and saves them once on the main thread. --------- Co-authored-by: Alliballibaba <alliballibaba@gmail.com>
This commit is contained in:
committed by
GitHub
parent
7e39e0a201
commit
dd250e3bda
49
cgi.go
49
cgi.go
@@ -10,6 +10,8 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/dunglas/frankenphp/internal/phpheaders"
|
||||
)
|
||||
|
||||
var knownServerKeys = []string{
|
||||
@@ -47,7 +49,7 @@ var knownServerKeys = []string{
|
||||
// TODO: handle this case https://github.com/caddyserver/caddy/issues/3718
|
||||
// Inspired by https://github.com/caddyserver/caddy/blob/master/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go
|
||||
func addKnownVariablesToServer(thread *phpThread, request *http.Request, fc *FrankenPHPContext, trackVarsArray *C.zval) {
|
||||
keys := getKnownVariableKeys(thread)
|
||||
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 {
|
||||
@@ -158,18 +160,17 @@ 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))}
|
||||
}
|
||||
|
||||
func addHeadersToServer(request *http.Request, fc *FrankenPHPContext, trackVarsArray *C.zval) {
|
||||
func addHeadersToServer(request *http.Request, thread *phpThread, fc *FrankenPHPContext, trackVarsArray *C.zval) {
|
||||
for field, val := range request.Header {
|
||||
k, ok := headerKeyCache.Get(field)
|
||||
if !ok {
|
||||
k = "HTTP_" + headerNameReplacer.Replace(strings.ToUpper(field)) + "\x00"
|
||||
headerKeyCache.SetIfAbsent(field, k)
|
||||
}
|
||||
|
||||
if _, ok := fc.env[k]; ok {
|
||||
if k := mainThread.commonHeaders[field]; k != nil {
|
||||
v := strings.Join(val, ", ")
|
||||
C.frankenphp_register_single(k, toUnsafeChar(v), C.size_t(len(v)), trackVarsArray)
|
||||
continue
|
||||
}
|
||||
|
||||
// if the header name could not be cached, it needs to be registered safely
|
||||
// this is more inefficient but allows additional sanitizing by PHP
|
||||
k := phpheaders.GetUnCommonHeader(field)
|
||||
v := strings.Join(val, ", ")
|
||||
C.frankenphp_register_variable_safe(toUnsafeChar(k), toUnsafeChar(v), C.size_t(len(v)), trackVarsArray)
|
||||
}
|
||||
@@ -182,18 +183,6 @@ func addPreparedEnvToServer(fc *FrankenPHPContext, trackVarsArray *C.zval) {
|
||||
fc.env = nil
|
||||
}
|
||||
|
||||
func getKnownVariableKeys(thread *phpThread) map[string]*C.zend_string {
|
||||
if thread.knownVariableKeys != nil {
|
||||
return thread.knownVariableKeys
|
||||
}
|
||||
threadServerKeys := make(map[string]*C.zend_string)
|
||||
for _, k := range knownServerKeys {
|
||||
threadServerKeys[k] = C.frankenphp_init_persistent_string(toUnsafeChar(k), C.size_t(len(k)))
|
||||
}
|
||||
thread.knownVariableKeys = threadServerKeys
|
||||
return threadServerKeys
|
||||
}
|
||||
|
||||
//export go_register_variables
|
||||
func go_register_variables(threadIndex C.uintptr_t, trackVarsArray *C.zval) {
|
||||
thread := phpThreads[threadIndex]
|
||||
@@ -201,20 +190,10 @@ func go_register_variables(threadIndex C.uintptr_t, trackVarsArray *C.zval) {
|
||||
fc := r.Context().Value(contextKey).(*FrankenPHPContext)
|
||||
|
||||
addKnownVariablesToServer(thread, r, fc, trackVarsArray)
|
||||
addHeadersToServer(r, fc, trackVarsArray)
|
||||
addPreparedEnvToServer(fc, trackVarsArray)
|
||||
}
|
||||
addHeadersToServer(r, thread, fc, trackVarsArray)
|
||||
|
||||
//export go_frankenphp_release_known_variable_keys
|
||||
func go_frankenphp_release_known_variable_keys(threadIndex C.uintptr_t) {
|
||||
thread := phpThreads[threadIndex]
|
||||
if thread.knownVariableKeys == nil {
|
||||
return
|
||||
}
|
||||
for _, v := range thread.knownVariableKeys {
|
||||
C.frankenphp_release_zend_string(v)
|
||||
}
|
||||
thread.knownVariableKeys = nil
|
||||
// The Prepared Environment is registered last and can overwrite any previous values
|
||||
addPreparedEnvToServer(fc, trackVarsArray)
|
||||
}
|
||||
|
||||
// splitPos returns the index where path should
|
||||
@@ -245,8 +224,6 @@ var tlsProtocolStrings = map[uint16]string{
|
||||
tls.VersionTLS13: "TLSv1.3",
|
||||
}
|
||||
|
||||
var headerNameReplacer = strings.NewReplacer(" ", "_", "-", "_")
|
||||
|
||||
// SanitizedPathJoin performs filepath.Join(root, reqPath) that
|
||||
// is safe against directory traversal attacks. It uses logic
|
||||
// similar to that in the Go standard library, specifically
|
||||
|
||||
19
frankenphp.c
19
frankenphp.c
@@ -683,6 +683,12 @@ void frankenphp_register_trusted_var(zend_string *z_key, char *value,
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -743,10 +749,15 @@ void frankenphp_register_bulk(
|
||||
request_uri.val_len, ht);
|
||||
}
|
||||
|
||||
/** Persistent strings are ignored by the PHP GC, we have to release these
|
||||
* ourselves **/
|
||||
/** Create an immutable zend_string that lasts for the whole process **/
|
||||
zend_string *frankenphp_init_persistent_string(const char *string, size_t len) {
|
||||
return zend_string_init(string, len, 1);
|
||||
/* persistent strings will be ignored by the GC at the end of a request */
|
||||
zend_string *z_string = zend_string_init(string, len, 1);
|
||||
|
||||
/* interned strings will not be ref counted by the GC */
|
||||
GC_ADD_FLAGS(z_string, IS_STR_INTERNED);
|
||||
|
||||
return z_string;
|
||||
}
|
||||
|
||||
void frankenphp_release_zend_string(zend_string *z_string) {
|
||||
@@ -920,8 +931,6 @@ static void *php_thread(void *arg) {
|
||||
frankenphp_execute_script(scriptName));
|
||||
}
|
||||
|
||||
go_frankenphp_release_known_variable_keys(thread_index);
|
||||
|
||||
#ifdef ZTS
|
||||
ts_free_thread();
|
||||
#endif
|
||||
|
||||
@@ -43,7 +43,6 @@ import (
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/maypok86/otter"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
// debug on Linux
|
||||
@@ -524,17 +523,6 @@ func go_ub_write(threadIndex C.uintptr_t, cBuf *C.char, length C.int) (C.size_t,
|
||||
return C.size_t(i), C.bool(clientHasClosed(r))
|
||||
}
|
||||
|
||||
// There are around 60 common request headers according to https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields
|
||||
// Give some space for custom headers
|
||||
var headerKeyCache = func() otter.Cache[string, string] {
|
||||
c, err := otter.MustBuilder[string, string](256).Build()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return c
|
||||
}()
|
||||
|
||||
//export go_apache_request_headers
|
||||
func go_apache_request_headers(threadIndex C.uintptr_t, hasActiveRequest bool) (*C.go_string, C.size_t) {
|
||||
thread := phpThreads[threadIndex]
|
||||
@@ -650,19 +638,17 @@ func go_read_post(threadIndex C.uintptr_t, cBuf *C.char, countBytes C.size_t) (r
|
||||
|
||||
//export go_read_cookies
|
||||
func go_read_cookies(threadIndex C.uintptr_t) *C.char {
|
||||
r := phpThreads[threadIndex].getActiveRequest()
|
||||
|
||||
cookies := r.Cookies()
|
||||
if len(cookies) == 0 {
|
||||
cookies := phpThreads[threadIndex].getActiveRequest().Header.Values("Cookie")
|
||||
cookie := strings.Join(cookies, "; ")
|
||||
if cookie == "" {
|
||||
return nil
|
||||
}
|
||||
cookieStrings := make([]string, len(cookies))
|
||||
for i, cookie := range cookies {
|
||||
cookieStrings[i] = cookie.String()
|
||||
}
|
||||
|
||||
// remove potential null bytes
|
||||
cookie = strings.ReplaceAll(cookie, "\x00", "")
|
||||
|
||||
// freed in frankenphp_free_request_context()
|
||||
return C.CString(strings.Join(cookieStrings, "; "))
|
||||
return C.CString(cookie)
|
||||
}
|
||||
|
||||
//export go_log
|
||||
|
||||
@@ -72,6 +72,8 @@ zend_string *frankenphp_init_persistent_string(const char *string, size_t len);
|
||||
void frankenphp_release_zend_string(zend_string *z_string);
|
||||
int frankenphp_reset_opcache(void);
|
||||
|
||||
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,
|
||||
|
||||
@@ -321,6 +321,31 @@ func testCookies(t *testing.T, opts *testOptions) {
|
||||
}, opts)
|
||||
}
|
||||
|
||||
func TestMalformedCookie(t *testing.T) {
|
||||
runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) {
|
||||
req := httptest.NewRequest("GET", "http://example.com/cookies.php", nil)
|
||||
req.Header.Add("Cookie", "foo =bar; ===;;==; .dot.=val ;\x00 ; PHPSESSID=1234")
|
||||
// Muliple Cookie header should be joined https://www.rfc-editor.org/rfc/rfc7540#section-8.1.2.5
|
||||
req.Header.Add("Cookie", "secondCookie=test; secondCookie=overwritten")
|
||||
w := httptest.NewRecorder()
|
||||
handler(w, req)
|
||||
|
||||
resp := w.Result()
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
|
||||
assert.Contains(t, string(body), "'foo_' => 'bar'")
|
||||
assert.Contains(t, string(body), "'_dot_' => 'val '")
|
||||
|
||||
// PHPSESSID should still be present since we remove the null byte
|
||||
assert.Contains(t, string(body), "'PHPSESSID' => '1234'")
|
||||
|
||||
// The cookie in the second headers should be present
|
||||
// but it should not be overwritten by following values
|
||||
assert.Contains(t, string(body), "'secondCookie' => 'test'")
|
||||
|
||||
}, &testOptions{nbParallelRequests: 1})
|
||||
}
|
||||
|
||||
func TestSession_module(t *testing.T) { testSession(t, nil) }
|
||||
func TestSession_worker(t *testing.T) {
|
||||
testSession(t, &testOptions{workerScript: "session.php"})
|
||||
|
||||
136
internal/phpheaders/phpheaders.go
Normal file
136
internal/phpheaders/phpheaders.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package phpheaders
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/maypok86/otter"
|
||||
)
|
||||
|
||||
// Translate header names to PHP header names
|
||||
// All headers in 'commonHeaders' can be cached and registered safely
|
||||
// All other headers must be sanitized
|
||||
// Note: net/http will capitalize lowercase headers, so we don't need to worry about case sensitivity
|
||||
var CommonRequestHeaders = map[string]string{
|
||||
"Accept": "HTTP_ACCEPT",
|
||||
"Accept-Charset": "HTTP_ACCEPT_CHARSET",
|
||||
"Accept-Encoding": "HTTP_ACCEPT_ENCODING",
|
||||
"Accept-Language": "HTTP_ACCEPT_LANGUAGE",
|
||||
"Access-Control-Request-Headers": "HTTP_ACCESS_CONTROL_REQUEST_HEADERS",
|
||||
"Access-Control-Request-Method": "HTTP_ACCESS_CONTROL_REQUEST_METHOD",
|
||||
"Authorization": "HTTP_AUTHORIZATION",
|
||||
"Cache-Control": "HTTP_CACHE_CONTROL",
|
||||
"Connection": "HTTP_CONNECTION",
|
||||
"Content-Disposition": "HTTP_CONTENT_DISPOSITION",
|
||||
"Content-Encoding": "HTTP_CONTENT_ENCODING",
|
||||
"Content-Length": "HTTP_CONTENT_LENGTH",
|
||||
"Content-Type": "HTTP_CONTENT_TYPE",
|
||||
"Cookie": "HTTP_COOKIE",
|
||||
"Date": "HTTP_DATE",
|
||||
"Device-Memory": "HTTP_DEVICE_MEMORY",
|
||||
"Dnt": "HTTP_DNT",
|
||||
"Downlink": "HTTP_DOWNLINK",
|
||||
"Dpr": "HTTP_DPR",
|
||||
"Early-Data": "HTTP_EARLY_DATA",
|
||||
"Ect": "HTTP_ECT",
|
||||
"Am-I": "HTTP_AM_I",
|
||||
"Expect": "HTTP_EXPECT",
|
||||
"Forwarded": "HTTP_FORWARDED",
|
||||
"From": "HTTP_FROM",
|
||||
"Host": "HTTP_HOST",
|
||||
"If-Match": "HTTP_IF_MATCH",
|
||||
"If-Modified-Since": "HTTP_IF_MODIFIED_SINCE",
|
||||
"If-None-Match": "HTTP_IF_NONE_MATCH",
|
||||
"If-Range": "HTTP_IF_RANGE",
|
||||
"If-Unmodified-Since": "HTTP_IF_UNMODIFIED_SINCE",
|
||||
"Keep-Alive": "HTTP_KEEP_ALIVE",
|
||||
"Max-Forwards": "HTTP_MAX_FORWARDS",
|
||||
"Origin": "HTTP_ORIGIN",
|
||||
"Pragma": "HTTP_PRAGMA",
|
||||
"Proxy-Authorization": "HTTP_PROXY_AUTHORIZATION",
|
||||
"Range": "HTTP_RANGE",
|
||||
"Referer": "HTTP_REFERER",
|
||||
"Rtt": "HTTP_RTT",
|
||||
"Save-Data": "HTTP_SAVE_DATA",
|
||||
"Sec-Ch-Ua": "HTTP_SEC_CH_UA",
|
||||
"Sec-Ch-Ua-Arch": "HTTP_SEC_CH_UA_ARCH",
|
||||
"Sec-Ch-Ua-Bitness": "HTTP_SEC_CH_UA_BITNESS",
|
||||
"Sec-Ch-Ua-Full-Version": "HTTP_SEC_CH_UA_FULL_VERSION",
|
||||
"Sec-Ch-Ua-Full-Version-List": "HTTP_SEC_CH_UA_FULL_VERSION_LIST",
|
||||
"Sec-Ch-Ua-Mobile": "HTTP_SEC_CH_UA_MOBILE",
|
||||
"Sec-Ch-Ua-Model": "HTTP_SEC_CH_UA_MODEL",
|
||||
"Sec-Ch-Ua-Platform": "HTTP_SEC_CH_UA_PLATFORM",
|
||||
"Sec-Ch-Ua-Platform-Version": "HTTP_SEC_CH_UA_PLATFORM_VERSION",
|
||||
"Sec-Fetch-Dest": "HTTP_SEC_FETCH_DEST",
|
||||
"Sec-Fetch-Mode": "HTTP_SEC_FETCH_MODE",
|
||||
"Sec-Fetch-Site": "HTTP_SEC_FETCH_SITE",
|
||||
"Sec-Fetch-User": "HTTP_SEC_FETCH_USER",
|
||||
"Sec-Gpc": "HTTP_SEC_GPC",
|
||||
"Service-Worker-Navigation-Preload": "HTTP_SERVICE_WORKER_NAVIGATION_PRELOAD",
|
||||
"Te": "HTTP_TE",
|
||||
"Priority": "HTTP_PRIORITY",
|
||||
"Trailer": "HTTP_TRAILER",
|
||||
"Transfer-Encoding": "HTTP_TRANSFER_ENCODING",
|
||||
"Upgrade": "HTTP_UPGRADE",
|
||||
"Upgrade-Insecure-Requests": "HTTP_UPGRADE_INSECURE_REQUESTS",
|
||||
"User-Agent": "HTTP_USER_AGENT",
|
||||
"Via": "HTTP_VIA",
|
||||
"Viewport-Width": "HTTP_VIEWPORT_WIDTH",
|
||||
"Want-Digest": "HTTP_WANT_DIGEST",
|
||||
"Warning": "HTTP_WARNING",
|
||||
"Width": "HTTP_WIDTH",
|
||||
"X-Forwarded-For": "HTTP_X_FORWARDED_FOR",
|
||||
"X-Forwarded-Host": "HTTP_X_FORWARDED_HOST",
|
||||
"X-Forwarded-Proto": "HTTP_X_FORWARDED_PROTO",
|
||||
"A-Im": "HTTP_A_IM",
|
||||
"Accept-Datetime": "HTTP_ACCEPT_DATETIME",
|
||||
"Content-Md5": "HTTP_CONTENT_MD5",
|
||||
"Http2-Settings": "HTTP_HTTP2_SETTINGS",
|
||||
"Prefer": "HTTP_PREFER",
|
||||
"X-Requested-With": "HTTP_X_REQUESTED_WITH",
|
||||
"Front-End-Https": "HTTP_FRONT_END_HTTPS",
|
||||
"X-Http-Method-Override": "HTTP_X_HTTP_METHOD_OVERRIDE",
|
||||
"X-Att-Deviceid": "HTTP_X_ATT_DEVICEID",
|
||||
"X-Wap-Profile": "HTTP_X_WAP_PROFILE",
|
||||
"Proxy-Connection": "HTTP_PROXY_CONNECTION",
|
||||
"X-Uidh": "HTTP_X_UIDH",
|
||||
"X-Csrf-Token": "HTTP_X_CSRF_TOKEN",
|
||||
"X-Request-Id": "HTTP_X_REQUEST_ID",
|
||||
"X-Correlation-Id": "HTTP_X_CORRELATION_ID",
|
||||
// Additional CDN/Framework headers
|
||||
"Cloudflare-Visitor": "HTTP_CLOUDFLARE_VISITOR",
|
||||
"Cloudfront-Viewer-Address": "HTTP_CLOUDFRONT_VIEWER_ADDRESS",
|
||||
"Cloudfront-Viewer-Country": "HTTP_CLOUDFRONT_VIEWER_COUNTRY",
|
||||
"X-Amzn-Trace-Id": "HTTP_X_AMZN_TRACE_ID",
|
||||
"X-Cloud-Trace-Context": "HTTP_X_CLOUD_TRACE_CONTEXT",
|
||||
"Cf-Ray": "HTTP_CF_RAY",
|
||||
"Cf-Visitor": "HTTP_CF_VISITOR",
|
||||
"Cf-Request-Id": "HTTP_CF_REQUEST_ID",
|
||||
"Cf-Ipcountry": "HTTP_CF_IPCOUNTRY",
|
||||
"X-Device-Type": "HTTP_X_DEVICE_TYPE",
|
||||
"X-Network-Info": "HTTP_X_NETWORK_INFO",
|
||||
"X-Client-Id": "HTTP_X_CLIENT_ID",
|
||||
"X-Livewire": "HTTP_X_LIVEWIRE",
|
||||
}
|
||||
|
||||
// Cache up to 256 uncommon headers
|
||||
// This is ~2.5x faster than converting the header each time
|
||||
var headerKeyCache = func() otter.Cache[string, string] {
|
||||
c, err := otter.MustBuilder[string, string](256).Build()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return c
|
||||
}()
|
||||
|
||||
var headerNameReplacer = strings.NewReplacer(" ", "_", "-", "_")
|
||||
|
||||
func GetUnCommonHeader(key string) string {
|
||||
phpHeaderKey, ok := headerKeyCache.Get(key)
|
||||
if !ok {
|
||||
phpHeaderKey = "HTTP_" + headerNameReplacer.Replace(strings.ToUpper(key)) + "\x00"
|
||||
headerKeyCache.SetIfAbsent(key, phpHeaderKey)
|
||||
}
|
||||
|
||||
return phpHeaderKey
|
||||
}
|
||||
@@ -5,15 +5,18 @@ import "C"
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/dunglas/frankenphp/internal/phpheaders"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// represents the main PHP thread
|
||||
// the thread needs to keep running as long as all other threads are running
|
||||
type phpMainThread struct {
|
||||
state *threadState
|
||||
done chan struct{}
|
||||
numThreads int
|
||||
state *threadState
|
||||
done chan struct{}
|
||||
numThreads int
|
||||
commonHeaders map[string]*C.zend_string
|
||||
knownServerKeys map[string]*C.zend_string
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -86,6 +89,19 @@ func (mainThread *phpMainThread) start() error {
|
||||
return MainThreadCreationError
|
||||
}
|
||||
mainThread.state.waitFor(stateReady)
|
||||
|
||||
// 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)))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/dunglas/frankenphp/internal/phpheaders"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
@@ -136,6 +137,22 @@ func TestTransitionThreadsWhileDoingRequests(t *testing.T) {
|
||||
Shutdown()
|
||||
}
|
||||
|
||||
// Note: this test is here since it would break compilation when put into the phpheaders package
|
||||
func TestAllCommonHeadersAreCorrect(t *testing.T) {
|
||||
fakeRequest := httptest.NewRequest("GET", "http://localhost", nil)
|
||||
|
||||
for header, phpHeader := range phpheaders.CommonRequestHeaders {
|
||||
// verify that common and uncommon headers return the same result
|
||||
expectedPHPHeader := phpheaders.GetUnCommonHeader(header)
|
||||
assert.Equal(t, phpHeader+"\x00", expectedPHPHeader, "header is not well formed: "+phpHeader)
|
||||
|
||||
// net/http will capitalize lowercase headers, verify that headers are capitalized
|
||||
fakeRequest.Header.Add(header, "foo")
|
||||
_, ok := fakeRequest.Header[header]
|
||||
assert.True(t, ok, "header is not correctly capitalized: "+header)
|
||||
}
|
||||
}
|
||||
|
||||
func getDummyWorker(fileName string) *worker {
|
||||
if workers == nil {
|
||||
workers = make(map[string]*worker)
|
||||
|
||||
18
phpthread.go
18
phpthread.go
@@ -14,13 +14,12 @@ import (
|
||||
type phpThread struct {
|
||||
runtime.Pinner
|
||||
|
||||
threadIndex int
|
||||
knownVariableKeys map[string]*C.zend_string
|
||||
requestChan chan *http.Request
|
||||
drainChan chan struct{}
|
||||
handlerMu *sync.Mutex
|
||||
handler threadHandler
|
||||
state *threadState
|
||||
threadIndex int
|
||||
requestChan chan *http.Request
|
||||
drainChan chan struct{}
|
||||
handlerMu *sync.Mutex
|
||||
handler threadHandler
|
||||
state *threadState
|
||||
}
|
||||
|
||||
// interface that defines how the callbacks from the C thread should be handled
|
||||
@@ -114,6 +113,7 @@ func go_frankenphp_after_script_execution(threadIndex C.uintptr_t, exitStatus C.
|
||||
|
||||
//export go_frankenphp_on_thread_shutdown
|
||||
func go_frankenphp_on_thread_shutdown(threadIndex C.uintptr_t) {
|
||||
phpThreads[threadIndex].Unpin()
|
||||
phpThreads[threadIndex].state.set(stateDone)
|
||||
thread := phpThreads[threadIndex]
|
||||
thread.Unpin()
|
||||
thread.state.set(stateDone)
|
||||
}
|
||||
|
||||
5
testdata/server-all-vars-ordered.php
vendored
5
testdata/server-all-vars-ordered.php
vendored
@@ -4,6 +4,8 @@ echo "<pre>\n";
|
||||
foreach ([
|
||||
'CONTENT_LENGTH',
|
||||
'HTTP_CONTENT_LENGTH',
|
||||
'CONTENT_TYPE',
|
||||
'HTTP_CONTENT_TYPE',
|
||||
'HTTP_SPECIAL_CHARS',
|
||||
'DOCUMENT_ROOT',
|
||||
'DOCUMENT_URI',
|
||||
@@ -11,10 +13,8 @@ foreach ([
|
||||
'HTTP_HOST',
|
||||
'HTTPS',
|
||||
'PATH_INFO',
|
||||
'CONTENT_TYPE',
|
||||
'DOCUMENT_ROOT',
|
||||
'REMOTE_ADDR',
|
||||
'CONTENT_LENGTH',
|
||||
'PHP_SELF',
|
||||
'REMOTE_HOST',
|
||||
'REQUEST_SCHEME',
|
||||
@@ -27,7 +27,6 @@ foreach ([
|
||||
'SSL_PROTOCOL',
|
||||
'AUTH_TYPE',
|
||||
'REMOTE_IDENT',
|
||||
'CONTENT_TYPE',
|
||||
'PATH_TRANSLATED',
|
||||
'QUERY_STRING',
|
||||
'REMOTE_USER',
|
||||
|
||||
5
testdata/server-all-vars-ordered.txt
vendored
5
testdata/server-all-vars-ordered.txt
vendored
@@ -1,6 +1,8 @@
|
||||
<pre>
|
||||
CONTENT_LENGTH:7
|
||||
HTTP_CONTENT_LENGTH:7
|
||||
CONTENT_TYPE:application/x-www-form-urlencoded
|
||||
HTTP_CONTENT_TYPE:application/x-www-form-urlencoded
|
||||
HTTP_SPECIAL_CHARS:<%00>
|
||||
DOCUMENT_ROOT:{documentRoot}
|
||||
DOCUMENT_URI:/server-all-vars-ordered.php
|
||||
@@ -8,10 +10,8 @@ GATEWAY_INTERFACE:CGI/1.1
|
||||
HTTP_HOST:localhost:{testPort}
|
||||
HTTPS:
|
||||
PATH_INFO:/path
|
||||
CONTENT_TYPE:application/x-www-form-urlencoded
|
||||
DOCUMENT_ROOT:{documentRoot}
|
||||
REMOTE_ADDR:127.0.0.1
|
||||
CONTENT_LENGTH:7
|
||||
PHP_SELF:/server-all-vars-ordered.php/path
|
||||
REMOTE_HOST:127.0.0.1
|
||||
REQUEST_SCHEME:http
|
||||
@@ -24,7 +24,6 @@ SERVER_SOFTWARE:FrankenPHP
|
||||
SSL_PROTOCOL:
|
||||
AUTH_TYPE:
|
||||
REMOTE_IDENT:
|
||||
CONTENT_TYPE:application/x-www-form-urlencoded
|
||||
PATH_TRANSLATED:{documentRoot}/path
|
||||
QUERY_STRING:specialChars=%3E\x00%00</>
|
||||
REMOTE_USER:user
|
||||
|
||||
Reference in New Issue
Block a user