如何在Kubernetes中配置NGINX使React应用的WebSocket正常工作
问题描述
本地开发环境中,使用@stomp/stompjs库的WebSocket连接运行正常,但将React应用部署到Azure AKS平台(通过NGINX容器代理静态资源,搭配NGINX Ingress Controller)后,应用本身可正常访问,但WebSocket连接失败。Firefox浏览器返回错误:
Uncaught (in promise) DOMException: An invalid or illegal string was specified ... i stomp-handler.ts:31 (Async: promise callback) s stomp-handler.ts:31 da stomp-handler.ts:31 da stomp-handler.ts:31 value client.ts:404 value client.ts:401
当前关键配置现状:
- React客户端:使用环境变量
REACT_APP_BASE_URL_SOCKET=/ws作为Stomp客户端的brokerURL - NGINX容器:将
/ws路径代理到Kubernetes内部服务bar-api-service.default.svc.cluster.local:8000 - Kubernetes Ingress:将所有请求转发到
bar-ui-service,并配置了WebSocket相关注解 - Spring Boot后端:WebSocket端点
/ws允许来源为foo.com,Spring Security放行/ws路径
解决方案
1. 修正Stomp客户端的WebSocket URL协议
生产环境使用HTTPS,WebSocket需对应使用wss://协议。当前仅用相对路径/ws可能导致协议不匹配,可通过两种方式修正:
- 修改环境变量为完整URL:
REACT_APP_BASE_URL_SOCKET=wss://foo.com/ws - 或在代码中根据当前页面协议动态生成:
const socketProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const server = { name: `${socketProtocol}//${window.location.host}/ws` }; const client = new Client(); client.configure({ brokerURL: server.name, // 其他配置... });
2. 修复NGINX配置中的请求头语法错误
当前NGINX配置里的Connection头使用了HTML实体",这会导致NGINX发送错误的请求头,需改为正常双引号:
location /ws { proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; # 修正此处的引号 proxy_pass http://bar-api-service.default.svc.cluster.local:8000; }
3. 优化Kubernetes Ingress配置,直接转发WebSocket请求
跳过React应用的NGINX代理,让Ingress直接将/ws请求转发到bar-api-service,减少转发层级:
修改Ingress的spec.rules部分,新增/ws路径的转发规则:
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: foo-ingress annotations: kubernetes.io/ingress.class: nginx cert-manager.io/cluster-issuer: letsencrypt nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" nginx.ingress.kubernetes.io/ssl-redirect: "true" # 添加WebSocket处理注解 nginx.ingress.kubernetes.io/proxy-http-version: "1.1" nginx.ingress.kubernetes.io/configuration-snippet: | proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; spec: tls: - hosts: - foo.com secretName: tls-secret rules: - host: foo.com http: paths: - pathType: Prefix backend: service: name: bar-ui-service port: number: 80 path: / # 新增WebSocket路径转发 - pathType: Prefix backend: service: name: bar-api-service port: number: 8000 path: /ws
同时移除原有的websocket-services相关注解,因为现在直接转发到后端服务。
4. 修正Spring Boot WebSocket的允许来源配置
当前setAllowedOrigins("foo.com")未包含HTTPS协议前缀,需指定完整的Origin:
@Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws") .setAllowedOrigins("https://foo.com"); // 添加HTTPS前缀 }
若需支持更灵活的域名匹配,可使用setAllowedOriginPatterns:
registry.addEndpoint("/ws") .setAllowedOriginPatterns("https://*.foo.com");
5. 确保Spring Security的CORS配置支持WebSocket预检请求
显式配置CORS规则,允许WebSocket相关的请求方法和头:
@Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(Arrays.asList("https://foo.com")); configuration.setAllowedMethods(Arrays.asList("GET", "POST", "OPTIONS", "PUT", "DELETE")); configuration.setAllowedHeaders(Arrays.asList("*")); configuration.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); return source; }
同时确保Spring Security的configure方法中,/ws路径允许OPTIONS请求(浏览器预检请求):
@Override protected void configure(HttpSecurity http) throws Exception { http.httpBasic().disable() .formLogin().disable() .and() .cors() .and() .authorizeRequests() .antMatchers("/ws", "/ws/**").permitAll() // 包含WebSocket的子路径 .antMatchers(HttpMethod.OPTIONS, "/ws").permitAll(); // 允许预检请求 // 其他配置... }
内容的提问来源于stack exchange,提问作者nkmuturi
相关产品推荐
相关产品推荐

