I'm doing a migration from a monolith to microservices on a springboot web application, there is a service about websocket but I can't connect when I'm doing the migration. This code still work in monolith. Here is the log from the client:
Access to XMLHttpRequest at 'http://127.0.0.1:8080/api/ws/info?t=1711855420621' from origin 'http://localhost:3000' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains multiple values 'http://localhost:3000, http://localhost:3000', but only one is allowed.
Here is the log from the server (api-gateway):
Sorted gatewayFilterFactories: [[GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.RemoveCachedBodyFilter@13067317}, order = -2147483648], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.AdaptCachedBodyGlobalFilter@344683bb}, order = -2147482648], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.NettyWriteResponseFilter@2de8fe55}, order = -1], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.ForwardPathFilter@38c2baec}, order = 0], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.GatewayMetricsFilter@111a419a}, order = 0], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter@dc085f8}, order = 10000], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter@15d560a7}, order = 10150], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.LoadBalancerServiceInstanceCookieFilter@53f9b423}, order = 10151], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.WebsocketRoutingFilter@187cf828}, order = 2147483646], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.NettyRoutingFilter@5e8035ac}, order = 2147483647], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.ForwardRoutingFilter@52b2ecb9}, order = 2147483647]]
2024-03-31T10:26:34.318+07:00 TRACE 15124 --- [api-gateway] [ parallel-8] [ ] o.s.c.g.filter.RouteToRequestUrlFilter : RouteToRequestUrlFilter start
2024-03-31T10:26:34.319+07:00 TRACE 15124 --- [api-gateway] [ parallel-8] [ ] s.c.g.f.ReactiveLoadBalancerClientFilter : ReactiveLoadBalancerClientFilter url before: lb://realtime-service/api/ws/info?t=1711855594299
2024-03-31T10:26:34.320+07:00 TRACE 15124 --- [api-gateway] [ parallel-8] [ ] s.c.g.f.ReactiveLoadBalancerClientFilter : LoadBalancerClientFilter url chosen: http://host.docker.internal:8086/api/ws/info?t=1711855594299
2024-03-31T10:26:34.322+07:00 DEBUG 15124 --- [api-gateway] [ parallel-8] [ ] g.f.h.o.ObservedRequestHttpHeadersFilter : Will instrument the HTTP request headers [Host:"127.0.0.1:8080", sec-ch-ua:""Google Chrome";v="123", "Not:A-Brand";v="8", "Chromium";v="123"", sec-ch-ua-mobile:"?0", User-Agent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36", sec-ch-ua-platform:""Windows"", Accept:"*/*", Origin:"http://localhost:3000", Sec-Fetch-Site:"cross-site", Sec-Fetch-Mode:"cors", Sec-Fetch-Dest:"empty", Referer:"http://localhost:3000/", Accept-Encoding:"gzip, deflate, br, zstd", Accept-Language:"en-US,en;q=0.9,vi;q=0.8,la;q=0.7", Forwarded:"proto=http;host="127.0.0.1:8080";for="127.0.0.1:64886"", X-Forwarded-For:"127.0.0.1", X-Forwarded-Proto:"http", X-Forwarded-Port:"8080", X-Forwarded-Host:"127.0.0.1:8080"]
2024-03-31T10:26:34.325+07:00 DEBUG 15124 --- [api-gateway] [ parallel-8] [ ] g.f.h.o.ObservedRequestHttpHeadersFilter : Client observation {name=http.client.requests(null), error=null, context=name='http.client.requests', contextualName='null', error='null', lowCardinalityKeyValues=[http.method='GET', http.status_code='UNKNOWN', spring.cloud.gateway.route.id='eee45cc4-bd46-412d-98a5-f555c3b4eac0', spring.cloud.gateway.route.uri='lb://realtime-service'], highCardinalityKeyValues=[http.uri='http://127.0.0.1:8080/api/ws/info?t=1711855594299'], map=[class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@290be52c', class io.micrometer.tracing.handler.TracingObservationHandler$TracingContext='TracingContext{span=6608d7eae803b66b027ee92588e83ffe/97ba6f0d15af2bd8}', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=1.344E-4, duration(nanos)=134400.0, startTimeNanos=82941717160100}'], parentObservation=org.springframework.security.web.server.ObservationWebFilterChainDecorator$PhasedObservation@4072a488} created for the request. New headers are [Host:"127.0.0.1:8080", sec-ch-ua:""Google Chrome";v="123", "Not:A-Brand";v="8", "Chromium";v="123"", sec-ch-ua-mobile:"?0", User-Agent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36", sec-ch-ua-platform:""Windows"", Accept:"*/*", Origin:"http://localhost:3000", Sec-Fetch-Site:"cross-site", Sec-Fetch-Mode:"cors", Sec-Fetch-Dest:"empty", Referer:"http://localhost:3000/", Accept-Encoding:"gzip, deflate, br, zstd", Accept-Language:"en-US,en;q=0.9,vi;q=0.8,la;q=0.7", Forwarded:"proto=http;host="127.0.0.1:8080";for="127.0.0.1:64886"", X-Forwarded-For:"127.0.0.1", X-Forwarded-Proto:"http", X-Forwarded-Port:"8080", X-Forwarded-Host:"127.0.0.1:8080", traceparent:"00-6608d7eae803b66b027ee92588e83ffe-97ba6f0d15af2bd8-00"]
2024-03-31T10:26:34.329+07:00 TRACE 15124 --- [api-gateway] [ctor-http-nio-6] [ ] o.s.c.gateway.filter.NettyRoutingFilter : outbound route: e6029e59, inbound: [ee6ad2f0-11]
2024-03-31T10:26:34.334+07:00 DEBUG 15124 --- [api-gateway] [ctor-http-nio-6] [ ] .f.h.o.ObservedResponseHttpHeadersFilter : Will instrument the response
2024-03-31T10:26:34.335+07:00 DEBUG 15124 --- [api-gateway] [ctor-http-nio-6] [ ] .f.h.o.ObservedResponseHttpHeadersFilter : The response was handled for observation {name=http.client.requests(null), error=null, context=name='http.client.requests', contextualName='null', error='null', lowCardinalityKeyValues=[http.method='GET', http.status_code='UNKNOWN', spring.cloud.gateway.route.id='eee45cc4-bd46-412d-98a5-f555c3b4eac0', spring.cloud.gateway.route.uri='lb://realtime-service'], highCardinalityKeyValues=[http.uri='http://127.0.0.1:8080/api/ws/info?t=1711855594299'], map=[class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@290be52c', class io.micrometer.tracing.handler.TracingObservationHandler$TracingContext='TracingContext{span=6608d7eae803b66b027ee92588e83ffe/97ba6f0d15af2bd8}', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=0.0095177, duration(nanos)=9517700.0, startTimeNanos=82941717160100}'], parentObservation=org.springframework.security.web.server.ObservationWebFilterChainDecorator$PhasedObservation@4072a488}
2024-03-31T10:26:34.337+07:00 TRACE 15124 --- [api-gateway] [ctor-http-nio-6] [ ] o.s.c.g.filter.NettyWriteResponseFilter : NettyWriteResponseFilter start inbound: e6029e59, outbound: [ee6ad2f0-11]
2024-03-31T10:26:34.339+07:00 TRACE 15124 --- [api-gateway] [ctor-http-nio-6] [ ] o.s.c.g.filter.GatewayMetricsFilter : spring.cloud.gateway.requests tags: [tag(httpMethod=GET),tag(httpStatusCode=200),tag(outcome=SUCCESSFUL),tag(routeId=eee45cc4-bd46-412d-98a5-f555c3b4eac0),tag(routeUri=lb://realtime-service),tag(status=OK)]
I got the connect issue but I have fixed it by adding customRouteLocator and some configuration in application.properties but then, I got the issue about the duplicate headers. This is the configuration of api-gateway
@Bean
public CorsWebFilter corsWebFilter() {
String clientHostName = environment.getProperty("CLIENT_HOSTNAME");
CorsConfiguration corsConfig = new CorsConfiguration();
corsConfig.setAllowedOrigins(Arrays.asList(clientHostName));
corsConfig.setMaxAge(3000L);
corsConfig.setAllowedMethods(List.of("PUT", "GET", "POST", "DELETE", "OPTION"));
corsConfig.setAllowedHeaders(List.of("*"));
corsConfig.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfig);
return new CorsWebFilter(source);
}
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route(p -> p
.path("/api/ws/**")
.uri("lb://realtime-service"))
.build();
}
This is a part of application.properties of api-gateway
## Realtime Service Route
spring.cloud.gateway.routes[5].id=realtime-service
spring.cloud.gateway.routes[5].uri=lb://realtime-service
spring.cloud.gateway.routes[5].predicates[0]=Path=/api/realtime
This is how I handle the socket service
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/comment", "/reaction", "/comment-total", "/home", "/profile", "/reply");
registry.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry
.addEndpoint("/api/ws")
.setAllowedOriginPatterns("*")
.withSockJS();
}
}
This is how I connect to socket server
const socket = new SockJS(socketUrl)
const client = Stomp.over(socket)
client.debug = null
client.connect({Authorization: `Bearer ${ load("access-token") }`}, () => {
//do something
})
I tried to comment out the cors config (corsWebFilter) and the socket request passes and it works well but I can't call the rest api because it's blocked by cors.
Appreciate for your help!