1

I am pretty new to spring-xd and need some advice for creating a custom sink. Specifically, I would like to create a sink that registers a Websocket. The general idea is that

module upload --file "websocket-sink-0.0.1-SNAPSHOT.jar" --type "sink" --name "websocket-sink"
stream create --name "websocket-sink-test" --definition "http --port=9191 | websocket-sink --port=9292" --deploy

should install the sink module and create a stream that accepts http input on port 9191 and sends the payload to the websocket sink where clients can connect (via port 9292) and consume that data.

I pretty much followed the guidelines in the documentation and used the spring integration websocket tutorial from Josh Long's techtips (https://github.com/joshlong/techtips/tree/master/examples/spring-integration-4.1-websockets-example). So this is what i came up with

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.dsl.support.Function;
import org.springframework.integration.websocket.ServerWebSocketContainer;
import org.springframework.integration.websocket.outbound.WebSocketOutboundMessageHandler;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.Executors;
import java.util.stream.Collectors;


@Configuration
@EnableIntegration
@ComponentScan
@EnableAutoConfiguration
@RestController
public class WebsocketSink {

    @Bean
    ServerWebSocketContainer serverWebSocketContainer() {
        return new ServerWebSocketContainer("/messages").withSockJs();
    }

    @Bean
    MessageHandler webSocketOutboundAdapter() {
        return new WebSocketOutboundMessageHandler(serverWebSocketContainer());
    }

    @Bean
    MessageChannel input() {
        return new DirectChannel();
    }

    @Bean
    IntegrationFlow webSocketFlow() {
        Function<Message, Object> splitter = m -> serverWebSocketContainer()
                .getSessions()
                .keySet()
                .stream()
                .map(s -> MessageBuilder.fromMessage(m)
                        .setHeader(SimpMessageHeaderAccessor.SESSION_ID_HEADER, s)
                        .build())
                .collect(Collectors.toList());

        return IntegrationFlows
                .from(input())
                .split(Message.class, splitter)
                .channel(c -> c.executor(Executors.newCachedThreadPool()))
                .handle(webSocketOutboundAdapter()).get();
    }

}

some of the class level annotations (e.g. @ComponentScan or @RestController) might not be required i guess. But the thing i am missing (conceptually) is where and how websocket clients would connect to the websocket exposed by the sink (that is, how can i specify the port that should be used for the container that hosts the Websocket in the sink implementation, e.g. 9292 in the stream definition from above).

I am a little lost here so I would be grateful for any advice.

omoser
  • 115
  • 2
  • 8

1 Answers1

1

The Spring Integration WebSocket adapter in Josh's tech tip needs to run in a web container (tomcat etc). So it's not currently supported out of the box in XD.

The http source uses a custom HTTP channel adapter based on netty so it doesn't have the same issue (standard Spring Integration http inbound endpoints also need to run in a container).

Feel free to open a new feature JIRA issue.

Gary Russell
  • 166,535
  • 14
  • 146
  • 179
  • Thanks Gary. The thing about the webcontainer is basically what I assumed. It seems there is already a JIRA issue for [xd-native websocket sinks](https://jira.spring.io/browse/XD-2398). However, since there is no timeframe on that issue I would be glad to help. So, conceptually, we need to fire up an embedded Webcontainer in the sink implementation and link the `ServerWebSocketContainer`? Is it possible to use `@EnableAutoConfiguration` or other Spring Boot related features to configure the Webcontainer stuff? – omoser Mar 25 '15 at 09:43
  • 1
    I am taking a quick look at a prototype for XD-2398 using netty; I need to think some more but I suspect that adding an embedded web container in the sink will be non-trivial - the module's input channel would need to be hooked into the websocket context somehow. – Gary Russell Mar 25 '15 at 11:12
  • You can take a look to the [tests](https://github.com/spring-projects/spring-integration/blob/master/spring-integration-websocket/src/test/java/org/springframework/integration/websocket/server/WebSocketServerTests.java). The main feature there is a `@Bean` for the `TomcatWebSocketTestServer`. Hope that helps somehow – Artem Bilan Mar 25 '15 at 12:14
  • thanks for all your input. I put together a very basic sink implementation that uses netty for the websocket part: [spring-xd-netty-websocket-sink](https://github.com/olmoser/spring-xd-netty-websocket-sink) The code is largely based on the netty websocket examples, and definitely needs improvement. But it covers the basic use case I have. Suggestions very welcome :-) – omoser Mar 26 '15 at 16:55