Fix HostRegexp and Query muxers
This commit is contained in:
parent
77e1ce2877
commit
a887794313
7 changed files with 246 additions and 6 deletions
3
go.mod
3
go.mod
|
@ -176,7 +176,6 @@ require (
|
|||
github.com/googleapis/gnostic v0.5.5 // indirect
|
||||
github.com/gophercloud/gophercloud v0.16.0 // indirect
|
||||
github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae // indirect
|
||||
github.com/gorilla/context v1.1.1 // indirect
|
||||
github.com/gravitational/trace v1.1.16-0.20220114165159-14a9a7dd6aaf // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.2.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect
|
||||
|
@ -334,7 +333,7 @@ require (
|
|||
replace (
|
||||
github.com/abbot/go-http-auth => github.com/containous/go-http-auth v0.4.1-0.20200324110947-a37a7636d23e
|
||||
github.com/go-check/check => github.com/containous/check v0.0.0-20170915194414-ca0bf163426a
|
||||
github.com/gorilla/mux => github.com/containous/mux v0.0.0-20220113180107-8ffa4f6d063c
|
||||
github.com/gorilla/mux => github.com/containous/mux v0.0.0-20220627093034-b2dd784e613f
|
||||
github.com/mailgun/minheap => github.com/containous/minheap v0.0.0-20190809180810-6e71eb837595
|
||||
)
|
||||
|
||||
|
|
5
go.sum
5
go.sum
|
@ -456,8 +456,8 @@ github.com/containous/go-http-auth v0.4.1-0.20200324110947-a37a7636d23e h1:D+uTE
|
|||
github.com/containous/go-http-auth v0.4.1-0.20200324110947-a37a7636d23e/go.mod h1:s8kLgBQolDbsJOPVIGCEEv9zGAKUUf/685Gi0Qqg8z8=
|
||||
github.com/containous/minheap v0.0.0-20190809180810-6e71eb837595 h1:aPspFRO6b94To3gl4yTDOEtpjFwXI7V2W+z0JcNljQ4=
|
||||
github.com/containous/minheap v0.0.0-20190809180810-6e71eb837595/go.mod h1:+lHFbEasIiQVGzhVDVw/cn0ZaOzde2OwNncp1NhXV4c=
|
||||
github.com/containous/mux v0.0.0-20220113180107-8ffa4f6d063c h1:g6JvgTtfpS6AfhRjY87NZ0g39CrNDbdm8R+1CD85Cfo=
|
||||
github.com/containous/mux v0.0.0-20220113180107-8ffa4f6d063c/go.mod h1:z8WW7n06n8/1xF9Jl9WmuDeZuHAhfL+bwarNjsciwwg=
|
||||
github.com/containous/mux v0.0.0-20220627093034-b2dd784e613f h1:1uEtynq2C0ljy3630jt7EAxg8jZY2gy6YHdGwdqEpWw=
|
||||
github.com/containous/mux v0.0.0-20220627093034-b2dd784e613f/go.mod h1:z8WW7n06n8/1xF9Jl9WmuDeZuHAhfL+bwarNjsciwwg=
|
||||
github.com/coredns/coredns v1.1.2/go.mod h1:zASH/MVDgR6XZTbxvOnsZfffS+31vg6Ackf/wo1+AM0=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
|
@ -930,7 +930,6 @@ github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae h1:Hi3IgB9RQDE15
|
|||
github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae/go.mod h1:wx8HMD8oQD0Ryhz6+6ykq75PJ79iPyEqYHfwZ4l7OsA=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
||||
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
|
||||
|
|
44
integration/fixtures/simple_muxer.toml
Normal file
44
integration/fixtures/simple_muxer.toml
Normal file
|
@ -0,0 +1,44 @@
|
|||
[global]
|
||||
checkNewVersion = false
|
||||
sendAnonymousUsage = false
|
||||
|
||||
[log]
|
||||
level = "DEBUG"
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.webHost]
|
||||
address = ":8000"
|
||||
[entryPoints.webHostRegexp]
|
||||
address = ":8001"
|
||||
[entryPoints.webQuery]
|
||||
address = ":8002"
|
||||
|
||||
[api]
|
||||
insecure = true
|
||||
|
||||
[providers.file]
|
||||
filename = "{{ .SelfFilename }}"
|
||||
|
||||
## dynamic configuration ##
|
||||
|
||||
[http.routers]
|
||||
[http.routers.router1]
|
||||
entryPoints = ["webHost"]
|
||||
service = "service1"
|
||||
rule = "!Host(`test.localhost`)"
|
||||
|
||||
[http.routers.router2]
|
||||
entryPoints = ["webHostRegexp"]
|
||||
service = "service1"
|
||||
rule = "!HostRegexp(`test.localhost`)"
|
||||
|
||||
[http.routers.router3]
|
||||
entryPoints = ["webQuery"]
|
||||
service = "service1"
|
||||
rule = "!Query(`foo=`)"
|
||||
|
||||
|
||||
[http.services]
|
||||
[http.services.service1.loadBalancer]
|
||||
[[http.services.service1.loadBalancer.servers]]
|
||||
url = "{{ .Server1 }}"
|
|
@ -1,6 +1,7 @@
|
|||
package integration
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
|
@ -1178,3 +1179,124 @@ func (s *SimpleSuite) TestContentTypeDisableAutoDetect(c *check.C) {
|
|||
})
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
func (s *SimpleSuite) TestMuxer(c *check.C) {
|
||||
s.createComposeProject(c, "base")
|
||||
|
||||
s.composeUp(c)
|
||||
defer s.composeDown(c)
|
||||
|
||||
whoami1URL := "http://" + net.JoinHostPort(s.getComposeServiceIP(c, "whoami1"), "80")
|
||||
|
||||
file := s.adaptFile(c, "fixtures/simple_muxer.toml", struct {
|
||||
Server1 string
|
||||
}{whoami1URL})
|
||||
defer os.Remove(file)
|
||||
|
||||
cmd, output := s.traefikCmd(withConfigFile(file))
|
||||
defer output(c)
|
||||
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer s.killCmd(cmd)
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("!Host"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
request string
|
||||
target string
|
||||
body string
|
||||
expected int
|
||||
}{
|
||||
{
|
||||
desc: "!Host with absolute-form URL with empty host and host header, no match",
|
||||
request: "GET http://@/ HTTP/1.1\r\nHost: test.localhost\r\n\r\n",
|
||||
target: "127.0.0.1:8000",
|
||||
expected: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
desc: "!Host with absolute-form URL with empty host and host header, match",
|
||||
request: "GET http://@/ HTTP/1.1\r\nHost: toto.localhost\r\n\r\n",
|
||||
target: "127.0.0.1:8000",
|
||||
expected: http.StatusOK,
|
||||
},
|
||||
{
|
||||
desc: "!Host with absolute-form URL and host header, no match",
|
||||
request: "GET http://test.localhost/ HTTP/1.1\r\nHost: toto.localhost\r\n\r\n",
|
||||
target: "127.0.0.1:8000",
|
||||
expected: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
desc: "!Host with absolute-form URL and host header, match",
|
||||
request: "GET http://toto.localhost/ HTTP/1.1\r\nHost: test.localhost\r\n\r\n",
|
||||
target: "127.0.0.1:8000",
|
||||
expected: http.StatusOK,
|
||||
},
|
||||
{
|
||||
desc: "!HostRegexp with absolute-form URL with empty host and host header, no match",
|
||||
request: "GET http://@/ HTTP/1.1\r\nHost: test.localhost\r\n\r\n",
|
||||
target: "127.0.0.1:8001",
|
||||
expected: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
desc: "!HostRegexp with absolute-form URL with empty host and host header, match",
|
||||
request: "GET http://@/ HTTP/1.1\r\nHost: toto.localhost\r\n\r\n",
|
||||
target: "127.0.0.1:8001",
|
||||
expected: http.StatusOK,
|
||||
},
|
||||
{
|
||||
desc: "!HostRegexp with absolute-form URL and host header, no match",
|
||||
request: "GET http://test.localhost/ HTTP/1.1\r\nHost: toto.localhost\r\n\r\n",
|
||||
target: "127.0.0.1:8001",
|
||||
expected: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
desc: "!HostRegexp with absolute-form URL and host header, match",
|
||||
request: "GET http://toto.localhost/ HTTP/1.1\r\nHost: test.localhost\r\n\r\n",
|
||||
target: "127.0.0.1:8001",
|
||||
expected: http.StatusOK,
|
||||
},
|
||||
{
|
||||
desc: "!Query with semicolon, no match",
|
||||
request: "GET /?foo=; HTTP/1.1\r\nHost: other.localhost\r\n\r\n",
|
||||
target: "127.0.0.1:8002",
|
||||
expected: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
desc: "!Query with semicolon, no match",
|
||||
request: "GET /?foo=titi;bar=toto HTTP/1.1\r\nHost: other.localhost\r\n\r\n",
|
||||
target: "127.0.0.1:8002",
|
||||
expected: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
desc: "!Query with semicolon, match",
|
||||
request: "GET /?bar=toto;boo=titi HTTP/1.1\r\nHost: other.localhost\r\n\r\n",
|
||||
target: "127.0.0.1:8002",
|
||||
expected: http.StatusOK,
|
||||
body: "bar=toto&boo=titi",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
conn, err := net.Dial("tcp", test.target)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
_, err = conn.Write([]byte(test.request))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
resp, err := http.ReadResponse(bufio.NewReader(conn), nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
if resp.StatusCode != test.expected {
|
||||
c.Errorf("%s failed with %d instead of %d", test.desc, resp.StatusCode, test.expected)
|
||||
}
|
||||
|
||||
if test.body != "" {
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(string(body), checker.Contains, test.body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
@ -956,3 +958,74 @@ func TestParseDomains(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAbsoluteFormURL(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
request string
|
||||
rule string
|
||||
expected int
|
||||
}{
|
||||
{
|
||||
desc: "!HostRegexp with absolute-form URL with empty host with non-matching host header",
|
||||
request: "GET http://@/ HTTP/1.1\r\nHost: test.localhost\r\n\r\n",
|
||||
rule: "!HostRegexp(`test.localhost`)",
|
||||
expected: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
desc: "!Host with absolute-form URL with empty host with non-matching host header",
|
||||
request: "GET http://@/ HTTP/1.1\r\nHost: test.localhost\r\n\r\n",
|
||||
rule: "!Host(`test.localhost`)",
|
||||
expected: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
desc: "!HostRegexp with absolute-form URL with matching host header",
|
||||
request: "GET http://test.localhost/ HTTP/1.1\r\nHost: toto.localhost\r\n\r\n",
|
||||
rule: "!HostRegexp(`test.localhost`)",
|
||||
expected: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
desc: "!Host with absolute-form URL with matching host header",
|
||||
request: "GET http://test.localhost/ HTTP/1.1\r\nHost: toto.localhost\r\n\r\n",
|
||||
rule: "!Host(`test.localhost`)",
|
||||
expected: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
desc: "!HostRegexp with absolute-form URL with non-matching host header",
|
||||
request: "GET http://test.localhost/ HTTP/1.1\r\nHost: toto.localhost\r\n\r\n",
|
||||
rule: "!HostRegexp(`toto.localhost`)",
|
||||
expected: http.StatusOK,
|
||||
},
|
||||
{
|
||||
desc: "!Host with absolute-form URL with non-matching host header",
|
||||
request: "GET http://test.localhost/ HTTP/1.1\r\nHost: toto.localhost\r\n\r\n",
|
||||
rule: "!Host(`toto.localhost`)",
|
||||
expected: http.StatusOK,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
muxer, err := NewMuxer()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = muxer.AddRoute(test.rule, 0, handler)
|
||||
require.NoError(t, err)
|
||||
|
||||
// RequestDecorator is necessary for the host rule
|
||||
reqHost := requestdecorator.New(nil)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
req, err := http.ReadRequest(bufio.NewReader(bytes.NewReader([]byte(test.request))))
|
||||
require.NoError(t, err)
|
||||
|
||||
reqHost.ServeHTTP(w, req, muxer.ServeHTTP)
|
||||
assert.Equal(t, test.expected, w.Code)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -523,6 +523,8 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati
|
|||
return nil, err
|
||||
}
|
||||
|
||||
handler = http.AllowQuerySemicolons(handler)
|
||||
|
||||
if withH2c {
|
||||
handler = h2c.NewHandler(handler, &http2.Server{})
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
ptypes "github.com/traefik/paerser/types"
|
||||
|
@ -46,7 +47,7 @@ func buildProxy(passHostHeader *bool, responseForwarding *dynamic.ResponseForwar
|
|||
|
||||
outReq.URL.Path = u.Path
|
||||
outReq.URL.RawPath = u.RawPath
|
||||
outReq.URL.RawQuery = u.RawQuery
|
||||
outReq.URL.RawQuery = strings.ReplaceAll(u.RawQuery, ";", "&")
|
||||
outReq.RequestURI = "" // Outgoing request should not have RequestURI
|
||||
|
||||
outReq.Proto = "HTTP/1.1"
|
||||
|
|
Loading…
Reference in a new issue