From 01586f506d778a031dc5d06d5bccf7c23b162dbc Mon Sep 17 00:00:00 2001 From: Ciprian Dorin Craciun Date: Fri, 17 Dec 2021 21:58:36 +0200 Subject: [PATCH] [server] Try to optimize memory allocations for FastHTTP to Go HTTP bridging (it involves heavy "magic"). --- sources/cmd/server/server.go | 25 ++++---- sources/lib/server/headers-magic.go | 98 +++++++++++++++++++++++++++++ sources/lib/server/headers.go | 13 ++++ 3 files changed, 125 insertions(+), 11 deletions(-) create mode 100644 sources/lib/server/headers-magic.go diff --git a/sources/cmd/server/server.go b/sources/cmd/server/server.go index c7eee8a..2e64757 100644 --- a/sources/cmd/server/server.go +++ b/sources/cmd/server/server.go @@ -503,27 +503,29 @@ func (_server *server) ServeHTTP (_response http.ResponseWriter, _request *http. _server.Serve (_context) - _responseHeaders := _response.Header () - _responseHeaders["Date"] = nil + _responseHeaders := HttpResponseWriterHeaderDoMagic (_response) _context.Response.Header.VisitAll ( func (_key_0 []byte, _value_0 []byte) () { switch string (_key_0) { case "Connection", "Content-Length", "Date" : // NOP default : - _key := string (_key_0) - _value := string (_value_0) - _responseHeaders[_key] = append (_responseHeaders[_key], _value) + _key := CanonicalHeaderNameFromBytes (_key_0) + if _values, _ := _responseHeaders[_key]; _values == nil { + _values = CanonicalHeaderValueArrayFromBytes (_value_0) + _responseHeaders[_key] = _values + } else { + _value := CanonicalHeaderValueFromBytes (_value_0) + _values = append (_values, _value) + _responseHeaders[_key] = _values + } } }) - - _responseBody := _context.Response.Body () - if len (_responseBody) > 0 { - _responseHeaders["Content-Length"] = []string { fmt.Sprintf ("%d", len (_responseBody)) } - } + _responseHeaders["Date"] = nil _response.WriteHeader (_context.Response.Header.StatusCode ()) - _response.Write (_responseBody) + + _response.Write (_context.Response.Body ()) } @@ -724,6 +726,7 @@ func main_0 () (error) { } else { AbortError (nil, "[1a5476b1] HTTP/3 Alt-Svc is invalid!") } + CanonicalHeaderValueRegister (_http3AltSvc) } if !_dummy { diff --git a/sources/lib/server/headers-magic.go b/sources/lib/server/headers-magic.go new file mode 100644 index 0000000..7ee0c26 --- /dev/null +++ b/sources/lib/server/headers-magic.go @@ -0,0 +1,98 @@ + + +package server + + +import "fmt" +import "net/http" +import "log" +import "reflect" +import "sync/atomic" +import "unsafe" + + + + +func HttpResponseWriterHeaderDoMagic (_response http.ResponseWriter) (http.Header) { + + + if !_httpResponseWriteHeaderMagic_enabled { + return _response.Header () + } + + + // NOTE: Because we don't modify the headers after calling `WriteHeader`, + // the following code tricks `net/http` into believing it didn't gave us the headers. + // This eliminates the `http.Header.Clone()` call on `WriteHeader`. + + _responseReflect := reflect.ValueOf (_response) .Elem () + _responseAddress := unsafe.Pointer (_responseReflect.UnsafeAddr ()) + _responseType := _responseReflect.Type () + _responsePackage := _responseType.PkgPath () + _responseTypeName := _responseType.Name () + + var _header http.Header + + switch { + + case (_responsePackage == "net/http") && (_responseTypeName == "response") : { + + _handlerHeaderOffset := atomic.LoadInt32 (&_httpResponseWriteHeaderMagic_netHttp_handlerHeaderOffset) + _cwHeaderOffset := atomic.LoadInt32 (&_httpResponseWriteHeaderMagic_netHttp_cwHeaderOffset) + + if (_handlerHeaderOffset == 0) || (_cwHeaderOffset == 0) { + + _handlerHeaderReflect := _responseReflect.FieldByName ("handlerHeader") + _handlerHeaderAddress := unsafe.Pointer (_handlerHeaderReflect.UnsafeAddr ()) + + _cwHeaderReflect := _responseReflect.FieldByName ("cw") .FieldByName ("header") + _cwHeaderAddress := unsafe.Pointer (_cwHeaderReflect.UnsafeAddr ()) + + _handlerHeaderOffset = int32 (int64 (uintptr (_handlerHeaderAddress)) - int64 (uintptr (_responseAddress))) + _cwHeaderOffset = int32 (int64 (uintptr (_cwHeaderAddress)) - int64 (uintptr (_responseAddress))) + + atomic.StoreInt32 (&_httpResponseWriteHeaderMagic_netHttp_handlerHeaderOffset, _handlerHeaderOffset) + atomic.StoreInt32 (&_httpResponseWriteHeaderMagic_netHttp_cwHeaderOffset, _cwHeaderOffset) + } + + _handlerHeaderValue := (*http.Header) (unsafe.Add (_responseAddress, _handlerHeaderOffset)) + _cwHeaderValue := (*http.Header) (unsafe.Add (_responseAddress, _cwHeaderOffset)) + + _header = make (map[string][]string, 16) + + *_handlerHeaderValue = _header + *_cwHeaderValue = _header + } + + case (_responsePackage == "net/http") && (_responseTypeName == "http2responseWriter") : { + + _header = _response.Header () + } + + case (_responsePackage == "github.com/lucas-clemente/quic-go/http3") && (_responseTypeName == "responseWriter") : { + + _header = _response.Header () + } + + default : { + + log.Printf ("[ee] [64583df9] unsupported HTTP ResponseWriter `%s.%s`!\n", _responsePackage, _responseTypeName) + + if _httpResponseWriteHeaderMagic_panic { + panic (fmt.Sprintf ("[09274c17] unsupported HTTP ResponseWriter `%s.%s`!", _responsePackage, _responseTypeName)) + } + + _header = _response.Header () + } + } + + return _header +} + + +var _httpResponseWriteHeaderMagic_enabled = true +var _httpResponseWriteHeaderMagic_panic = true + +var _httpResponseWriteHeaderMagic_netHttp_handlerHeaderOffset int32 +var _httpResponseWriteHeaderMagic_netHttp_cwHeaderOffset int32 + diff --git a/sources/lib/server/headers.go b/sources/lib/server/headers.go index 626bc11..3ecbcee 100644 --- a/sources/lib/server/headers.go +++ b/sources/lib/server/headers.go @@ -22,6 +22,8 @@ func CanonicalHeaderNameFromBytes (_header []byte) (string) { } + + func CanonicalHeaderValueFromBytes (_value []byte) (string) { if _canonical, _found := CanonicalHeaderValuesMap[BytesToString (*NoEscapeBytes (&_value))]; _found { return _canonical @@ -30,6 +32,14 @@ func CanonicalHeaderValueFromBytes (_value []byte) (string) { } } +func CanonicalHeaderValueArrayFromBytes (_value []byte) ([]string) { + if _canonical, _found := CanonicalHeaderValuesArraysMap[BytesToString (*NoEscapeBytes (&_value))]; _found { + return _canonical + } else { + return []string { string (_value) } + } +} + func CanonicalHeaderValueRegister (_value string) () { CanonicalHeaderValues = append (CanonicalHeaderValues, _value) @@ -41,6 +51,7 @@ func CanonicalHeaderValueRegister (_value string) () { var CanonicalHeaderNamesMap map[string]string var CanonicalHeaderValuesMap map[string]string +var CanonicalHeaderValuesArraysMap map[string][]string var CanonicalHeaderNames = []string { @@ -218,8 +229,10 @@ func init () { CanonicalHeaderValues = append (CanonicalHeaderValues, MimeTypes ...) CanonicalHeaderValuesMap = make (map[string]string, len (CanonicalHeaderValues)) + CanonicalHeaderValuesArraysMap = make (map[string][]string, len (CanonicalHeaderValues)) for _, _value := range CanonicalHeaderValues { CanonicalHeaderValuesMap[_value] = _value + CanonicalHeaderValuesArraysMap[_value] = []string { _value } } }