diff --git a/internal/provider/adc/translator/apisixroute.go b/internal/provider/adc/translator/apisixroute.go index 3cf1a68cd7..7ff1ed20bb 100644 --- a/internal/provider/adc/translator/apisixroute.go +++ b/internal/provider/adc/translator/apisixroute.go @@ -177,7 +177,7 @@ func (t *Translator) buildRoute(ar *apiv2.ApisixRoute, service *adc.Service, rul route.ID = id.GenID(route.Name) route.Desc = "Created by apisix-ingress-controller, DO NOT modify it manually" route.Labels = label.GenLabel(ar) - route.EnableWebsocket = ptr.To(true) + route.EnableWebsocket = ptr.To(rule.Websocket) route.FilterFunc = rule.Match.FilterFunc route.Hosts = rule.Match.Hosts route.Methods = rule.Match.Methods diff --git a/test/e2e/crds/v2/route.go b/test/e2e/crds/v2/route.go index 78d14636ce..21bb426d42 100644 --- a/test/e2e/crds/v2/route.go +++ b/test/e2e/crds/v2/route.go @@ -23,8 +23,10 @@ import ( "io" "net" "net/http" + "net/url" "time" + "github.com/gorilla/websocket" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "k8s.io/apimachinery/pkg/types" @@ -757,4 +759,137 @@ spec: }) }) }) + + Context("Test ApisixRoute WebSocket Support", func() { + It("basic websocket functionality", func() { + const websocketServerResources = ` +apiVersion: v1 +kind: Pod +metadata: + name: websocket-server + labels: + app: websocket-server +spec: + containers: + - name: websocket-server + image: jmalloc/echo-server:latest + ports: + - containerPort: 8080 +--- +apiVersion: v1 +kind: Service +metadata: + name: websocket-server-service +spec: + selector: + app: websocket-server + ports: + - name: ws + port: 8080 + protocol: TCP + targetPort: 8080 +` + const apisixRouteSpec = ` +apiVersion: apisix.apache.org/v2 +kind: ApisixRoute +metadata: + name: websocket-route +spec: + ingressClassName: apisix + http: + - name: rule1 + match: + hosts: + - httpbin.org + paths: + - /echo + websocket: true + backends: + - serviceName: websocket-server-service + servicePort: 8080 +` + + const apisixRouteSpec2 = ` +apiVersion: apisix.apache.org/v2 +kind: ApisixRoute +metadata: + name: websocket-route +spec: + ingressClassName: apisix + http: + - name: rule1 + match: + hosts: + - httpbin.org + paths: + - /echo + backends: + - serviceName: websocket-server-service + servicePort: 8080 +` + + By("create WebSocket server resources") + err := s.CreateResourceFromString(websocketServerResources) + Expect(err).ShouldNot(HaveOccurred(), "creating WebSocket server resources") + + By("create ApisixRoute without WebSocker") + var apisixRouteWithoutWS apiv2.ApisixRoute + applier.MustApplyAPIv2( + types.NamespacedName{Namespace: s.Namespace(), Name: "websocket-route"}, + &apisixRouteWithoutWS, + apisixRouteSpec2, + ) + time.Sleep(8 * time.Second) + + By("verify WebSocket connection fails without WebSocket enabled") + u := url.URL{ + Scheme: "ws", + Host: s.ApisixHTTPEndpoint(), + Path: "/echo", + } + headers := http.Header{"Host": []string{"httpbin.org"}} + _, resp, _ := websocket.DefaultDialer.Dial(u.String(), headers) + // should receive 200 instead of 101 + Expect(resp.StatusCode).Should(Equal(http.StatusOK)) + + By("apply ApisixRoute for WebSocket") + var apisixRoute apiv2.ApisixRoute + applier.MustApplyAPIv2( + types.NamespacedName{Namespace: s.Namespace(), Name: "websocket-route"}, + &apisixRoute, + apisixRouteSpec, + ) + By("wait for WebSocket server to be ready") + time.Sleep(10 * time.Second) + By("verify WebSocket connection") + u = url.URL{ + Scheme: "ws", + Host: s.ApisixHTTPEndpoint(), + Path: "/echo", + } + headers = http.Header{"Host": []string{"httpbin.org"}} + + conn, resp, err := websocket.DefaultDialer.Dial(u.String(), headers) + Expect(err).ShouldNot(HaveOccurred(), "WebSocket handshake") + Expect(resp.StatusCode).Should(Equal(http.StatusSwitchingProtocols)) + + defer func() { + _ = conn.Close() + }() + + By("send and receive message through WebSocket") + testMessage := "hello, this is APISIX" + err = conn.WriteMessage(websocket.TextMessage, []byte(testMessage)) + Expect(err).ShouldNot(HaveOccurred(), "writing WebSocket message") + + // The echo server sends an identification message first + _, _, err = conn.ReadMessage() + Expect(err).ShouldNot(HaveOccurred(), "reading identification message") + + // Then our echo + _, msg, err := conn.ReadMessage() + Expect(err).ShouldNot(HaveOccurred(), "reading echo message") + Expect(string(msg)).To(Equal(testMessage), "message content verification") + }) + }) }) diff --git a/test/e2e/scaffold/scaffold.go b/test/e2e/scaffold/scaffold.go index 82c01f47b5..48cc3b81b1 100644 --- a/test/e2e/scaffold/scaffold.go +++ b/test/e2e/scaffold/scaffold.go @@ -162,6 +162,10 @@ func (s *Scaffold) NewAPISIXClient() *httpexpect.Expect { }) } +func (s *Scaffold) ApisixHTTPEndpoint() string { + return s.apisixHttpTunnel.Endpoint() +} + // GetAPISIXHTTPSEndpoint get apisix https endpoint from tunnel map func (s *Scaffold) GetAPISIXHTTPSEndpoint() string { return s.apisixHttpsTunnel.Endpoint()