This is an automated email from the ASF dual-hosted git repository. zhangjintao pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git
The following commit(s) were added to refs/heads/master by this push: new aae2105e feat: ingress annotations support enable websocket (#1101) aae2105e is described below commit aae2105e123008a0170b68a0133432695ee230c9 Author: seven dickens <language...@163.com> AuthorDate: Tue Jul 5 02:05:40 2022 +0800 feat: ingress annotations support enable websocket (#1101) --- docs/en/latest/concepts/annotations.md | 33 ++++ pkg/kube/translation/ingress.go | 8 + pkg/kube/translation/ingress_test.go | 261 +++++++++++++++++++++++++++ test/e2e/suite-annotations/websocket.go | 306 ++++++++++++++++++++++++++++++++ 4 files changed, 608 insertions(+) diff --git a/docs/en/latest/concepts/annotations.md b/docs/en/latest/concepts/annotations.md index ff215ccd..338c7562 100644 --- a/docs/en/latest/concepts/annotations.md +++ b/docs/en/latest/concepts/annotations.md @@ -171,3 +171,36 @@ spec: port: number: 80 ``` + +Enable websocket +--------- + +You can use the follow annotations to enable websocket + +* `k8s.apisix.apache.org/enable-websocket` + +If this annotations set to `true` the route will enable websoket + +For example, the following Ingress, if we set `k8s.apisix.apache.org/enable-websocket: "true"`. `/api/*` route will enable websocket + +```yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + kubernetes.io/ingress.class: apisix + k8s.apisix.apache.org/enable-websocket: "true" + name: ingress-v1 +spec: + rules: + - host: httpbin.org + http: + paths: + - path: /api/* + pathType: ImplementationSpecific + backend: + service: + name: service1 + port: + number: 80 +``` diff --git a/pkg/kube/translation/ingress.go b/pkg/kube/translation/ingress.go index 76fb1e07..8b0ab9d6 100644 --- a/pkg/kube/translation/ingress.go +++ b/pkg/kube/translation/ingress.go @@ -44,6 +44,8 @@ func (t *translator) translateIngressV1(ing *networkingv1.Ingress, skipVerify bo plugins := t.translateAnnotations(ing.Annotations) annoExtractor := annotations.NewExtractor(ing.Annotations) useRegex := annoExtractor.GetBoolAnnotation(annotations.AnnotationsPrefix + "use-regex") + enableWebsocket := annoExtractor.GetBoolAnnotation(annotations.AnnotationsPrefix + "enable-websocket") + // add https for _, tls := range ing.Spec.TLS { apisixTls := kubev2.ApisixTls{ @@ -132,6 +134,7 @@ func (t *translator) translateIngressV1(ing *networkingv1.Ingress, skipVerify bo route.ID = id.GenID(route.Name) route.Host = rule.Host route.Uris = uris + route.EnableWebsocket = enableWebsocket if len(nginxVars) > 0 { routeVars, err := t.translateRouteMatchExprs(nginxVars) if err != nil { @@ -165,6 +168,8 @@ func (t *translator) translateIngressV1beta1(ing *networkingv1beta1.Ingress, ski plugins := t.translateAnnotations(ing.Annotations) annoExtractor := annotations.NewExtractor(ing.Annotations) useRegex := annoExtractor.GetBoolAnnotation(annotations.AnnotationsPrefix + "use-regex") + enableWebsocket := annoExtractor.GetBoolAnnotation(annotations.AnnotationsPrefix + "enable-websocket") + // add https for _, tls := range ing.Spec.TLS { apisixTls := kubev2beta3.ApisixTls{ @@ -253,6 +258,7 @@ func (t *translator) translateIngressV1beta1(ing *networkingv1beta1.Ingress, ski route.ID = id.GenID(route.Name) route.Host = rule.Host route.Uris = uris + route.EnableWebsocket = enableWebsocket if len(nginxVars) > 0 { routeVars, err := t.translateRouteMatchExprs(nginxVars) if err != nil { @@ -340,6 +346,7 @@ func (t *translator) translateIngressExtensionsV1beta1(ing *extensionsv1beta1.In plugins := t.translateAnnotations(ing.Annotations) annoExtractor := annotations.NewExtractor(ing.Annotations) useRegex := annoExtractor.GetBoolAnnotation(annotations.AnnotationsPrefix + "use-regex") + enableWebsocket := annoExtractor.GetBoolAnnotation(annotations.AnnotationsPrefix + "enable-websocket") for _, rule := range ing.Spec.Rules { for _, pathRule := range rule.HTTP.Paths { @@ -400,6 +407,7 @@ func (t *translator) translateIngressExtensionsV1beta1(ing *extensionsv1beta1.In route.ID = id.GenID(route.Name) route.Host = rule.Host route.Uris = uris + route.EnableWebsocket = enableWebsocket if len(nginxVars) > 0 { routeVars, err := t.translateRouteMatchExprs(nginxVars) if err != nil { diff --git a/pkg/kube/translation/ingress_test.go b/pkg/kube/translation/ingress_test.go index a2365c66..fed2ae28 100644 --- a/pkg/kube/translation/ingress_test.go +++ b/pkg/kube/translation/ingress_test.go @@ -1048,3 +1048,264 @@ func TestTranslateIngressExtensionsV1beta1WithRegex(t *testing.T) { assert.Equal(t, []string{"/*"}, ctx.Routes[0].Uris) assert.Equal(t, expectedVars, ctx.Routes[0].Vars) } + +func TestTranslateIngressV1WithWebsocket(t *testing.T) { + prefix := networkingv1.PathTypeImplementationSpecific + regexPath := "/foo/*/bar" + ing := &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + Annotations: map[string]string{ + "k8s.apisix.apache.org/enable-websocket": "true", + }, + }, + Spec: networkingv1.IngressSpec{ + Rules: []networkingv1.IngressRule{ + { + Host: "apisix.apache.org", + IngressRuleValue: networkingv1.IngressRuleValue{ + HTTP: &networkingv1.HTTPIngressRuleValue{ + Paths: []networkingv1.HTTPIngressPath{ + { + Path: regexPath, + PathType: &prefix, + Backend: networkingv1.IngressBackend{ + Service: &networkingv1.IngressServiceBackend{ + Name: "test-service", + Port: networkingv1.ServiceBackendPort{ + Name: "port1", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + client := fake.NewSimpleClientset() + informersFactory := informers.NewSharedInformerFactory(client, 0) + svcInformer := informersFactory.Core().V1().Services().Informer() + svcLister := informersFactory.Core().V1().Services().Lister() + epLister, epInformer := kube.NewEndpointListerAndInformer(informersFactory, false) + apisixClient := fakeapisix.NewSimpleClientset() + apisixInformersFactory := apisixinformers.NewSharedInformerFactory(apisixClient, 0) + processCh := make(chan struct{}) + svcInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + processCh <- struct{}{} + }, + }) + epInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + processCh <- struct{}{} + }, + }) + + stopCh := make(chan struct{}) + defer close(stopCh) + go svcInformer.Run(stopCh) + go epInformer.Run(stopCh) + cache.WaitForCacheSync(stopCh, svcInformer.HasSynced) + + _, err := client.CoreV1().Services("default").Create(context.Background(), _testSvc, metav1.CreateOptions{}) + assert.Nil(t, err) + _, err = client.CoreV1().Endpoints("default").Create(context.Background(), _testEp, metav1.CreateOptions{}) + assert.Nil(t, err) + + tr := &translator{ + TranslatorOptions: &TranslatorOptions{ + ServiceLister: svcLister, + EndpointLister: epLister, + ApisixUpstreamLister: apisixInformersFactory.Apisix().V2beta3().ApisixUpstreams().Lister(), + }, + } + + <-processCh + <-processCh + ctx, err := tr.translateIngressV1(ing, false) + assert.Nil(t, err) + assert.Len(t, ctx.Routes, 1) + assert.Len(t, ctx.Upstreams, 1) + // the number of the PluginConfigs should be zero, cause there no available Annotations matched te rule + assert.Len(t, ctx.PluginConfigs, 0) + + assert.Equal(t, true, ctx.Routes[0].EnableWebsocket) +} + +func TestTranslateIngressV1beta1WithWebsocket(t *testing.T) { + prefix := networkingv1beta1.PathTypeImplementationSpecific + // no backend. + regexPath := "/foo/*/bar" + ing := &networkingv1beta1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + Annotations: map[string]string{ + "k8s.apisix.apache.org/enable-websocket": "true", + }, + }, + Spec: networkingv1beta1.IngressSpec{ + Rules: []networkingv1beta1.IngressRule{ + { + Host: "apisix.apache.org", + IngressRuleValue: networkingv1beta1.IngressRuleValue{ + HTTP: &networkingv1beta1.HTTPIngressRuleValue{ + Paths: []networkingv1beta1.HTTPIngressPath{ + { + Path: regexPath, + PathType: &prefix, + Backend: networkingv1beta1.IngressBackend{ + ServiceName: "test-service", + ServicePort: intstr.IntOrString{ + Type: intstr.String, + StrVal: "port1", + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + client := fake.NewSimpleClientset() + informersFactory := informers.NewSharedInformerFactory(client, 0) + svcInformer := informersFactory.Core().V1().Services().Informer() + svcLister := informersFactory.Core().V1().Services().Lister() + epLister, epInformer := kube.NewEndpointListerAndInformer(informersFactory, false) + apisixClient := fakeapisix.NewSimpleClientset() + apisixInformersFactory := apisixinformers.NewSharedInformerFactory(apisixClient, 0) + processCh := make(chan struct{}) + svcInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + processCh <- struct{}{} + }, + }) + epInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + processCh <- struct{}{} + }, + }) + + stopCh := make(chan struct{}) + defer close(stopCh) + go svcInformer.Run(stopCh) + go epInformer.Run(stopCh) + cache.WaitForCacheSync(stopCh, svcInformer.HasSynced) + + _, err := client.CoreV1().Services("default").Create(context.Background(), _testSvc, metav1.CreateOptions{}) + assert.Nil(t, err) + _, err = client.CoreV1().Endpoints("default").Create(context.Background(), _testEp, metav1.CreateOptions{}) + assert.Nil(t, err) + + tr := &translator{ + TranslatorOptions: &TranslatorOptions{ + ServiceLister: svcLister, + EndpointLister: epLister, + ApisixUpstreamLister: apisixInformersFactory.Apisix().V2beta3().ApisixUpstreams().Lister(), + }, + } + + <-processCh + <-processCh + ctx, err := tr.translateIngressV1beta1(ing, false) + assert.Nil(t, err) + assert.Len(t, ctx.Routes, 1) + assert.Len(t, ctx.Upstreams, 1) + // the number of the PluginConfigs should be zero, cause there no available Annotations matched te rule + assert.Len(t, ctx.PluginConfigs, 0) + + assert.Nil(t, err) + assert.Equal(t, true, ctx.Routes[0].EnableWebsocket) +} + +func TestTranslateIngressExtensionsV1beta1WithWebsocket(t *testing.T) { + prefix := extensionsv1beta1.PathTypeImplementationSpecific + regexPath := "/foo/*/bar" + ing := &extensionsv1beta1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + Annotations: map[string]string{ + "k8s.apisix.apache.org/enable-websocket": "true", + }, + }, + Spec: extensionsv1beta1.IngressSpec{ + Rules: []extensionsv1beta1.IngressRule{ + { + Host: "apisix.apache.org", + IngressRuleValue: extensionsv1beta1.IngressRuleValue{ + HTTP: &extensionsv1beta1.HTTPIngressRuleValue{ + Paths: []extensionsv1beta1.HTTPIngressPath{ + { + Path: regexPath, + PathType: &prefix, + Backend: extensionsv1beta1.IngressBackend{ + ServiceName: "test-service", + ServicePort: intstr.IntOrString{ + Type: intstr.String, + StrVal: "port1", + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + client := fake.NewSimpleClientset() + informersFactory := informers.NewSharedInformerFactory(client, 0) + svcInformer := informersFactory.Core().V1().Services().Informer() + svcLister := informersFactory.Core().V1().Services().Lister() + epLister, epInformer := kube.NewEndpointListerAndInformer(informersFactory, false) + apisixClient := fakeapisix.NewSimpleClientset() + apisixInformersFactory := apisixinformers.NewSharedInformerFactory(apisixClient, 0) + processCh := make(chan struct{}) + svcInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + processCh <- struct{}{} + }, + }) + epInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + processCh <- struct{}{} + }, + }) + + stopCh := make(chan struct{}) + defer close(stopCh) + go svcInformer.Run(stopCh) + go epInformer.Run(stopCh) + cache.WaitForCacheSync(stopCh, svcInformer.HasSynced) + + _, err := client.CoreV1().Services("default").Create(context.Background(), _testSvc, metav1.CreateOptions{}) + assert.Nil(t, err) + _, err = client.CoreV1().Endpoints("default").Create(context.Background(), _testEp, metav1.CreateOptions{}) + assert.Nil(t, err) + + tr := &translator{ + TranslatorOptions: &TranslatorOptions{ + ServiceLister: svcLister, + EndpointLister: epLister, + ApisixUpstreamLister: apisixInformersFactory.Apisix().V2beta3().ApisixUpstreams().Lister(), + }, + } + + <-processCh + <-processCh + ctx, err := tr.translateIngressExtensionsV1beta1(ing, false) + assert.Nil(t, err) + assert.Len(t, ctx.Routes, 1) + assert.Len(t, ctx.Upstreams, 1) + // the number of the PluginConfigs should be zero, cause there no available Annotations matched te rule + assert.Len(t, ctx.PluginConfigs, 0) + + assert.Equal(t, true, ctx.Routes[0].EnableWebsocket) +} diff --git a/test/e2e/suite-annotations/websocket.go b/test/e2e/suite-annotations/websocket.go new file mode 100644 index 00000000..3ffb5920 --- /dev/null +++ b/test/e2e/suite-annotations/websocket.go @@ -0,0 +1,306 @@ +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package annotations + +import ( + "net/http" + "net/url" + "time" + + "github.com/gorilla/websocket" + ginkgo "github.com/onsi/ginkgo/v2" + "github.com/stretchr/testify/assert" + + "github.com/apache/apisix-ingress-controller/test/e2e/scaffold" +) + +var _ = ginkgo.Describe("suite-annotations: annotations.networking/v1 websocket", func() { + opts := &scaffold.Options{ + Name: "default", + Kubeconfig: scaffold.GetKubeconfig(), + APISIXConfigPath: "testdata/apisix-gw-config.yaml", + IngressAPISIXReplicas: 1, + HTTPBinServicePort: 80, + APISIXRouteVersion: "apisix.apache.org/v2beta3", + } + s := scaffold.NewScaffold(opts) + ginkgo.It("sanity", func() { + resources := ` +apiVersion: v1 +kind: Pod +metadata: + name: websocket-server + labels: + app: websocket-server +spec: + containers: + - name: websocket-server + image: localhost:5000/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: 48733 + protocol: TCP + targetPort: 8080 +` + err := s.CreateResourceFromString(resources) + assert.Nil(ginkgo.GinkgoT(), err) + time.Sleep(5 * time.Second) + + ing := ` +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + kubernetes.io/ingress.class: apisix + k8s.apisix.apache.org/enable-websocket: 'true' + name: ingress-v1 +spec: + rules: + - host: httpbin.org + http: + paths: + - path: /echo + pathType: ImplementationSpecific + backend: + service: + name: websocket-server-service + port: + number: 48733 +` + assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ing)) + err = s.EnsureNumApisixUpstreamsCreated(1) + assert.Nil(ginkgo.GinkgoT(), err, "Checking number of upstreams") + err = s.EnsureNumApisixRoutesCreated(1) + assert.Nil(ginkgo.GinkgoT(), err, "Checking number of routes") + + dialer := websocket.Dialer{} + u := url.URL{ + Scheme: "ws", + Host: s.APISIXGatewayServiceEndpoint(), + Path: "/echo", + } + header := http.Header{ + "Host": []string{"httpbin.org"}, + } + conn, resp, err := dialer.Dial(u.String(), header) + assert.Nil(ginkgo.GinkgoT(), err, "websocket handshake failure") + assert.Equal(ginkgo.GinkgoT(), resp.StatusCode, http.StatusSwitchingProtocols) + + assert.Nil(ginkgo.GinkgoT(), conn.WriteMessage(websocket.TextMessage, []byte("hello, I'm gorilla")), "writing message") + msgType, buf, err := conn.ReadMessage() + assert.Nil(ginkgo.GinkgoT(), err, "reading message") + assert.Equal(ginkgo.GinkgoT(), string(buf), "Request served by websocket-server") + msgType, buf, err = conn.ReadMessage() + assert.Nil(ginkgo.GinkgoT(), err, "reading message") + assert.Equal(ginkgo.GinkgoT(), msgType, websocket.TextMessage) + assert.Equal(ginkgo.GinkgoT(), string(buf), "hello, I'm gorilla") + assert.Nil(ginkgo.GinkgoT(), conn.Close(), "closing ws connection") + }) +}) + +var _ = ginkgo.Describe("suite-annotations: annotations.networking/v1beta1 with websocket", func() { + opts := &scaffold.Options{ + Name: "default", + Kubeconfig: scaffold.GetKubeconfig(), + APISIXConfigPath: "testdata/apisix-gw-config.yaml", + IngressAPISIXReplicas: 1, + HTTPBinServicePort: 80, + APISIXRouteVersion: "apisix.apache.org/v2beta3", + } + s := scaffold.NewScaffold(opts) + ginkgo.It("sanity", func() { + resources := ` +apiVersion: v1 +kind: Pod +metadata: + name: websocket-server + labels: + app: websocket-server +spec: + containers: + - name: websocket-server + image: localhost:5000/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: 48733 + protocol: TCP + targetPort: 8080 +` + err := s.CreateResourceFromString(resources) + assert.Nil(ginkgo.GinkgoT(), err) + time.Sleep(5 * time.Second) + + ing := ` +apiVersion: networking.k8s.io/v1beta1 +kind: Ingress +metadata: + name: ingress-v1beta1 + annotations: + kubernetes.io/ingress.class: apisix + k8s.apisix.apache.org/enable-websocket: 'true' +spec: + rules: + - host: httpbin.org + http: + paths: + - path: /echo + pathType: Exact + backend: + serviceName: websocket-server-service + servicePort: 48733 +` + assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ing)) + err = s.EnsureNumApisixUpstreamsCreated(1) + assert.Nil(ginkgo.GinkgoT(), err, "Checking number of upstreams") + err = s.EnsureNumApisixRoutesCreated(1) + assert.Nil(ginkgo.GinkgoT(), err, "Checking number of routes") + + dialer := websocket.Dialer{} + u := url.URL{ + Scheme: "ws", + Host: s.APISIXGatewayServiceEndpoint(), + Path: "/echo", + } + header := http.Header{ + "Host": []string{"httpbin.org"}, + } + conn, resp, err := dialer.Dial(u.String(), header) + assert.Nil(ginkgo.GinkgoT(), err, "websocket handshake failure") + assert.Equal(ginkgo.GinkgoT(), resp.StatusCode, http.StatusSwitchingProtocols) + + assert.Nil(ginkgo.GinkgoT(), conn.WriteMessage(websocket.TextMessage, []byte("hello, I'm gorilla")), "writing message") + msgType, buf, err := conn.ReadMessage() + assert.Nil(ginkgo.GinkgoT(), err, "reading message") + assert.Equal(ginkgo.GinkgoT(), string(buf), "Request served by websocket-server") + msgType, buf, err = conn.ReadMessage() + assert.Nil(ginkgo.GinkgoT(), err, "reading message") + assert.Equal(ginkgo.GinkgoT(), msgType, websocket.TextMessage) + assert.Equal(ginkgo.GinkgoT(), string(buf), "hello, I'm gorilla") + assert.Nil(ginkgo.GinkgoT(), conn.Close(), "closing ws connection") + }) +}) + +var _ = ginkgo.Describe("suite-annotations: ingress.networking/v1beta1 with websocket", func() { + opts := &scaffold.Options{ + Name: "default", + Kubeconfig: scaffold.GetKubeconfig(), + APISIXConfigPath: "testdata/apisix-gw-config.yaml", + IngressAPISIXReplicas: 1, + HTTPBinServicePort: 80, + APISIXRouteVersion: "apisix.apache.org/v2beta3", + } + s := scaffold.NewScaffold(opts) + ginkgo.It("sanity", func() { + resources := ` +apiVersion: v1 +kind: Pod +metadata: + name: websocket-server + labels: + app: websocket-server +spec: + containers: + - name: websocket-server + image: localhost:5000/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: 48733 + protocol: TCP + targetPort: 8080 +` + err := s.CreateResourceFromString(resources) + assert.Nil(ginkgo.GinkgoT(), err) + time.Sleep(5 * time.Second) + + ing := ` +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: ingress-ext-v1beta1 + annotations: + kubernetes.io/ingress.class: apisix + k8s.apisix.apache.org/enable-websocket: 'true' +spec: + rules: + - host: httpbin.org + http: + paths: + - path: /echo + pathType: Exact + backend: + serviceName: websocket-server-service + servicePort: 48733 +` + assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ing)) + err = s.EnsureNumApisixUpstreamsCreated(1) + assert.Nil(ginkgo.GinkgoT(), err, "Checking number of upstreams") + err = s.EnsureNumApisixRoutesCreated(1) + assert.Nil(ginkgo.GinkgoT(), err, "Checking number of routes") + + dialer := websocket.Dialer{} + u := url.URL{ + Scheme: "ws", + Host: s.APISIXGatewayServiceEndpoint(), + Path: "/echo", + } + header := http.Header{ + "Host": []string{"httpbin.org"}, + } + conn, resp, err := dialer.Dial(u.String(), header) + assert.Nil(ginkgo.GinkgoT(), err, "websocket handshake failure") + assert.Equal(ginkgo.GinkgoT(), resp.StatusCode, http.StatusSwitchingProtocols) + + assert.Nil(ginkgo.GinkgoT(), conn.WriteMessage(websocket.TextMessage, []byte("hello, I'm gorilla")), "writing message") + msgType, buf, err := conn.ReadMessage() + assert.Nil(ginkgo.GinkgoT(), err, "reading message") + assert.Equal(ginkgo.GinkgoT(), string(buf), "Request served by websocket-server") + msgType, buf, err = conn.ReadMessage() + assert.Nil(ginkgo.GinkgoT(), err, "reading message") + assert.Equal(ginkgo.GinkgoT(), msgType, websocket.TextMessage) + assert.Equal(ginkgo.GinkgoT(), string(buf), "hello, I'm gorilla") + assert.Nil(ginkgo.GinkgoT(), conn.Close(), "closing ws connection") + }) +})