Spring Cloud Gateway网关是所有微服务的统一入口。
客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。
spring: cloud: gateway: routes: - id: websocket1 uri: lb:ws://serviceName #使用方式2:websocket配置,通过nacos注册中心调用serviceName predicates: - Path=/websocket
当websocket服务为基于netty的socketio,netty需要单独开端口访问,上面方式要直接指定websocket服务的端口,多个websocket服务时,可以配置多个相同的路由规则,每个指定一个socketio服务,然后通权重实现负载均衡:
spring: cloud: gateway: routes: - id: websocket1 uri: ws://127.0.0.1:8081 predicates: - Path=/socket - Weight=group1,50 - id: websocket2 uri: ws://127.0.0.1:8082 predicates: - Path=/socket - Weight=group1,50
2023-10-24 10:05:23.433 ERROR 12636 --- [ctor-http-nio-6] o.s.w.s.adapter.HttpWebHandlerAdapter : [6726d297-6] Error [java.lang.UnsupportedOperationException] for HTTP GET "/socket/?EIO=3&transport=websocket", but ServerHttpResponse already committed (200 OK) 2023-10-24 10:05:23.433 ERROR 12636 --- [ctor-http-nio-6] r.n.http.server.HttpServerOperations : [6726d297-1, L:/192.168.20.5:9099 - R:/192.168.20.5:9099] Error finishing response. Closing connection java.lang.UnsupportedOperationException: null at org.springframework.http.ReadOnlyHttpHeaders.put(ReadOnlyHttpHeaders.java:126) ~[spring-web-5.3.20.jar:5.3.20] Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: Error has been observed at the following site(s): *__checkpoint ⇢ org.springframework.web.cors.reactive.CorsWebFilter [DefaultWebFilterChain] *__checkpoint ⇢ org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter [DefaultWebFilterChain] *__checkpoint ⇢ HTTP GET "/socket/?EIO=3&transport=websocket" [ExceptionHandlingWebHandler] Original Stack Trace: at org.springframework.http.ReadOnlyHttpHeaders.put(ReadOnlyHttpHeaders.java:126) ~[spring-web-5.3.20.jar:5.3.20]
通过分析发现是Gateway处理跨域时使用的是如下方式:
// 跨域配置源 UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); //设置跨域的配置信息 CorsConfiguration corsConfiguration = new CorsConfiguration(); // 允许所有请求来源进行跨域 //corsConfiguration.addAllowedOrigin("*"); ...
需改成reactor响应式方式,如下:
return (ServerWebExchange ctx, WebFilterChain chain) -> { ServerHttpRequest request = ctx.getRequest(); // 使用SpringMvc自带的跨域检测工具类判断当前请求是否跨域 if (!CorsUtils.isCorsRequest(request)) { return chain.filter(ctx); } HttpHeaders requestHeaders = request.getHeaders(); // 获取请求头 ServerHttpResponse response = ctx.getResponse(); // 获取响应对象 HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod(); // 获取请求方式对象 HttpHeaders headers = response.getHeaders(); // 获取响应头 headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin()); // 把请求头中的请求源(协议+ip+端口)添加到响应头中(相当于yml中的allowedOrigins) headers.addAll(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders.getAccessControlRequestHeaders()); if (requestMethod != null) { headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name()); // 允许被响应的方法(GET/POST等,相当于yml中的allowedMethods) } headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true"); // 允许在请求中携带cookie(相当于yml中的allowCredentials) headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*"); // 允许在请求中携带的头信息(相当于yml中的allowedHeaders) headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "18000L"); // 本次跨域检测的有效期(单位毫秒,相当于yml中的maxAge) if (request.getMethod() == HttpMethod.OPTIONS) { // 直接给option请求反回结果 response.setStatusCode(HttpStatus.OK); return Mono.empty(); } return chain.filter(ctx); // 不是option请求则放行 };
5、Spring Cloud Gateway处理跨域,可以通过yml配置方式实现,如:
gateway: # 全局的跨域配置 globalcors: # 解决options请求被拦截问题 add-to-simple-url-handler-mapping: true cors-configurations: # 拦截的请求 '[/**]': # 允许跨域的请求 #allowedOrigins: "*" # spring boot2.4以前的配置 allowedOriginPatterns: "*" # spring boot2.4以后的配置 # 允许请求中携带的头信息 allowedHeaders: "*" # 运行跨域的请求方式 allowedMethods: "*" # 是否允许携带cookie allowCredentials: true # 跨域检测的有效期,单位s maxAge: 3600
也可以通过编码的方式定义跨域配置类,如:
@Configuration public class CorsConfig { @Bean public WebFilter corsFilter() { return (ServerWebExchange ctx, WebFilterChain chain) -> { ServerHttpRequest request = ctx.getRequest(); // 使用SpringMvc自带的跨域检测工具类判断当前请求是否跨域 if (!CorsUtils.isCorsRequest(request)) { return chain.filter(ctx); } HttpHeaders requestHeaders = request.getHeaders(); // 获取请求头 ServerHttpResponse response = ctx.getResponse(); // 获取响应对象 HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod(); // 获取请求方式对象 HttpHeaders headers = response.getHeaders(); // 获取响应头 headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin()); // 把请求头中的请求源(协议+ip+端口)添加到响应头中(相当于yml中的allowedOrigins) headers.addAll(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders.getAccessControlRequestHeaders()); if (requestMethod != null) { headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name()); // 允许被响应的方法(GET/POST等,相当于yml中的allowedMethods) } headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true"); // 允许在请求中携带cookie(相当于yml中的allowCredentials) headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*"); // 允许在请求中携带的头信息(相当于yml中的allowedHeaders) headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "18000L"); // 本次跨域检测的有效期(单位毫秒,相当于yml中的maxAge) if (request.getMethod() == HttpMethod.OPTIONS) { // 直接给option请求反回结果 response.setStatusCode(HttpStatus.OK); return Mono.empty(); } return chain.filter(ctx); // 不是option请求则放行 }; } }