Remove deprecated elements
This commit is contained in:
parent
e92b01c528
commit
015cd7a3d0
17 changed files with 19 additions and 204 deletions
2
Gopkg.lock
generated
2
Gopkg.lock
generated
|
@ -1769,6 +1769,6 @@
|
|||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "f323b06b4963d6cec0eee5b88dd4e85d6b2e80ea41418154a8a57a0dae574bcd"
|
||||
inputs-digest = "0b453fff40221eb9d779eeb4ad75fc076e8cbd93c1ecf058f4b9411d4afddf68"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
|
|
@ -56,12 +56,6 @@ Træfik can be configured with a file.
|
|||
passTLSCert = true
|
||||
priority = 42
|
||||
|
||||
# Use frontends.frontend1.auth.basic below instead
|
||||
basicAuth = [
|
||||
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
|
||||
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
|
||||
]
|
||||
|
||||
[frontends.frontend1.auth]
|
||||
headerField = "X-WebAuth-User"
|
||||
[frontends.frontend1.auth.basic]
|
||||
|
|
|
@ -62,6 +62,10 @@ debug = true
|
|||
[frontends.frontend3]
|
||||
passHostHeader = true
|
||||
backend = "backend3"
|
||||
basicAuth = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"]
|
||||
[frontends.frontend3.auth.basic]
|
||||
users = [
|
||||
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
|
||||
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
|
||||
]
|
||||
[frontends.frontend3.routes.test_auth]
|
||||
rule = "Path:/auth"
|
||||
|
|
|
@ -40,8 +40,6 @@ const (
|
|||
RequestPath = "RequestPath"
|
||||
// RequestProtocol is the map key used for the version of HTTP requested.
|
||||
RequestProtocol = "RequestProtocol"
|
||||
// RequestLine is the original request line
|
||||
RequestLine = "RequestLine"
|
||||
// RequestContentSize is the map key used for the number of bytes in the request entity (a.k.a. body) sent by the client.
|
||||
RequestContentSize = "RequestContentSize"
|
||||
// RequestRefererHeader is the Referer header in the request
|
||||
|
@ -55,14 +53,8 @@ const (
|
|||
// OriginStatus is the map key used for the HTTP status code returned by the origin server.
|
||||
// If the request was handled by this Traefik instance (e.g. with a redirect), then this value will be absent.
|
||||
OriginStatus = "OriginStatus"
|
||||
// OriginStatusLine is the map key used for the HTTP status code and corresponding descriptive string.
|
||||
// If the request was handled by this Traefik instance (e.g. with a redirect), then this value will be absent.
|
||||
// Note that the actual message string might be different to what is reported here, depending on server behaviour.
|
||||
OriginStatusLine = "OriginStatusLine"
|
||||
// DownstreamStatus is the map key used for the HTTP status code returned to the client.
|
||||
DownstreamStatus = "DownstreamStatus"
|
||||
// DownstreamStatusLine is the map key used for the HTTP status line returned to the client.
|
||||
DownstreamStatusLine = "DownstreamStatusLine"
|
||||
// DownstreamContentSize is the map key used for the number of bytes in the response entity returned to the client.
|
||||
// This is in addition to the "Content-Length" header, which may be present in the origin response.
|
||||
DownstreamContentSize = "DownstreamContentSize"
|
||||
|
@ -110,9 +102,6 @@ func init() {
|
|||
allCoreKeys[BackendAddr] = struct{}{}
|
||||
allCoreKeys[ClientAddr] = struct{}{}
|
||||
allCoreKeys[RequestAddr] = struct{}{}
|
||||
allCoreKeys[RequestLine] = struct{}{}
|
||||
allCoreKeys[OriginStatusLine] = struct{}{}
|
||||
allCoreKeys[DownstreamStatusLine] = struct{}{}
|
||||
allCoreKeys[GzipRatio] = struct{}{}
|
||||
allCoreKeys[StartLocal] = struct{}{}
|
||||
allCoreKeys[Overhead] = struct{}{}
|
||||
|
|
|
@ -167,7 +167,6 @@ func (l *LogHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next h
|
|||
core[RequestMethod] = req.Method
|
||||
core[RequestPath] = urlCopyString
|
||||
core[RequestProtocol] = req.Proto
|
||||
core[RequestLine] = fmt.Sprintf("%s %s %s", req.Method, urlCopyString, req.Proto)
|
||||
|
||||
core[ClientAddr] = req.RemoteAddr
|
||||
core[ClientHost], core[ClientPort] = silentSplitHostPort(req.RemoteAddr)
|
||||
|
@ -262,7 +261,6 @@ func (l *LogHandler) logTheRoundTrip(logDataTable *LogData, crr *captureRequestR
|
|||
core[Duration] = totalDuration
|
||||
|
||||
if l.keepAccessLog(crw.Status(), retryAttempts, totalDuration) {
|
||||
core[DownstreamStatusLine] = fmt.Sprintf("%03d %s", crw.Status(), http.StatusText(crw.Status()))
|
||||
core[DownstreamContentSize] = crw.Size()
|
||||
if original, ok := core[OriginContentSize]; ok {
|
||||
o64 := original.(int64)
|
||||
|
|
|
@ -198,9 +198,7 @@ func TestLoggerJSON(t *testing.T) {
|
|||
RequestPath: assertString(testPath),
|
||||
RequestProtocol: assertString(testProto),
|
||||
RequestPort: assertString("-"),
|
||||
RequestLine: assertString(fmt.Sprintf("%s %s %s", testMethod, testPath, testProto)),
|
||||
DownstreamStatus: assertFloat64(float64(testStatus)),
|
||||
DownstreamStatusLine: assertString(fmt.Sprintf("%d ", testStatus)),
|
||||
DownstreamContentSize: assertFloat64(float64(len(testContent))),
|
||||
OriginContentSize: assertFloat64(float64(len(testContent))),
|
||||
OriginStatus: assertFloat64(float64(testStatus)),
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package accesslog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
|
@ -58,7 +57,6 @@ func serveSaveBackend(rw http.ResponseWriter, r *http.Request, backendName strin
|
|||
// use UTC to handle switchover of daylight saving correctly
|
||||
table.Core[OriginDuration] = time.Now().UTC().Sub(start)
|
||||
table.Core[OriginStatus] = crw.Status()
|
||||
table.Core[OriginStatusLine] = fmt.Sprintf("%03d %s", crw.Status(), http.StatusText(crw.Status()))
|
||||
// make copy of headers so we can ensure there is no subsequent mutation during response processing
|
||||
table.OriginResponse = make(http.Header)
|
||||
utils.CopyHeaders(table.OriginResponse, crw.Header())
|
||||
|
|
|
@ -66,22 +66,6 @@ const (
|
|||
annotationKubernetesProtocol = "ingress.kubernetes.io/protocol"
|
||||
)
|
||||
|
||||
// TODO [breaking] remove label support
|
||||
var compatibilityMapping = map[string]string{
|
||||
annotationKubernetesPreserveHost: "traefik.frontend.passHostHeader",
|
||||
annotationKubernetesPassTLSCert: "traefik.frontend.passTLSCert",
|
||||
annotationKubernetesFrontendEntryPoints: "traefik.frontend.entryPoints",
|
||||
annotationKubernetesPriority: "traefik.frontend.priority",
|
||||
annotationKubernetesCircuitBreakerExpression: "traefik.backend.circuitbreaker",
|
||||
annotationKubernetesLoadBalancerMethod: "traefik.backend.loadbalancer.method",
|
||||
annotationKubernetesAffinity: "traefik.backend.loadbalancer.stickiness",
|
||||
annotationKubernetesSessionCookieName: "traefik.backend.loadbalancer.stickiness.cookieName",
|
||||
annotationKubernetesRuleType: "traefik.frontend.rule.type",
|
||||
annotationKubernetesRedirectEntryPoint: "traefik.frontend.redirect.entrypoint",
|
||||
annotationKubernetesRedirectRegex: "traefik.frontend.redirect.regex",
|
||||
annotationKubernetesRedirectReplacement: "traefik.frontend.redirect.replacement",
|
||||
}
|
||||
|
||||
func getAnnotationName(annotations map[string]string, name string) string {
|
||||
if _, ok := annotations[name]; ok {
|
||||
return name
|
||||
|
@ -91,13 +75,6 @@ func getAnnotationName(annotations map[string]string, name string) string {
|
|||
return label.Prefix + name
|
||||
}
|
||||
|
||||
// TODO [breaking] remove label support
|
||||
if lbl, compat := compatibilityMapping[name]; compat {
|
||||
if _, ok := annotations[lbl]; ok {
|
||||
return lbl
|
||||
}
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
|
|
|
@ -30,14 +30,6 @@ func TestGetAnnotationName(t *testing.T) {
|
|||
},
|
||||
expected: label.Prefix + annotationKubernetesPreserveHost,
|
||||
},
|
||||
{
|
||||
desc: "with label",
|
||||
name: annotationKubernetesPreserveHost,
|
||||
annotations: map[string]string{
|
||||
label.TraefikFrontendPassHostHeader: "true",
|
||||
},
|
||||
expected: label.TraefikFrontendPassHostHeader,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
|
|
|
@ -32,7 +32,6 @@ const (
|
|||
pathFrontendWhiteListSourceRange = "/whitelist/sourcerange"
|
||||
pathFrontendWhiteListUseXForwardedFor = "/whitelist/usexforwardedfor"
|
||||
|
||||
pathFrontendBasicAuth = "/basicauth" // Deprecated
|
||||
pathFrontendAuth = "/auth/"
|
||||
pathFrontendAuthBasic = pathFrontendAuth + "basic/"
|
||||
pathFrontendAuthBasicRemoveHeader = pathFrontendAuthBasic + "removeheader"
|
||||
|
|
|
@ -46,7 +46,6 @@ func (p *Provider) buildConfiguration() *types.Configuration {
|
|||
"getPassHostHeader": p.getFuncBool(pathFrontendPassHostHeader, label.DefaultPassHostHeader),
|
||||
"getPassTLSCert": p.getFuncBool(pathFrontendPassTLSCert, label.DefaultPassTLSCert),
|
||||
"getEntryPoints": p.getFuncList(pathFrontendEntryPoints),
|
||||
"getBasicAuth": p.getFuncList(pathFrontendBasicAuth), // Deprecated
|
||||
"getAuth": p.getAuth,
|
||||
"getRoutes": p.getRoutes,
|
||||
"getRedirect": p.getRedirect,
|
||||
|
@ -320,20 +319,14 @@ func (p *Provider) getTLSSection(prefix string) []*tls.Configuration {
|
|||
return tlsSection
|
||||
}
|
||||
|
||||
// hasDeprecatedBasicAuth check if the frontend basic auth use the deprecated configuration
|
||||
func (p *Provider) hasDeprecatedBasicAuth(rootPath string) bool {
|
||||
return len(p.getList(rootPath, pathFrontendBasicAuth)) > 0
|
||||
}
|
||||
|
||||
// GetAuth Create auth from path
|
||||
func (p *Provider) getAuth(rootPath string) *types.Auth {
|
||||
hasDeprecatedBasicAuth := p.hasDeprecatedBasicAuth(rootPath)
|
||||
if p.hasPrefix(rootPath, pathFrontendAuth) || hasDeprecatedBasicAuth {
|
||||
if p.hasPrefix(rootPath, pathFrontendAuth) {
|
||||
auth := &types.Auth{
|
||||
HeaderField: p.get("", rootPath, pathFrontendAuthHeaderField),
|
||||
}
|
||||
|
||||
if p.hasPrefix(rootPath, pathFrontendAuthBasic) || hasDeprecatedBasicAuth {
|
||||
if p.hasPrefix(rootPath, pathFrontendAuthBasic) {
|
||||
auth.Basic = p.getAuthBasic(rootPath)
|
||||
} else if p.hasPrefix(rootPath, pathFrontendAuthDigest) {
|
||||
auth.Digest = p.getAuthDigest(rootPath)
|
||||
|
@ -348,20 +341,11 @@ func (p *Provider) getAuth(rootPath string) *types.Auth {
|
|||
|
||||
// getAuthBasic Create Basic Auth from path
|
||||
func (p *Provider) getAuthBasic(rootPath string) *types.Basic {
|
||||
basicAuth := &types.Basic{
|
||||
return &types.Basic{
|
||||
UsersFile: p.get("", rootPath, pathFrontendAuthBasicUsersFile),
|
||||
RemoveHeader: p.getBool(false, rootPath, pathFrontendAuthBasicRemoveHeader),
|
||||
Users: p.getList(rootPath, pathFrontendAuthBasicUsers),
|
||||
}
|
||||
|
||||
// backward compatibility
|
||||
if p.hasDeprecatedBasicAuth(rootPath) {
|
||||
basicAuth.Users = p.getList(rootPath, pathFrontendBasicAuth)
|
||||
log.Warnf("Deprecated configuration found: %s. Please use %s.", pathFrontendBasicAuth, pathFrontendAuthBasic)
|
||||
} else {
|
||||
basicAuth.Users = p.getList(rootPath, pathFrontendAuthBasicUsers)
|
||||
}
|
||||
|
||||
return basicAuth
|
||||
}
|
||||
|
||||
// getAuthDigest Create Digest Auth from path
|
||||
|
|
|
@ -130,38 +130,6 @@ func TestProviderBuildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "basic auth (backward compatibility)",
|
||||
kvPairs: filler("traefik",
|
||||
frontend("frontend",
|
||||
withPair(pathFrontendBackend, "backend"),
|
||||
withList(pathFrontendBasicAuth, "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"),
|
||||
),
|
||||
backend("backend"),
|
||||
),
|
||||
expected: &types.Configuration{
|
||||
Backends: map[string]*types.Backend{
|
||||
"backend": {
|
||||
LoadBalancer: &types.LoadBalancer{
|
||||
Method: "wrr",
|
||||
},
|
||||
},
|
||||
},
|
||||
Frontends: map[string]*types.Frontend{
|
||||
"frontend": {
|
||||
Backend: "backend",
|
||||
PassHostHeader: true,
|
||||
EntryPoints: []string{},
|
||||
Auth: &types.Auth{
|
||||
Basic: &types.Basic{
|
||||
Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
|
||||
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "digest auth",
|
||||
kvPairs: filler("traefik",
|
||||
|
@ -281,7 +249,6 @@ func TestProviderBuildConfiguration(t *testing.T) {
|
|||
withList(pathFrontendWhiteListSourceRange, "1.1.1.1/24", "1234:abcd::42/32"),
|
||||
withPair(pathFrontendWhiteListUseXForwardedFor, "true"),
|
||||
|
||||
withList(pathFrontendBasicAuth, "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"),
|
||||
withPair(pathFrontendAuthBasicRemoveHeader, "true"),
|
||||
withList(pathFrontendAuthBasicUsers, "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"),
|
||||
withPair(pathFrontendAuthBasicUsersFile, ".htpasswd"),
|
||||
|
@ -2163,20 +2130,6 @@ func TestProviderGetAuth(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "should return a valid basic auth (backward compatibility)",
|
||||
rootPath: "traefik/frontends/foo",
|
||||
kvPairs: filler("traefik",
|
||||
frontend("foo",
|
||||
withPair(pathFrontendBasicAuth, "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"),
|
||||
)),
|
||||
expected: &types.Auth{
|
||||
Basic: &types.Basic{
|
||||
Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
|
||||
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "should return a valid digest auth",
|
||||
rootPath: "traefik/frontends/foo",
|
||||
|
@ -2240,61 +2193,6 @@ func TestProviderGetAuth(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestProviderHasDeprecatedBasicAuth(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
rootPath string
|
||||
kvPairs []*store.KVPair
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
desc: "should return nil when no data",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
desc: "should return a valid basic auth",
|
||||
rootPath: "traefik/frontends/foo",
|
||||
kvPairs: filler("traefik",
|
||||
frontend("foo",
|
||||
withList(pathFrontendAuthBasicUsers, "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"),
|
||||
)),
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
desc: "should return a valid basic auth",
|
||||
rootPath: "traefik/frontends/foo",
|
||||
kvPairs: filler("traefik",
|
||||
frontend("foo",
|
||||
withList(pathFrontendBasicAuth, "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"),
|
||||
)),
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
desc: "should return a valid basic auth",
|
||||
rootPath: "traefik/frontends/foo",
|
||||
kvPairs: filler("traefik",
|
||||
frontend("foo",
|
||||
withList(pathFrontendAuthBasicUsers, "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"),
|
||||
withList(pathFrontendBasicAuth, "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"),
|
||||
)),
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
p := newProviderMock(test.kvPairs)
|
||||
|
||||
result := p.hasDeprecatedBasicAuth(test.rootPath)
|
||||
|
||||
assert.Equal(t, test.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProviderGetRoutes(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
|
|
|
@ -161,7 +161,6 @@ func TestServerLoadConfigEmptyBasicAuth(t *testing.T) {
|
|||
"frontend": {
|
||||
EntryPoints: []string{"http"},
|
||||
Backend: "backend",
|
||||
BasicAuth: []string{""},
|
||||
},
|
||||
},
|
||||
Backends: map[string]*types.Backend{
|
||||
|
@ -248,7 +247,11 @@ func TestReuseBackend(t *testing.T) {
|
|||
th.WithFrontendName("frontend1"),
|
||||
th.WithEntryPoints("http"),
|
||||
th.WithRoutes(th.WithRoute("/unauthorized", "Path: /unauthorized")),
|
||||
th.WithBasicAuth("foo", "bar")),
|
||||
th.WithFrontEndAuth(&types.Auth{
|
||||
Basic: &types.Basic{
|
||||
Users: []string{"foo:bar"},
|
||||
},
|
||||
})),
|
||||
),
|
||||
th.WithBackends(th.WithBackendNew("backend",
|
||||
th.WithLBMethod("wrr"),
|
||||
|
|
|
@ -95,19 +95,6 @@ func (s *Server) buildMiddlewares(frontendName string, frontend *types.Frontend,
|
|||
middle = append(middle, handler)
|
||||
}
|
||||
|
||||
// Basic auth
|
||||
if len(frontend.BasicAuth) > 0 {
|
||||
log.Debugf("Adding basic authentication for frontend %s", frontendName)
|
||||
|
||||
authMiddleware, err := s.buildBasicAuthMiddleware(frontend.BasicAuth)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
handler := s.wrapNegroniHandlerWithAccessLog(authMiddleware, fmt.Sprintf("Basic Auth for %s", frontendName))
|
||||
middle = append(middle, handler)
|
||||
}
|
||||
|
||||
// Authentication
|
||||
if frontend.Auth != nil {
|
||||
authMiddleware, err := mauth.NewAuthenticator(frontend.Auth, s.tracingMiddleware)
|
||||
|
|
|
@ -265,7 +265,10 @@ func TestServerGenericFrontendAuthFail(t *testing.T) {
|
|||
"frontend": {
|
||||
EntryPoints: []string{"http"},
|
||||
Backend: "backend",
|
||||
BasicAuth: []string{""},
|
||||
Auth: &types.Auth{
|
||||
Basic: &types.Basic{
|
||||
Users: []string{""},
|
||||
}},
|
||||
},
|
||||
},
|
||||
Backends: map[string]*types.Backend{
|
||||
|
|
|
@ -137,14 +137,6 @@ func WithRoute(name string, rule string) func(*types.Route) string {
|
|||
}
|
||||
}
|
||||
|
||||
// WithBasicAuth is a helper to create a configuration
|
||||
// Deprecated
|
||||
func WithBasicAuth(username string, password string) func(*types.Frontend) {
|
||||
return func(fe *types.Frontend) {
|
||||
fe.BasicAuth = []string{username + ":" + password}
|
||||
}
|
||||
}
|
||||
|
||||
// WithFrontEndAuth is a helper to create a configuration
|
||||
func WithFrontEndAuth(auth *types.Auth) func(*types.Frontend) {
|
||||
return func(fe *types.Frontend) {
|
||||
|
|
|
@ -183,7 +183,6 @@ type Frontend struct {
|
|||
PassHostHeader bool `json:"passHostHeader,omitempty"`
|
||||
PassTLSCert bool `json:"passTLSCert,omitempty"`
|
||||
Priority int `json:"priority"`
|
||||
BasicAuth []string `json:"basicAuth"` // Deprecated
|
||||
WhitelistSourceRange []string `json:"whitelistSourceRange,omitempty"` // Deprecated
|
||||
WhiteList *WhiteList `json:"whiteList,omitempty"`
|
||||
Headers *Headers `json:"headers,omitempty"`
|
||||
|
|
Loading…
Reference in a new issue