原文链接
前言
在前篇使用Spring构建实时聊天通知的页面应用中,我们构建了一个可通信的简单websocket
应用。但是这里还有一个问题,我们常规的微服务一般是不会直接暴露在公网的,那么在网关转发下我们如何建立连接?
网关
首先,前文中我们提到对于跨域问题,一般的处理方法是
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
//...
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
//建立连接地址
registry.addEndpoint("/chat-websocket")
.setAllowedOrigins("http://localhost:8080")
.withSockJS();
}
}
但是,如果我们尝试使用通配符进行处理的时候,例如.setAllowedOrigins("*")
就会报错No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
,此时我们需要使用.setAllowedOriginPatterns("*")
代替
此时我们再来考虑网关,如果我们的网关此前也是使用的通配符配置,比如:
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "*"
allowedHeaders: "*"
allowedMethods: "*"
allowCredentials: true
那么同样,我们需要将allowedOrigins
改为allowedOriginPatterns
,使用专门的通配参数进行处理,此时就会报一个新的错误:The 'Access-Control-Allow-Origin' header contains multiple values 'http://localhost:8080, http://localhost:8080', but only one is allowed. Have the server send the header with a valid value, or, if an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
,意味着我们在网关和服务端都设置了Access-Control-Allow-Origin
导致重复报错,那我们的选择是去掉一个么?重新配置网关的逻辑肯定是不合适的,作为网关不应该跟着业务逻辑跑,那么改服务端的么,那就只透了网关,等于没配。所以这里正常的处理应该是网关处对于Access-Control-Allow-Origin
进行去重即可,这里可以选择配置文件的方式,也可以选择编程的方式进行配置:
spring:
cloud:
gateway:
defaultFilters:
- DedupeResponseHeader=Access-Control-Allow-Origin Access-Control-Allow-Credentials, RETAIN_UNIQUE
或者:
//RouteLocatorBuilder builder -> builder.routes -> RouteLocatorBuilder.Builder::route -> Function<PredicateSpec, Buildable<Route>> fn
filter.dedupeResponseHeader("Access-Control-Allow-Origin", "RETAIN_UNIQUE")
.dedupeResponseHeader("Access-Control-Allow-Credentials", "RETAIN_UNIQUE");
其实以上对于服务本身来说就已经可以了,但是还有相当一部分的代码会提供给你类似这样的配置:
spring:
cloud:
gateway:
routes:
- id: websocket-sockjs-route
uri: http://websocket-service
predicates:
- Path=/websocket/info/**
- id: websocket_route
uri: lb:ws://websocket-service
predicates:
- Path=/websocket/**
这个配置的目的是将流程中websocket
操作和http
区分开,但是对于绝大多数的spring
版本没有必要进行这个单独的配置,正常转发就可以了,在很早期的spring
就已经将websocket
兼容了,不需要进行特殊配置,更何况这种类似的配置很多配的都不对,只是因为spring
都兼容了,很多情况下所以即使不对也不会产生什么影响
Nginx
nginx
对于websocket
的兼容是比较麻烦的,但是在1.3.13之后,就非常简单了,使用了协议切换Switching Protocols
将HTTP
切换为WebSocket
,具体可以参考WebSocket proxying
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name api.astercasc.com;
#some else config
#static resources
location /ushio/resources {
proxy_pass http://xxx:xxx;
}
#websocket
location /yui/chat-websocket/ {
proxy_pass http://xxx:xxx;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600;
}
#service
location / {
proxy_pass http://xxx:xxx;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
}
}
可以使用proxy_read_timeout
自定义超时时间,默认为60秒
目前代理版本proxy_http_version
不支持http2
,参考Allow proxy_http_version 2.0
There are no plans to implement HTTP/2 support in the proxy module in the foreseeable future
There is almost no sense to implement it, as the main HTTP/2 benefit is that it allows multiplexing many requests within a single connection, thus almost removing the limit on number of simalteneous requests - and there is no such limit when talking to your own backends. Moreover, things may even become worse when using HTTP/2 to backends, due to single TCP connection being used instead of multiple ones.