Cannot connect to rabbitmq STOMP from Spring boot - spring

I have used the RabbitMQ docker image which has STOMP enabled. With the following configuration, when I try to run my Spring Boot Application, I am getting an exception.
StackTrace:
2020-11-21 16:03:07.620 INFO 28504 --- [ient-loop-nio-1] o.s.m.s.s.StompBrokerRelayMessageHandler : TCP connection failure in session system: Failed to connect: Connection refused: /127.0.0.1:61613
io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: /127.0.0.1:61613
Caused by: java.net.ConnectException: Connection refused
at sun.nio.ch.SocketChannelImpl.checkConnect(Native Method) ~[na:1.8.0_242]
at sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:714) ~[na:1.8.0_242]
at io.netty.channel.socket.nio.NioSocketChannel.doFinishConnect(NioSocketChannel.java:330) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final]
at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.finishConnect(AbstractNioChannel.java:334) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:702) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:650) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:576) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final]
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989) ~[netty-common-4.1.51.Final.jar:4.1.51.Final]
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.51.Final.jar:4.1.51.Final]
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.51.Final.jar:4.1.51.Final]
at java.lang.Thread.run(Thread.java:748) ~[na:1.8.0_242]
Dockerfile
FROM rabbitmq:3-management
RUN rabbitmq-plugins enable --offline rabbitmq_stomp
EXPOSE 61613
The logs from Rabbitmq container looks fine to me.
WebSocketConfig.java looks like:
#EnableWebSocketMessageBroker
#Configuration
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws-connection")
.setAllowedOrigins("*")
.withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableStompBrokerRelay("/topic", "/queue")
.setRelayPort(61613)
.setRelayHost("127.0.0.1")
.setClientPasscode("guest")
.setClientLogin("guest");
registry.setApplicationDestinationPrefixes("/ws");
}
}
pom.xml
<dependency>
<groupId>io.projectreactor.netty</groupId>
<artifactId>reactor-netty</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
What's wrong with the configuration? Can anyone help me?

I think you made a mistake while exposing the rabbitmq stomp port 61613 for the client. By the way, I tested with a similar configuration it works for me.
For implementation please check my demo application on GitHub or read the following details.
Dockerfile
FROM rabbitmq:3-management
RUN rabbitmq-plugins enable --offline rabbitmq_stomp
EXPOSE 15671 15672 61613
Server Implementation
Message Contract
public class ZbytesMessage {
private String from;
private String text;
...getters and setters...
}
WebSocket Configuration
#Configuration
#EnableWebSocketMessageBroker
public class StompConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/zsockets")
.setAllowedOrigins("*").withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableStompBrokerRelay("/topic", "/queue")
.setRelayHost("localhost")
.setRelayPort(61613)
.setClientLogin("guest")
.setClientPasscode("guest");
config.setApplicationDestinationPrefixes("/zbytes");
}
}
Web Controller
#Controller
public class ZbytesController {
private static final Logger LOG = LoggerFactory.getLogger(ZbytesController.class);
#MessageMapping("/hello")
#SendTo("/topic/greetings")
public ZbytesMessage greeting(ZbytesMessage msg) throws Exception {
Thread.sleep(1000); // simulated delay
LOG.info("Received : {} from: {} ", msg.getText(), msg.getFrom());
return msg;
}
}
Server Runner
#SpringBootApplication
public class ServerRunner {
public static void main(String[] args) {
SpringApplication.run(ServerRunner.class, args);
}
}
Client Implementation
public class HelloClient {
private static final WebSocketHttpHeaders headers = new WebSocketHttpHeaders();
private static final Logger LOG = LoggerFactory.getLogger(HelloClient.class);
public static void main(String[] args) throws Exception {
HelloClient helloClient = new HelloClient();
ListenableFuture<StompSession> f = helloClient.connect();
StompSession stompSession = f.get();
LOG.info("Subscribing to greeting topic using session {}", stompSession);
helloClient.subscribeGreetings(stompSession);
LOG.info("Sending hello message {}", stompSession);
helloClient.sendHello(stompSession);
Thread.sleep(60000);
}
public ListenableFuture<StompSession> connect() {
Transport webSocketTransport = new WebSocketTransport(new StandardWebSocketClient());
List<Transport> transports = Collections.singletonList(webSocketTransport);
SockJsClient sockJsClient = new SockJsClient(transports);
sockJsClient.setMessageCodec(new Jackson2SockJsMessageCodec());
WebSocketStompClient stompClient = new WebSocketStompClient(sockJsClient);
String url = "ws://{host}:{port}/zsockets";
return stompClient.connect(url, headers, new MyHandler(), "localhost", 8080);
}
public void subscribeGreetings(StompSession stompSession) {
stompSession.subscribe("/topic/greetings", new StompFrameHandler() {
public Type getPayloadType(StompHeaders stompHeaders) {
return byte[].class;
}
public void handleFrame(StompHeaders stompHeaders, Object o) {
LOG.info("Received greeting {}", new String((byte[]) o));
}
});
}
public void sendHello(StompSession stompSession) {
String jsonHello = "{ \"from\" : \"suraj\", \"text\" : \"Hi zbytes!\" }";
stompSession.send("/zbytes/hello", jsonHello.getBytes());
}
private static class MyHandler extends StompSessionHandlerAdapter {
#Override
public void afterConnected(StompSession stompSession, StompHeaders stompHeaders) {
LOG.info("Now connected");
}
}
}
To Run
Build the docker image and run it (don't forget to expose port 61613). (Note: I would prefer docker-compose.yaml)
docker build -t zbytes/rabbitmq .
docker run -p61613:61613 zbytes/rabbitmq
Run ServerRunner java main class.
Run HelloClient java main class.
Server Output
i.g.zbytes.demo.server.ZbytesController : Received : Hi zbytes! from: suraj
Client Output
Received greeting {"from":"suraj","text":"Hi zbytes!"}

Related

Springboot websocket give 404 error in postman

I am writing a chatroom service by springboot websocket.And i want to build multiple chatrooms for the clients base on the url.But it fail when testing postman with 404 not found
My controller :
#Controller
public class ChatroomController {
private final ChatroomService chatroomService;
private final SimpMessageSendingOperations messagingTemplate;
public ChatroomController(ChatroomService chatroomService, SimpMessageSendingOperations messagingTemplate) {
this.chatroomService = chatroomService;
this.messagingTemplate = messagingTemplate;
}
//send chat
#MessageMapping("/chat/{roomId}/sendMessage")
public ChatMessage sendMessage(#DestinationVariable String roomId, #Payload ChatMessage chatMessage) {
return chatroomService.sendMessage(roomId,chatMessage);
}
My service:
#Service
#Slf4j
public class ChatroomService {
private final ChatroomRepository chatroomRepository;
private final SimpMessageSendingOperations messagingTemplate;
public ChatroomService(ChatroomRepository chatroomRepository, SimpMessageSendingOperations messagingTemplate) {
this.chatroomRepository = chatroomRepository;
this.messagingTemplate = messagingTemplate;
}
public ChatMessage sendMessage(String roomId, ChatMessage chatMessage) {
//check chatroom is existed
chatMessage.setDateTime(Instant.now());
chatMessage.setOrder_id(roomId);
messagingTemplate.convertAndSend(format("/channel/%s", roomId), chatMessage);
ChatMessage savedchat=chatroomRepository.save(chatMessage);
return savedchat;
}
My config:
#Configuration
#EnableWebSocketMessageBroker
public class WebsocketConfig implements WebSocketMessageBrokerConfigurer {
#Override //register the endpoint
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws").withSockJS();
registry.addEndpoint("/ws");
//sockJs is for setting the STOMP =>send message to who(subscribe)
}
#Override //control with "/app" can access
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/Chatroom");
// '/topic' is access the broker
registry.enableSimpleBroker("/channel");
}
When i test with: ws://localhost:8084/ws/chat/12/sendMessage, it give the 404 error, but when i test with ws://localhost:8084/ws, it connected.Is there any problem on my url?
Error :
Invalid SockJS path '/chat/12' - required to have 3 path segments"
try configuring your application to run on a different port by adding this to your application.properties
server.port = 8081

How to close or reset a http2 stream in Spring boot with embedded Netty server application?

Our application runs on Springboot with embedded Netty server(enabled http2). And it exposes services via Rest Apis.
On Rest Api call, we have a scenario where we wanted to ignore the http2 request without sending any response.
Our Environment:
Reactor version(s) used: reactor-netty-http : 1.0.16
JVM version (java -version): 17.0.1
springBootVersion= 2.7.4
Do we have any support from Netty Server to close a http2 stream(request) without response?
Here is my code:
GreetingController.java
public class GreetingController {
private final GreetingService greetingService;
public GreetingController(GreetingService greetingService) {
this.greetingService = greetingService;
}
#GetMapping("/{name}")
private Mono<String> greet(#PathVariable String name) {
return greetingService.greet(name);
} }
DiscardOutBoundHandler.java
public class DiscardOutBoundHandler extends ChannelOutboundHandlerAdapter {
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { System.out.println("Outbound Handler name: " + ctx.channel().pipeline().names()); promise.channel().close(); }
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { System.out.print("exceptionCaught call"); cause.printStackTrace(); ctx.close(); } }
NettyWebServerFactoryPortCustomizer,java
public class NettyWebServerFactoryPortCustomizer implements WebServerFactoryCustomizer<NettyReactiveWebServerFactory> {
#Override
public void customize(NettyReactiveWebServerFactory serverFactory) {
Http2 h2 = new Http2();
serverFactory.setHttp2(h2);
serverFactory.addServerCustomizers(new PortCustomizer(8443));
serverFactory.addServerCustomizers(
httpServer -> httpServer.doOnChannelInit((connectionObserver, channel, remoteAddress) -> {
ChannelPipeline channelPipeline = channel.pipeline();
channelPipeline.addLast("encoder", new DiscardOutBoundHandler());
}));
}
private static class PortCustomizer implements NettyServerCustomizer {
private final int port;
private PortCustomizer(int port) {
this.port = port;
}
#Override
public HttpServer apply(HttpServer httpServer) {
return httpServer.port(port);
}
} }
What you are talking about is the problem of the HTTP protocol layer, which should be solved at the HTTP protocol layer, while ChannelOutboundHandler is located at the transport layer, and you cannot solve the problem here.
import reactor.core.publisher.Mono;
import reactor.netty.DisposableServer;
import reactor.netty.http.HttpProtocol;
import reactor.netty.http.server.HttpServer;
public class HttpServerTest {
public static void main(String[] args) {
DisposableServer server =
HttpServer.create()
.port(8080)
.protocol(HttpProtocol.H2C)
.route(routes ->
routes.get("/hello",
(request, response) -> response.sendString(Mono.just("Hello World!")))
.get("/ignore",
(request, response) -> response.then())
).bindNow();
server.onDispose()
.block();
}
}

Flutter Client Subscription to Spring Boot Websocket Server

Below is my Spring Boot Code for scheduling messages to its connected clients.
But my FLUTTER application is not able to receive the the pushed messages from the websocket server.
#Service
public class GreetingService {
private final SimpMessagingTemplate simpMessagingTemplate;
private static final String WS_MESSAGE_TRANSFER_DESTINATION = "/topic/greetings";
private List<String> userNames = new ArrayList<>();
GreetingService(SimpMessagingTemplate simpMessagingTemplate) {
this.simpMessagingTemplate = simpMessagingTemplate;
}
public void sendMessages() {
for (String userName : userNames) {
simpMessagingTemplate.convertAndSendToUser(userName, WS_MESSAGE_TRANSFER_DESTINATION,
"Hallo " + userName + " at " + new Date().toString());
}
}
public void addUserName(String username) {
userNames.add(username);
}
}
Flutter Code :-
var channel = IOWebSocketChannel.connect("ws://1f470ad1bdc8.ngrok.io/ws");
channel.stream.listen((message) {
channel.sink.add("received!");
});
You have to create a Spring Configuration class for initializing the subscription paths.
#Configuration
#EnableWebSocketMessageBroker
public class WSocketBrokerConfiguration implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/socket")
.setAllowedOrigins("*");
}
}

Spring Stomp slow connection

I'm experiencing a slow Stomp connection between two Spring microservices, the client takes about 5 minutes to send the CONNECT Stomp message. Both microservices run on the same node.
Spring Boot version: 2.1.3.RELEASE
The Websocket server is configured in the following way:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws-subscribe").setAllowedOrigins("*").withSockJS();
}
}
The client is configured this way:
#Component
public class WebsocketClient {
private GlobalConfiguration globalConfiguration;
private WebsocketListener websocketListener;
private WebSocketStompClient stompClient;
#Autowired
private JwtTokenGenerator jwtTokenGenerator;
private ApplicationContext appContext;
private Logger LOGGER = LoggerFactory.getLogger(WebsocketClient.class);
#Autowired
public WebsocketClient(ApplicationContext context,GlobalConfiguration globalConfiguration) {
Transport webSocketTransport = new WebSocketTransport(new StandardWebSocketClient());
List<Transport> transports = Collections.singletonList(webSocketTransport);
SockJsClient sockJsClient = new SockJsClient(transports);
sockJsClient.setMessageCodec(new Jackson2SockJsMessageCodec());
this.stompClient = new WebSocketStompClient(sockJsClient);
this.globalConfiguration = globalConfiguration;
this.websocketListener = new WebsocketListener(context);
}
public ListenableFuture<StompSession> connectToWebsocket(){
String url = globalConfiguration.getOrchestratorWs();
StompHeaders connectHeaders = new StompHeaders();
WebSocketHttpHeaders webSocketHttpHeaders = new WebSocketHttpHeaders();
webSocketHttpHeaders.add("Authorization",jwtTokenGenerator.token);
LOGGER.info("webSocketHttpHeaders: " + webSocketHttpHeaders.toString());
LOGGER.info("url: " + url);
this.stompClient.setMessageConverter(new MappingJackson2MessageConverter());
return stompClient.connect(url,webSocketHttpHeaders,connectHeaders, websocketListener);
}
}
And client-side we have this Websocket listener:
public class WebsocketListener extends StompSessionHandlerAdapter {
private static final Logger LOGGER = LoggerFactory.getLogger(WebsocketListener.class);
private GlobalConfiguration globalConfiguration;
private ApplicationContext appContext;
public WebsocketListener(ApplicationContext appContext) {
this.appContext = appContext;
this.globalConfiguration = appContext.getBean(GlobalConfiguration.class);
}
#Override
public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
LOGGER.info("New session established : " + globalConfiguration.getId());
session.subscribe("/topic/injections/"+globalConfiguration.getId(), this);
LOGGER.info("Subscribed to /topic/injections/"+globalConfiguration.getId());
// session.send("/app/chat", getSampleMessage());
// logger.info("Message sent to websocket server");
}
#Override
public void handleException(StompSession session, StompCommand command, StompHeaders headers, byte[] payload, Throwable exception) {
LOGGER.error("Got an exception", exception);
}
#Override
public Type getPayloadType(StompHeaders headers) {
return Map.class;
}
#Override
public void handleFrame(StompHeaders headers, Object payload) {
LOGGER.info("Injection Received : " + payload);
InjectionHandler injectionHandler = new InjectionHandler(appContext,(HashMap) payload);
injectionHandler.start();
}
}
The afterConnected is invoked about 5 minutes after the ws://10.2.0.43:7071/ws-subscribe/513/8306a7ac357847678795b049450fb6c5/websocket
Inspecting the source code of the Stomp library, I saw that the code where the microservice remains stuck for those 5 minutes is the following:
public ListenableFuture<StompSession> connect(URI url, #Nullable WebSocketHttpHeaders handshakeHeaders, #Nullable StompHeaders connectHeaders, StompSessionHandler sessionHandler) {
Assert.notNull(url, "'url' must not be null");
ConnectionHandlingStompSession session = this.createSession(connectHeaders, sessionHandler);
WebSocketStompClient.WebSocketTcpConnectionHandlerAdapter adapter = new WebSocketStompClient.WebSocketTcpConnectionHandlerAdapter(session);
this.getWebSocketClient().doHandshake(adapter, handshakeHeaders, url).addCallback(adapter);
return session.getSessionFuture();
}
In particular, the session.getSessionFuture() call.
Any idea on what may cause the delay in the CONNECT message sent by the client?

Spring boot integration testing Jersey JAX-RS resource

I am using Spring Boot 2.0.3.RELEASE and creating a simple Jersey JAX-RS resource (using the jersey spring boot starter). I would like to do a simple integration test using a Jersey Client class I have written, it doesn't seem however that the resource has started as I get a java.net.ConnectException: Connection refused
So here we go, first the simple resource
#Component
#Path(Constants.STATUS_PATH)
public class StatusResource {
private static final String OK = "OK";
#GET
#Produces(MediaType.TEXT_PLAIN)
public Response getStatus() {
return Response.ok(OK).build();
}
}
The Jersey config
#Component
public class JerseyConfig extends ResourceConfig {
private static final Logger logger = LoggerFactory.getLogger(JerseyConfig.class);
public JerseyConfig() {
logger.debug("Registering JAX-RS resources");
register(StatusResource.class);
}
}
The Spring boot application class
#SpringBootApplication
public class Application {
private static final Logger logger = LoggerFactory.getLogger(Application.class);
public static void main(String[] args) {
logger.info("==== Starting Services ====");
SpringApplication.run(Application.class, args);
}
}
The Jersey Client
public class ServicesClient {
private static final Logger logger = LoggerFactory.getLogger(ServicesClient.class);
private Client client;
public ServicesClient() {
client = initJerseyClient();
}
private Client initJerseyClient() {
logger.debug("Initialising Jersey Client");
ClientConfig jerseyClientConfig = new ClientConfig();
return ClientBuilder.newClient(jerseyClientConfig);
}
/**
* Returns OK if services are running
*
* #return service status
*/
public String getServicesStatus() {
logger.debug("Client get services status request");
Response respone = client.target("http://localhost:8080").path(Constants.STATUS_PATH).request().get();
return respone.readEntity(String.class);
}
}
And finally the Spring Boot integration test
#ExtendWith(SpringExtension.class)
#SpringBootTest(classes = Application.class)
public class StatusResourceTest {
private static Logger logger = LoggerFactory.getLogger(StatusResourceTest.class);
private static ServicesClient servicesClient;
#BeforeAll
public static void setup() throws IOException {
logger.debug("--- Starting StatusResourceTest setup ---");
System.setProperty("services.client.file", "classpath:com/zurich/utils/services/config/services.yaml");
servicesClient = new ServicesClientBuilder().build();
}
#Test
public void testGetStatus() {
logger.debug("--- Starting testGetStatus ---");
String status = servicesClient.getServicesStatus();
assertThat("Service status should be OK", status, equalTo("OK"));
}
}
Like I said when the test runs and get is called inside the client, I get the following stack trace. Which to me implies the resource isn't running at http://localhost:8080 but I don't know why.
javax.ws.rs.ProcessingException: java.net.ConnectException: Connection refused: connect
at org.glassfish.jersey.client.internal.HttpUrlConnector.apply(HttpUrlConnector.java:284)
at org.glassfish.jersey.client.ClientRuntime.invoke(ClientRuntime.java:278)
at org.glassfish.jersey.client.JerseyInvocation.lambda$invoke$0(JerseyInvocation.java:753)
at org.glassfish.jersey.internal.Errors.process(Errors.java:316)
at org.glassfish.jersey.internal.Errors.process(Errors.java:298)
at org.glassfish.jersey.internal.Errors.process(Errors.java:229)
at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:414)
at org.glassfish.jersey.client.JerseyInvocation.invoke(JerseyInvocation.java:752)
at org.glassfish.jersey.client.JerseyInvocation$Builder.method(JerseyInvocation.java:419)
at org.glassfish.jersey.client.JerseyInvocation$Builder.get(JerseyInvocation.java:319)
at com.xxx.utils.services.client.ServicesClient.getServicesStatus(ServicesClient.java:178)
at sit.resource.status.StatusResourceTest.testGetStatus(StatusResourceTest.java:39)
Caused by: java.net.ConnectException: Connection refused: connect
at java.net.DualStackPlainSocketImpl.connect0(Native Method)
at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:79)
at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:345)
at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172)
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
at java.net.Socket.connect(Socket.java:589)
at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:480)
at org.glassfish.jersey.client.internal.HttpUrlConnector._apply(HttpUrlConnector.java:390)
at org.glassfish.jersey.client.internal.HttpUrlConnector.apply(HttpUrlConnector.java:282)
... 11 more
It appears the issue was with the #SpringBootTest annotation, I changed it to the following and it is now working
#SpringBootTest(classes = Application.class, webEnvironment = WebEnvironment.DEFINED_PORT)

Resources