From 404144ecb3494f565de82dd9a936e7d89ad3632b Mon Sep 17 00:00:00 2001 From: kinggo Date: Mon, 3 Jul 2023 15:01:39 +0800 Subject: [PATCH] feat: add server DisableHeaderNamesNormalizing option --- pkg/app/server/option.go | 7 +++++++ pkg/common/config/option.go | 19 +++++++++++++++++++ pkg/protocol/http1/server.go | 35 +++++++++++++++++++++-------------- pkg/route/engine.go | 29 +++++++++++++++-------------- 4 files changed, 62 insertions(+), 28 deletions(-) diff --git a/pkg/app/server/option.go b/pkg/app/server/option.go index bf4482904..c9e3735be 100644 --- a/pkg/app/server/option.go +++ b/pkg/app/server/option.go @@ -346,3 +346,10 @@ func WithOnConnect(fn func(ctx context.Context, conn network.Conn) context.Conte o.OnConnect = fn }} } + +// WithDisableHeaderNamesNormalizing is used to set whether disable header names normalizing. +func WithDisableHeaderNamesNormalizing(disable bool) config.Option { + return config.Option{F: func(o *config.Options) { + o.DisableHeaderNamesNormalizing = disable + }} +} diff --git a/pkg/common/config/option.go b/pkg/common/config/option.go index 03c84a02a..cd48d4535 100644 --- a/pkg/common/config/option.go +++ b/pkg/common/config/option.go @@ -97,6 +97,22 @@ type Options struct { // The HTML template will reload according to files' changing event // otherwise it will reload after AutoReloadInterval. AutoReloadInterval time.Duration + + // Header names are passed as-is without normalization + // if this option is set. + // + // Disabled header names' normalization may be useful only for proxying + // responses to other clients expecting case-sensitive header names. + // + // By default request and response header names are normalized, i.e. + // The first letter and the first letters following dashes + // are uppercased, while all the other letters are lowercased. + // Examples: + // + // * HOST -> Host + // * content-type -> Content-Type + // * cONTENT-lenGTH -> Content-Length + DisableHeaderNamesNormalizing bool } func (o *Options) Apply(opts []Option) { @@ -225,6 +241,9 @@ func NewOptions(opts []Option) *Options { TraceLevel: new(interface{}), Registry: registry.NoopRegistry, + + // Disabled header names' normalization, default false + DisableHeaderNamesNormalizing: false, } options.Apply(opts) return options diff --git a/pkg/protocol/http1/server.go b/pkg/protocol/http1/server.go index 77cc74e18..1b16ebd62 100644 --- a/pkg/protocol/http1/server.go +++ b/pkg/protocol/http1/server.go @@ -54,20 +54,21 @@ var ( ) type Option struct { - StreamRequestBody bool - GetOnly bool - DisablePreParseMultipartForm bool - DisableKeepalive bool - NoDefaultServerHeader bool - MaxRequestBodySize int - IdleTimeout time.Duration - ReadTimeout time.Duration - ServerName []byte - TLS *tls.Config - HTMLRender render.HTMLRender - EnableTrace bool - ContinueHandler func(header *protocol.RequestHeader) bool - HijackConnHandle func(c network.Conn, h app.HijackHandler) + StreamRequestBody bool + GetOnly bool + DisablePreParseMultipartForm bool + DisableKeepalive bool + NoDefaultServerHeader bool + MaxRequestBodySize int + IdleTimeout time.Duration + ReadTimeout time.Duration + ServerName []byte + TLS *tls.Config + HTMLRender render.HTMLRender + EnableTrace bool + ContinueHandler func(header *protocol.RequestHeader) bool + HijackConnHandle func(c network.Conn, h app.HijackHandler) + DisableHeaderNamesNormalizing bool } type Server struct { @@ -179,6 +180,12 @@ func (s Server) Serve(c context.Context, conn network.Conn) (err error) { internalStats.Record(ti, stats.ReadHeaderFinish, err) }) } + + if s.DisableHeaderNamesNormalizing { + ctx.Request.Header.DisableNormalizing() + ctx.Response.Header.DisableNormalizing() + } + // Read Headers if err = req.ReadHeader(&ctx.Request.Header, zr); err == nil { if s.EnableTrace { diff --git a/pkg/route/engine.go b/pkg/route/engine.go index 10cffac7a..627406e84 100644 --- a/pkg/route/engine.go +++ b/pkg/route/engine.go @@ -990,20 +990,21 @@ func iterate(method string, routes RoutesInfo, root *node) RoutesInfo { // for built-in http1 impl only. func newHttp1OptionFromEngine(engine *Engine) *http1.Option { opt := &http1.Option{ - StreamRequestBody: engine.options.StreamRequestBody, - GetOnly: engine.options.GetOnly, - DisablePreParseMultipartForm: engine.options.DisablePreParseMultipartForm, - DisableKeepalive: engine.options.DisableKeepalive, - NoDefaultServerHeader: engine.options.NoDefaultServerHeader, - MaxRequestBodySize: engine.options.MaxRequestBodySize, - IdleTimeout: engine.options.IdleTimeout, - ReadTimeout: engine.options.ReadTimeout, - ServerName: engine.GetServerName(), - ContinueHandler: engine.ContinueHandler, - TLS: engine.options.TLS, - HTMLRender: engine.htmlRender, - EnableTrace: engine.IsTraceEnable(), - HijackConnHandle: engine.HijackConnHandle, + StreamRequestBody: engine.options.StreamRequestBody, + GetOnly: engine.options.GetOnly, + DisablePreParseMultipartForm: engine.options.DisablePreParseMultipartForm, + DisableKeepalive: engine.options.DisableKeepalive, + NoDefaultServerHeader: engine.options.NoDefaultServerHeader, + MaxRequestBodySize: engine.options.MaxRequestBodySize, + IdleTimeout: engine.options.IdleTimeout, + ReadTimeout: engine.options.ReadTimeout, + ServerName: engine.GetServerName(), + ContinueHandler: engine.ContinueHandler, + TLS: engine.options.TLS, + HTMLRender: engine.htmlRender, + EnableTrace: engine.IsTraceEnable(), + HijackConnHandle: engine.HijackConnHandle, + DisableHeaderNamesNormalizing: engine.options.DisableHeaderNamesNormalizing, } // Idle timeout of standard network must not be zero. Set it to -1 seconds if it is zero. // Due to the different triggering ways of the network library, see the actual use of this value for the detailed reasons.