I need to make my Spring Boot application start/stop listening on a new port dynamically.
I understand a new tomcat connector needs to be injected into Spring context for this.
I'm able to add a connector using a ServletWebServerFactory bean and tomcatConnectorCustomizer. But this bean is loaded only during Spring Bootup.
#Bean
public ServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
TomcatConnectorCustomizer tomcatConnectorCustomizer = connector -> {
connector.setPort(serverPort);
connector.setScheme("https");
connector.setSecure(true);
Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
protocol.setSSLEnabled(true);
protocol.setKeystoreType("PKCS12");
protocol.setKeystoreFile(keystorePath);
protocol.setKeystorePass(keystorePass);
protocol.setKeyAlias("spa");
protocol.setSSLVerifyClient(Boolean.toString(true));
tomcat.addConnectorCustomizers(tomcatConnectorCustomizer);
return tomcat;
}
}
Is there any way to add a tomcat connector during run time? Say on a method call?
Update: Connector created but all requests to it return 404:
I've managed to add a Tomcat connector at runtime. But the request made to that port are not going to my RestController.
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
TomcatConnectorCustomizer tomcatConnectorCustomizer = connector -> {
Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
connector.setScheme("http");
connector.setSecure(false);
connector.setPort(8472);
protocol.setSSLEnabled(false);
};
tomcat.addConnectorCustomizers(tomcatConnectorCustomizer);
tomcat.getWebServer().start();
How should I proceed further?
Hi here is my sample project: sample project
1- Main Application (DemoApplication.java):
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
2 - Config file (AppConfig.java):
#Configuration
public class AppConfig {
#Autowired
private ServletWebServerApplicationContext server;
private static FilterConfig filterConfig = new FilterConfig();
#PostConstruct
void init() {
//setting default port config
filterConfig.addNewPortConfig(8080, "/admin");
}
#Bean
#Scope(value = WebApplicationContext.SCOPE_APPLICATION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public FilterConfig createFilterConfig() {
return filterConfig;
}
public void addPort(String schema, String domain, int port, boolean secure) {
TomcatWebServer ts = (TomcatWebServer) server.getWebServer();
synchronized (this) {
ts.getTomcat().setConnector(createConnector(schema, domain, port, secure));
}
}
public void addContextAllowed(FilterConfig filterConfig, int port, String context) {
filterConfig.addNewPortConfig(port, context);
}
public void removePort(int port) {
TomcatWebServer ts = (TomcatWebServer) server.getWebServer();
Service service = ts.getTomcat().getService();
synchronized (this) {
Connector[] findConnectors = service.findConnectors();
for (Connector connector : findConnectors) {
if (connector.getPort() == port) {
try {
connector.stop();
connector.destroy();
filterConfig.removePortConfig(port);
} catch (LifecycleException e) {
e.printStackTrace();
}
}
}
}
}
private Connector createConnector(String schema, String domain, int port, boolean secure) {
Connector conn = new Connector("org.apache.coyote.http11.Http11NioProtocol");
conn.setScheme(schema);
conn.setPort(port);
conn.setSecure(true);
conn.setDomain(domain);
if (secure) {
// config secure port...
}
return conn;
}
}
3 - Filter (NewPortFilter.java):
public class NewPortFilter {
#Bean(name = "restrictFilter")
public FilterRegistrationBean<Filter> retstrictFilter(FilterConfig filterConfig) {
Filter filter = new OncePerRequestFilter() {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// get allowed url contexts
Set<String> config = filterConfig.getConfig().get(request.getLocalPort());
if (config == null || config.isEmpty()) {
response.sendError(403);
}
boolean accepted = false;
for (String value : config) {
if (request.getPathInfo().startsWith(value)) {
accepted = true;
break;
}
}
if (accepted) {
filterChain.doFilter(request, response);
} else {
response.sendError(403);
}
}
};
FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<Filter>();
filterRegistrationBean.setFilter(filter);
filterRegistrationBean.setOrder(-100);
filterRegistrationBean.setName("restrictFilter");
return filterRegistrationBean;
}
}
4 - Filter Config (FilterConfig.java):
public class FilterConfig {
private Map<Integer, Set<String>> acceptedContextsByPort = new ConcurrentHashMap<>();
public void addNewPortConfig(int port, String allowedContextUrl) {
if(port > 0 && allowedContextUrl != null) {
Set<String> set = acceptedContextsByPort.get(port);
if (set == null) {
set = new HashSet<>();
}
set = new HashSet<>(set);
set.add(allowedContextUrl);
acceptedContextsByPort.put(port, set);
}
}
public void removePortConfig(int port) {
if(port > 0) {
acceptedContextsByPort.remove(port);
}
}
public Map<Integer, Set<String>> getConfig(){
return acceptedContextsByPort;
}
}
5 - Controller (TestController.java):
#RestController
public class TestController {
#Autowired
AppConfig config;
#Autowired
FilterConfig filterConfig;
#GetMapping("/admin/hello")
String test() {
return "hello test";
}
#GetMapping("/alternative/hello")
String test2() {
return "hello test 2";
}
#GetMapping("/admin/addNewPort")
ResponseEntity<String> createNewPort(#RequestParam Integer port, #RequestParam String context) {
if (port == null || port < 1) {
return new ResponseEntity<>("Invalid Port" + port, HttpStatus.BAD_REQUEST);
}
config.addPort("http", "localhost", port, false);
if (context != null && context.length() > 0) {
config.addContextAllowed(filterConfig, port, context);
}
return new ResponseEntity<>("Added port:" + port, HttpStatus.OK);
}
#GetMapping("/admin/removePort")
ResponseEntity<String> removePort(#RequestParam Integer port) {
if (port == null || port < 1) {
return new ResponseEntity<>("Invalid Port" + port, HttpStatus.BAD_REQUEST);
}
config.removePort(port);
return new ResponseEntity<>("Removed port:" + port, HttpStatus.OK);
}
}
How to test it?
In a browser:
1 - try:
http://localhost:8080/admin/hello
Expected Response: hello test
2 - try:
http://localhost:8080/admin/addNewPort?port=9090&context=alternative
Expected Response: Added port:9090
3 - try:
http://localhost:9090/alternative/hello
Expected Response: hello test 2
4 - try expected errors:
http://localhost:9090/alternative/addNewPort?port=8181&context=alternative
Expected Response (context [alternative] allowed but endpoint not registered in controller for this context): Whitelabel Error Page...
http://localhost:9090/any/hello
Expected Response (context [any] not allowed): Whitelabel Error Page...
http://localhost:8888/any/hello
Expected Response (invalid port number): ERR_CONNECTION_REFUSED
http://localhost:8080/hello
Expected Response (no context allowed [/hello]): Whitelabel Error Page...
5 - try remove a port:
http://localhost:8080/admin/removePort?port=9090
6 - check removed port:
http://localhost:9090/alternative/hello
Expected Response (port closed): ERR_CONNECTION_REFUSED
I hope it helps.
You should create ServletWebServerFactory bean as a prototype bean using #Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE).
Now in bean where you need new tomcat connector to be injected into Spring context(MySingletonBean in example) autowire the application context and get ServletWebServerFactory bean(MyPrototypeBean in example) from getBean method. In this way you will always get new tomcat connector bean.
Following is a simple sample code:-
public class MySingletonBean {
#Autowired
private ApplicationContext applicationContext;
public void showMessage(){
MyPrototypeBean bean = applicationContext.getBean(MyPrototypeBean.class);
}
}
Based on the accepted answer of #ariel-carrera, I did it a little differently (IMO cleaner):
Define a normal Spring controller for handling the request on any dynamic port
import package.IgnoredBean;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import static org.apache.commons.lang3.reflect.MethodUtils.getMethodsWithAnnotation;
//must be declared in a separate java file so that it's not picked up by component scanning as inner class
#IgnoredBean
#RestController
#Slf4j
#RequiredArgsConstructor
class DynamicController {
static final Method HANDLER_METHOD = getMethodsWithAnnotation(DynamicController.class, RequestMapping.class)[0];
private final String myContext;
#RequestMapping
public Object handle(
#RequestBody Map<String, Object> body,
#RequestParam MultiValueMap<String, Object> requestParams,
#PathVariable Map<String, Object> pathVariables
) {
Map<String, Object> allAttributes = new HashMap<>(body.size() + requestParams.size() + pathVariables.size());
allAttributes.putAll(body);
allAttributes.putAll(pathVariables);
requestParams.forEach((name, values) -> allAttributes.put(name, values.size() > 1 ? values : values.get(0)));
log.info("Handling request for '{}': {}", myContext, allAttributes);
return allAttributes;
}
// this handler only affects this particular controller. Otherwise it will use any of your regular #ControllerAdvice beans or fall back to spring's default
#ExceptionHandler
public ResponseEntity<?> onError(Exception e) {
log.debug("something happened in '{}'", myContext, e);
return ResponseEntity.status(500).body(Map.of("message", e.getMessage()));
}
}
Must be declared in its own file
It is not a Spring Bean, we will instantiate it manually for each port and give it some context objects that are relevant to the port owner.
Note the #IgnoredBean, a custom annotation:
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
#Target(TYPE)
#Retention(RUNTIME)
public #interface IgnoredBean {
}
#SpringBootApplication
#ComponentScan(excludeFilters = #ComponentScan.Filter(IgnoredBean.class))
...
public class MyApplication{...}
The rest of it
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.startup.Tomcat;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.coyote.http11.Http11NioProtocol;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.embedded.tomcat.TomcatWebServer;
import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.condition.PathPatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;
import static java.util.stream.Collectors.toUnmodifiableSet;
#Service
#RequiredArgsConstructor
#Slf4j
class DynamicControllerService {
private final RequestMappingHandlerMapping requestHandlerMapper;
private final Map<Integer, RequestMappingInfo> mappingByPort = new ConcurrentHashMap<>();
private Tomcat tomcat;
#Autowired
void setTomcat(ServletWebServerApplicationContext context) {
tomcat = ((TomcatWebServer) context.getWebServer()).getTomcat();
}
public int addMapping(#Nullable Integer givenPort, RequestMethod method, String path, Object myContext) {
val connector = new Connector(new Http11NioProtocol());
connector.setThrowOnFailure(true);
//0 means it will pick any available port
connector.setPort(Optional.ofNullable(givenPort).orElse(0));
try {
tomcat.setConnector(connector);
} catch (IllegalArgumentException e) {
// if it fails to start the connector, the object will still be left inside here
tomcat.getService().removeConnector(connector);
val rootCause = ExceptionUtils.getRootCause(e);
throw new IllegalArgumentException(rootCause.getMessage(), rootCause);
}
int port = connector.getLocalPort();
val mapping = RequestMappingInfo
.paths(path)
.methods(method)
.customCondition(new PortRequestCondition(port))
.build();
requestHandlerMapper.registerMapping(
mapping,
new DynamicController("my context for port " + port),
DynamicController.HANDLER_METHOD
);
mappingByPort.put(port, mapping);
log.info("added mapping {} {} for port {}", method, path, port);
return port;
}
public void removeMapping(Integer port) {
Stream.of(tomcat.getService().findConnectors())
.filter(connector -> connector.getPort() == port)
.findFirst()
.ifPresent(connector -> {
try {
tomcat.getService().removeConnector(connector);
connector.destroy();
} catch (IllegalArgumentException | LifecycleException e) {
val rootCause = ExceptionUtils.getRootCause(e);
throw new IllegalArgumentException(rootCause.getMessage(), rootCause);
}
val mapping = mappingByPort.get(port);
requestHandlerMapper.unregisterMapping(mapping);
log.info("removed mapping {} {} for port {}",
mapping.getMethodsCondition().getMethods(),
Optional.ofNullable(mapping.getPathPatternsCondition())
.map(PathPatternsRequestCondition::getPatternValues)
.orElse(Set.of()),
port
);
});
}
#RequiredArgsConstructor
private static class PortRequestCondition implements RequestCondition<PortRequestCondition> {
private final Set<Integer> ports;
public PortRequestCondition(Integer... ports) {
this.ports = Set.of(ports);
}
#Override
public PortRequestCondition combine(PortRequestCondition other) {
return new PortRequestCondition(Stream.concat(ports.stream(), other.ports.stream()).collect(toUnmodifiableSet()));
}
#Override
public PortRequestCondition getMatchingCondition(HttpServletRequest request) {
return ports.contains(request.getLocalPort()) ? this : null;
}
#Override
public int compareTo(PortRequestCondition other, HttpServletRequest request) {
return 0;
}
}
}
I have an application with a large number of groups, where my server is using a message queue (RabbitMQ) to observe the groups and post notification to the user upon changes over WebSocket. I'm using Spring boot and their WebSocket implementation inspired by this guide: https://spring.io/guides/gs/messaging-stomp-websocket/
Here is an example of the JavaScript client subscribing to the channel:
var socket = new SockJS('http://localhost/ws');
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/group/1/notification', function (message) {
// to something..
});
});
My Java Spring WebSocket controller has this broadcastNotification method sending messages to the /topic/group/{groupId}/notification channel.
#Controller
public class GroupController {
private SimpMessagingTemplate template;
#Autowired
public GroupController(SimpMessagingTemplate template) {
this.template = template;
}
public void broadcastNotification(int groupId, Notification notification) {
this.template.convertAndSend("/topic/group/." + tenantId + "/notification", Notification);
}
}
Thats working fine, but with performacne in mind I would like my to business logic to only observe groups currently beeing subscribed on WebSocket.
How can I be notified on my server when clients subscribe to the /topic/group/1/notification or /topic/group/1/* channel? The web users will be subscribing and unsubscribing as they browse the web page.
You can listen to the event SessionSubscribeEvent like this:
#Component
public class WebSocketEventListener {
#EventListener
public void handleSessionSubscribeEvent(SessionSubscribeEvent event) {
GenericMessage message = (GenericMessage) event.getMessage();
String simpDestination = (String) message.getHeaders().get("simpDestination");
if (simpDestination.startsWith("/topic/group/1")) {
// do stuff
}
}
}
You can use annotation-driven event listener (Kotlin code):
#EventListener
private fun onSubscribeEvent(event: SessionSubscribeEvent) {
// do stuff...
}
Such event listeners can be registered on any public method of a managed bean via the #EventListener annotation.
Spring websockets events docs
Spring events examples
You can detect when a client subscribes to a topic using interceptors in the WebSocketConfig class:
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
#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("/gs-guide-websocket").withSockJS();
}
#Override
public void configureClientInboundChannel(ChannelRegistration registration){
registration.interceptors(new ChannelInterceptor() {
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
if(StompCommand.CONNECT.equals(accessor.getCommand())){
System.out.println("Connect ");
} else if(StompCommand.SUBSCRIBE.equals(accessor.getCommand())){
System.out.println("Subscribe ");
} else if(StompCommand.SEND.equals(accessor.getCommand())){
System.out.println("Send message " );
} else if(StompCommand.DISCONNECT.equals(accessor.getCommand())){
System.out.println("Exit ");
} else {
}
return message;
}
});
}
}
The accessor object contains all information sent from the client.
I'm new to the Play framework, and Play's docs are not comprehensive
I'm trying to write test websocket with help of this docs:
https://www.playframework.com/documentation/2.6.x/JavaWebSockets
Controller
package controllers.websockets;
import akka.actor.ActorSystem;
import akka.stream.Materializer;
import play.libs.streams.ActorFlow;
import play.mvc.Controller;
import play.mvc.WebSocket;
import javax.inject.Inject;
public class TestWebSocketController extends Controller {
private final ActorSystem actorSystem;
private final Materializer materializer;
#Inject
public TestWebSocketController(ActorSystem actorSystem, Materializer materializer) {
this.actorSystem = actorSystem;
this.materializer = materializer;
}
public WebSocket socket() {
return WebSocket.Text.accept(request ->
ActorFlow.actorRef(TestWebSocketActor::props,
actorSystem, materializer
)
);
}
}
Actor:
package controllers.websockets;
import akka.actor.AbstractActor;
import akka.actor.ActorRef;
import akka.actor.Props;
public class TestWebSocketActor extends AbstractActor {
public static Props props(ActorRef out) {
return Props.create(TestWebSocketActor.class, out);
}
private final ActorRef out;
public TestWebSocketActor(ActorRef out) {
this.out = out;
}
#Override
public AbstractActor.Receive createReceive() {
return receiveBuilder()
.match(String.class, message ->
out.tell("I received your message: " + message, self())
)
.build();
}
}
conf/routes:
# WebSockets
GET /ws controllers.websockets.TestWebSocketController.socket()
I'm trying to connect with help of Simmple WebSocket Plugin for Chrome, https://www.websocket.org/echo.html, with such url: ws://localhost:9000/ws , but error message is appeared:
WebSocket connection to 'ws://localhost:9000/ws' failed: Error during WebSocket handshake: Unexpected response code: 404
CLOSED: ws://localhost:9000/ws
SOLVED: see comment
I am currently trying to open a WebSockets connection between a spring server and an angular client with a x-auth-token. Therefore I have the following pieces of code:
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.support.ChannelInterceptorAdapter;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompSession;
import org.springframework.messaging.simp.stomp.StompHeaders;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
#Configuration
#EnableWebSocketMessageBroker
#Order(Ordered.HIGHEST_PRECEDENCE + 99)
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
private final Logger LOGGER = LoggerFactory.getLogger(this.getClass());
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
LOGGER.info("registering websockets");
registry.addEndpoint("/api/v1/websocket").setAllowedOrigins("*").withSockJS();
}
#Override
public void handleException(StompSession session, StompCommand command, StompHeaders headers, byte[] payload, Throwable exceptio) {
LOGGER.info("ERRORORRRRRRRR");
exception.printStackTrace();
}
#Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.setInterceptors(new ChannelInterceptorAdapter() {
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
LOGGER.info("in override");
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
String authToken = accessor.getFirstNativeHeader("x-auth-token");
LOGGER.info("Header auth token: " + authToken);
// Principal user = ... ; // access authentication header(s)
//accessor.setUser(user);
}
return message;
}
});
}
}
As you can see from my WebSocketConfig I am currently not really processing the x-auth-token, but rather trying to log it once. In the end I was trying to follow the Spring documentation: https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-stomp-authentication-token-based
The only problem I have with this is that in configureClientInboundChannel() the preSend() method seems not get called - and I cannot figure out why.
On the client side, the following error is displayed:
As you can see by comparing the error log and the endpoint defined in registerStompEndpoints() shows the actual endpoint is the correct one. Still I do not understand, why LOGGER.info("in override"); in configureClientInboundChannel() is not called.
I would really like to go with the auth-token being transferred via the stomp headers instead of the hacky token-as-URL-paramter-solution. Does someone here have an idea? Did I miss something in the already linked spring documentation?
Thanks in advance for your help!
Here is the codec and data format configuration:
package com.chorke.boot.jproxy.config;
import org.apache.camel.component.hl7.HL7DataFormat;
import org.apache.camel.component.hl7.HL7MLLPCodec;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
#Configuration
#ImportResource({"classpath*:/META-INF/camel/applicationContext-camel.xml"})
public class ApacheCamelConfig {
#SuppressWarnings("unused")
private static final Logger log = LoggerFactory.getLogger(ApacheCamelConfig.class);
#Bean
public HL7MLLPCodec hl7codec() {
HL7MLLPCodec hl7codec = new HL7MLLPCodec();
hl7codec.setCharset("UTF-8");
return hl7codec;
}
#Bean
public HL7DataFormat hl7Format() {
HL7DataFormat hl7Format = new HL7DataFormat();
return hl7Format;
}
}
Here is the port forwarding route:
package com.chorke.boot.jproxy.route;
import org.apache.camel.builder.RouteBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ProxyRoute extends RouteBuilder {
private static final Logger log =LoggerFactory.getLogger(ProxyRoute.class);
#Override
public void configure() throws Exception {
from("mina2://tcp://0.0.0.0:22210?codec=#hl7codec&sync=true").process(new Processor() {
public void process(Exchange exchange) throws Exception {
String body = exchange.getIn().getBody(String.class);
log.info("Port-Forwarded body:\n {}", body);
}
}).to("mina2://tcp://192.168.0.10:22210?codec=#hl7codec&sync=true").end();
from("mina2://tcp://0.0.0.0:22211?codec=#hl7codec&sync=true").process(new Processor() {
public void process(Exchange exchange) throws Exception {
String body = exchange.getIn().getBody(String.class);
log.info("Port-Forwarded body:\n {}", body);
}
}).to("mina2://tcp://192.168.0.11:22211?codec=#hl7codec&sync=true").end();
}
}
The route summery is:
============================================
request forwarded
tcp ip:port tcp ip:port
============================================
0.0.0.0:22210 192.168.0.10:22210
0.0.0.0:22211 192.168.0.11:22211
============================================
And it's working fine, it's protocol specific for MLLP. But our goal is to route any request regardless their protocol. Lets say it could be handle any kind of request, not limited to HTTP, REST, SOAP, MLLP, SMTP, FTP, SMB or etc. Would you please help us to configure port-forwarding route regardless their protocol.
There is no out of the box support for protocol detection and route port forwarding in Apache Camel. You are better finding some existing product that can do that.