I am supposed to interact with a legacy system where I have to setup a TCP client using spring-integration in java/kotlin to send a message to a TCP system and receive its response, parse this response and send it to other client via REST. I went through many documentations and blogs which tells how to do via xml. Not able to find corresponding annotations for everything. Any code snippet will be very helpful.
#Service
class MyService{
#Autowired
MyGateway gateway;
public String callTCPClient(String msg){
return gateway.exchange(msg);
}
}
interface MyGateway{
String exchange (String msg)
}
As is shown in that answer, you can do whatever you want after the response is received...
#Bean
public IntegrationFlow client() {
return IntegrationFlows.from(MyGateway.class)
.handle(Tcp.outboundGateway(
Tcp.netClient("localhost", 1234)
.serializer(codec()) // default is CRLF
.deserializer(codec()))) // default is CRLF
.transform(Transformers.objectToString()) // byte[] -> String
.get();
}
In this case, we simply transform the byte array to a String, but you can perform whatever operations you want on it, e.g. JSON to Object. You can add as many steps as you want - add .handle(...) elements to call arbitrary methods. Read the Spring Integration Reference Manual.
Related
I'm using Spring WebFlux for my project that is intended to work as pub/sub service: http clients connect to it and wait for events (like PUSH or SSE).
I need to manually write headers and body to the response without using ServerResponse.
I need to do it manually because I'm implementing an SSE server and I must send custom headers into the response before any event actually arrives.
I'm trying to do it this way:
#Bean
RouterFunction<ServerResponse> getStuff() {
return route(GET("/stuff"),
request -> {
final ServerWebExchange exchange = request.exchange();
final byte[] bytes = "data".getBytes(StandardCharsets.UTF_8);
final DataBuffer buffer =exchange.getResponse().bufferFactory().wrap(bytes);
return exchange.getResponse().writeWith(Flux.just(buffer));
}
);
But it does not work because writeWith() returns Mono<Void> and getStuff() must return RouterFunction. Can anybody help me find a way around this?
I have a web socket connection established between a browser client and a Spring Boot backend using STOMP and sock js. Every second, a payload is sent to the server from the client containing data that needs to be persisted to a Postgres database. There may be thousands of clients connected simultaneously so I don't want to update the database every second for each one. So, to reduce the CPU load, I want to listen for when a web socket StompCommand.DISCONNECT event occurs and then persist the last received message from the client.
Is this possible, or is there another way to get around thi sproblem?
In this case - the question is really opinionated - there are many possible implementations.
One of the implementations can do the following:
When you receive the message from the connected client - maintain a map (in memory will be enough, for the sake of idea) of the the identifier of the current client to the Last Data.
Every time you get a new message in #MessageMapping annotated class - update an entry in the map, so that it will always contain the last message.
The value of the map will be the last message, the key can be Principal, SessionId string - whatever you'll find useful.
#Component
public class LastMessageHolder {
private Map<Principal, MyData> lastDataPerPrincipal;
public void updateLastData(Principal principal, MyData data) {
lastDataPerPrincipal.put(principal, data);
}
public MyData getLastDataForPrincipalAndClear(Principal principal) {
return lastDataPerPrincipal.remove(principal);
}
}
The message Receiver will get the messages through the stomp channel and update the last message holder
#Component
public class MyMessageReceiver {
#Autowired
private LastMessageHolder lastMessageHolder;
#MessageMapping(...)
public void onDataReceived(Principal principal, MyData data) {
// this gets called every second per client
lastMessageHolder.updateLastData(principal, data);
}
}
And when you listen for the disconnect message in the channel interceptor - make remove the data from the principal that is being disconnected and store it in the database:
#Component
public class DbStoreChannelInterceptor implements ChannelInterceptor {
#Autowired
private LastMessageHolder lastMessageHolder;
#Autowired // something that will store your stuff in the db
private DbDao dbDao;
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor =
MessageHeaderAccessor.getAccessor(message,
StompHeaderAccessor.class);
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
// populate a principal here, from headers, authentication token,
whatever
Principal principal = ...
accessor.setUser(principal);
}
if (StompCommand.DISCONNECT.equals(accessor.getCommand())) {
Principal principal = accessor.getUser();
MyData data = lastMessageHolder.getDataForPrincipalAndClear(principal);
dbDao.storeDataInDbForPrincipal(principal, data);
}
}
}
This is a basic idea.
From that you can take it further, and instead of storing the data from the channel interceptor (in this case the actual INSERT will be done for each client) you might want to throw it into some in-memory or distributed queue - whatever suits you best, so that the consumer will read the batch of the data objects and store them all at once, so that it will lead to much less load on your RDBMS.
In addition, I'll just mention, that you should think about the situation where the client keeps sending the data, but the server gets down for some reason, while the client is still interested to keep sending data. This is more in the area of the architecture of the distributed system, so its way beyond the scope of the question.
From past few days i'm trying to implement the Spring cloud stream messaging system using RestController, but it is not happening through the current implementation.
For this sample code i'm going to add RestController
#EnableBinding(Source.class)
#EnableConfigurationProperties(TimeSourceOptionsMetadata.class)
public class TimeSource {
#Autowired
private TimeSourceOptionsMetadata options;
#InboundChannelAdapter(value = Source.OUTPUT)
public String timerMessageSource() {
return new SimpleDateFormat(this.options.getFormat()).format(new Date());
}
}
But the #InboundChannelAdapter cannot accept any parameters from RequestMapping Get Method URL.At the end what i need is to add message to the broker using Restful API Get method from api call. which is the best way to do it?, I couldn't figure out any best process from internet.
spring cloud team already provided a source application that listens for HTTP requests and emits the body as a message payload. If the Content-Type matches text/* or application/json, the payload will be a String, otherwise the payload will be a byte array.
github link
You can go with this or if you want to write it yourself, you can do it like below:
#RestController
#EnableBinding(Source.class)
public class RestSource {
#Autowired
private Source channels;
#RequestMapping(path = "/", method = POST, consumes = {"application/json" })
#ResponseStatus(HttpStatus.ACCEPTED)
public void handleRequest(#RequestBody String body, #RequestHeader(HttpHeaders.CONTENT_TYPE) Object contentType) {
sendMessage(body, contentType);
}
private void sendMessage(Object body, Object contentType) {
channels.output().send(MessageBuilder.createMessage(body,
new MessageHeaders(Collections.singletonMap(MessageHeaders.CONTENT_TYPE, contentType))));
}
}
I want to send a message to websocket subscribers of a specific record - when an action takes place in one of my service class.
I'm trying to read the Spring Websocket documentation but it's kind of ambiguous to the point of how to get all these things working together.
Here are my setup files (this is extending jHipster btw):
WebsocketConfiguration.java
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableStompBrokerRelay("/queue/", "/topic/", "/exchange/");
config.setApplicationDestinationPrefixes("/app");
config.setPathMatcher(new AntPathMatcher("."));
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws").withSockJS();
}
WebsocketSecurity.java
#Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
messages
// message types other than MESSAGE and SUBSCRIBE
.nullDestMatcher().authenticated()
// matches any destination that starts with /rooms/
.simpDestMatchers("/topic/tracker").hasAuthority(AuthoritiesConstants.ADMIN)
.simpDestMatchers("/topic/**").authenticated()
// (i.e. cannot send messages directly to /topic/, /queue/)
// (i.e. cannot subscribe to /topic/messages/* to get messages sent to
// /topic/messages-user<id>)
.simpTypeMatchers(SimpMessageType.MESSAGE, SimpMessageType.SUBSCRIBE).denyAll()
// catch all
.anyMessage().denyAll();
}
Controller class (attempt at implementing a simple broker I can test subscribing to from sockjs and recieving messages generated elsewhere in the application:
#MessageMapping("/ws")
#SendTo("/topic/sendactivity.{id}")
public void activity(#DestinationVariable string id, #Payload String message){
log.debug("Sending command center: "+message);
}
#RequestMapping(value = "/updateactivity", method = RequestMethod.PUT)
public ResponseEntity<Membership> updateMembership(
#RequestBody Membership membership) throws URISyntaxException {
// ...
String testString = "test";
messagingTemplate.convertAndSend("/topic/commandcenter"+membership.getId().toString(), testString);
// ...
}
When I put a breakpoint on the public void activity method, I don't get anything?
Sending a message to "/topic/commandcenterID" using the messaging template will send that message to the message broker, which will dispatch that message to clients subscribed to that topic. So it won't flow through your activity method.
When using #MessageMapping annotated methods, you're declaring those as application destinations. So sending a message to "/app/ws" should map to that method. Note that in that case I doubt it'll work since the destination variable you're expecting as a method argument is missing from the path definition in the #MessageMapping annotation.
Also, the #SendTo annotation in fact tells Spring that the value returned by the method should be converted to a message and sent to the given destination.
It seems you're mixing things up here, and I think you should:
read carefully the flow of messages in Spring STOMP support
look at a few example apps like the websocket portfolio and websocket chat
I'm have to implement custom API over Websockets that requires:
Custom WAMP-like subprotocol
Path parameters in socket URI
So I've following questions:
Is there any documentation or guides on implementing custom subprotocols in Spring? Protocol requires that exact version must be specified in the Sec-Websocket-Protocol field. Where this field could be read on server side?
What is a proper way to pass path parameters into a message handler? I could use ant patterns in handler registration
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(customHandler(), "/api/custom/{clientId}");
}
but those seems not available at TextWebSocketHandler. I'm solved this for now by extending default HttpSessionHandshakeInterceptor in a following way:
public class CustomHandshakeInterceptor extends HttpSessionHandshakeInterceptor {
private static final UriTemplate URI_TEMPLATE = new UriTemplate("/api/custom/{clientId}");
#Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
Map<String, String> segments = URI_TEMPLATE.match(request.getURI().getPath());
attributes.put("CLIENTID", segments.get("clientId"));
return super.beforeHandshake(request, response, wsHandler, attributes);
}
}
and then accessing it in TextWebSocketHandler:
public class CustomHandler extends TextWebSocketHandler {
#Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
super.handleTextMessage(session, message);
String clientId = session.getAttributes().get("CLIENTID");
...
session.sendMessage(response);
}
}
but this method, in my opinion, is a bit clunky. Is there more proper way to solve this?
Thanks.
The best advice I could give is to follow the example of the sub-protocol support that's built in -- starting with SubProtocolWebSocketHandler and the SubProtocolHandler's it delegates to including the StompSubProtocolHandler implementation. The SubProtocolWebSocketHandler is further connected to "clientInbound" and "clientOutbound" channels which are then used to form a processing flow as well as to provide thread boundaries.
There is a description for the processing flow for STOMP http://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-stomp-message-flow which includes delegating messages to annotated controllers and/or to a message broker which can also send messages back downstream to clients.
Essentially the StompSubProtocolHandler translates to and from a WebSocketMessage and a Spring Message with protocol-specific content. So that controllers, message brokers, or any other consumer of the messages from the client inbound channel are decoupled and unaware from the WebSocket transport layer. Many of the facilities built around the building, sending, and processing of such sub-protocol messages are meant to be potentially usable for support of other STOMP-like protocols. That includes all the classes in the org.springframework.messaging.simp package.
As for URL path parameters, Spring doesn't provide anything at the WebSocket level which is mostly a transport layer. Most of the interesting stuff happens at the sub-protocol level. For example for STOMP a MessageMapping is supported based on the destination header along with a #DestinationVariable which is comparable to using #PathVariable in Spring MVC but based on the destination header, not the URL.