current position:Home>How to make Zuul support WebSocket

How to make Zuul support WebSocket

2022-08-06 17:27:30Qianshandu

问题背景

A recent project encountered a scene,需要使用到WebSocketto push data to the client,即浏览器,The structure of the project is roughly as follows:

image.png

项目框架:SpringBoot2.x,SpringCloud Zuul 1.x,for this issue,Nginx不影响,所以可以忽略,NacosFramework for service discovery.

We want to be under this architecture,使用WebSocketBring real-time data from AppPush to the front-end browser,But after checking the data, I found that,Zuul 1.x根本不支持WebSocket协议,The specific reasons have not been studied,But probablyGitHub的Issue有提到:Zuul - Websocket proxy #163,Zuul 2.x支持WebSocket,but not integratedSpring Cloud.

解决方案

方案1: Sock.js + STOMP

This article mainly introduces the useSock.js库、STOMP协议、Spring Messagingframework to achieveWebSockettwo-way communication effect.Sock.js是一个js库,它提供了一个类似于网络的对象,在浏览器和web服务器之间创建了一个低延迟、全双工、跨域通信通道.Spring 4开始就支持SockJS了.STOMPIs a text-formatted communication protocol for message passing,因为如果直接使用WebSocket API开发,In fact, it is similar to the style of programming with sockets,所以使用STOMPProvides a more simplified interface,Spring MessagingThe framework supports it very well.

Use this program for me,并不算很好,The problem is that this solution is not supported on the browser or server sideWebSocket的情况下,It will degenerate into a polling method,Of course this degradation is completely transparent to the user,So there are still benefits,This part of the code is omitted.

Now it's all polling,That would be of little use,就不考虑了.

方案2: Zuul框架切换Spring Gateway

Spring Gateway是支持WebSocket,But for my project,不太现实,The main reason is also the switching cost,To switch, all project groups must cooperate,This is neither a technical solution pushed by the company,There is no architectural review,Not likely anyway,所以不考虑.

方案3: mthizo247 / spring-cloud-netflix-zuul-websocket

This project is using a called Ronald Mthombeniwritten by a foreigner,18Open source in the year,它就是一个jar包,mavenThe central repository can be downloaded,Note that the download is1.0.0-RELEASE版本,使用的是SpringBoot 1.x,Because my project is using SpringBoot 2.x,So the downloaded package is basically unavailable.

spring-cloud-netflix-zuul-websocket的masterThe branch has been updated to1.0.7.RELEASE,I am using this version,But there are still version issues,Or because my project is using SpringBoot 2.x,在使用的时候WebSocketA connection has never been established,也不报错,By reading itspring-cloud-netflix-zuul-websocket的源码,大致了解一下,Plus debug process breakpoints,结果发现在com.github.mthizo247.cloud.netflix.zuul.web.proxytarget.AbstractProxyTargetResolver这个类报错了

protected URI resolveUri(ServiceInstance serviceInstance) {
        Map<String, Object> metadata = new HashMap<>();
        for (Map.Entry<String, String> entry : serviceInstance.getMetadata().entrySet()) {
            metadata.put(entry.getKey(), entry.getValue());
        }

        UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUri(serviceInstance.getUri());
        RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(new MapPropertyResolver(metadata));
        String configPath = propertyResolver.getProperty("configPath");
        if (configPath != null) {
            uriBuilder.path(configPath);
        }

        return uriBuilder.build().toUri();
    }
复制代码

Specifically in this method,RelaxedPropertyResolver类在SpringBoot 2.x已经被移除了,So this class has to be removed,RelaxedPropertyResolver就是对Map的封装,So it can be changed to the following:

protected URI resolveUri(ServiceInstance serviceInstance) {
        Map<String, Object> metadata = new HashMap<>();
        for (Map.Entry<String, String> entry : serviceInstance.getMetadata().entrySet()) {
            metadata.put(entry.getKey(), entry.getValue());
        }

        UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUri(serviceInstance.getUri());
        String configPath = metadata.get("configPath") == null ? null : String.valueOf(metadata.get("configPath"));
        if (configPath != null) {
            uriBuilder.path(configPath);
        }

        return uriBuilder.build().toUri();
    }
复制代码

然后install到本地,re-introduced into the project,就可以使Zuul 1.x支持WebSocket了

Ronald Mthombeni还提供了个demo,mthizo247 / zuul-websocket-support-demo,这个demofront-end dependentsockjs、stompLibraries are referenced fromwebjar这个库的,Anyway, it's just for your own project,After some tossing, it is basically no problem,Nothing more than even a version problem.

The front-end code and the back-end code follow,就能跑起来了.Still have to sigh hereRonald Mthombeni的代码能力.

总结

这样总结一下,感觉也没啥,But he also tossed for a few days.In fact, I didn't believe it at firstZuul 1.x不支持WebSocket,Mainly trying to find the reason why it is not supported,So tossed a bit,在Zuul网关,我写了个Zuul Filter

public Object run(){
        RequestContext context = RequestContext.getCurrentContext();
        HttpServletRequest request = context.getRequest();
        String upgradeHeader = request.getHeader("Upgrade");
        if (null == upgradeHeader) {
            upgradeHeader = request.getHeader("upgrade");
        }
        if (null != upgradeHeader && "websocket".equalsIgnoreCase(upgradeHeader)) {
            context.addZuulRequestHeader("connection", "Upgrade");
        }
        return null;
}
复制代码

在Zuul转发到App的时候,Found a strange problem,这里的context.addZuulRequestHeader("connection", "Upgrade")already put the request headerconnection改为Upgrade,但是到AppReceived request headersconnection确实Keep-Alive,所以Append has been reportedHandshake失败,大概是这个原因吧,有时间再研究一下Zuul的源码.

copyright notice
author[Qianshandu],Please bring the original link to reprint, thank you.
https://en.cdmana.com/2022/218/202208061702144592.html

Random recommended