Netty-Socketio with Spring MVC - spring

I am trying to add sockets to my Spring MVC project. I think I should wright something in servlet-context.xml but don`t know what. So I create classes
#Component
public class Bootstrap {
#Autowired
private SocketIOServer server;
#PostConstruct
public void start() {
server.start();
}
#PreDestroy
public void stop() {
server.stop();
}
}
and
#Configuration
#ComponentScan("controllers")
public class SpringConfig {
#Bean(name="webSocketServer")
public SocketIOServer webSocketServer() {
com.corundumstudio.socketio.Configuration config = new com.corundumstudio.socketio.Configuration();
config.setHostname("localhost");
config.setPort(4443);
final SocketIOServer server = new SocketIOServer(config);
server.addJsonObjectListener(LogFile.class, new DataListener<LogFile>() {
#Override
public void onData(SocketIOClient client, LogFile data, AckRequest ackSender) {
server.getBroadcastOperations().sendJsonObject(data);
}
});
server.addConnectListener(new ConnectListener() {
#Override
public void onConnect(SocketIOClient client) {
LogFile log = new LogFile();
log.setMsg("hello");
server.getBroadcastOperations().sendJsonObject(log);
}
});
return server;
}
}
But nothing gonna work. I added client to resources of project and enter link localhost there and have client on my local machine with right link. But still can`t connect. What I am doing wrong? I believe that I mistaken in configuring beans.
Samples I get from this https://github.com/mrniko/netty-socketio.

Related

Spring Boot and Spring Data with Cassandra: Continue on failed database connection

I use Spring Boot and Spring Data with Cassandra. On application startup spring establishes a connection to the database to setup the schema and initialize spring data repositories. If the database is not available, the application won't start.
I want, that the application just logs an error and starts. Of course, I can't use the repositories anymore, but other services (rest controllers etc), which are independent from the database should work. It would also be nice to see in actuator healthcheck, that cassandra is down.
For JDBC, there is a spring.datasource.continue-on-error property. I couldn't find something similar for Cassandra.
I also tried to create a custom cassandra configuration and trying to catch Exception on CqlSession creation, but I couldn't achieve the desired behavior.
EDIT: As suggested by #adutra, I tried to set advanced.reconnect-on-init, Application tries to establish the connection, but the application is not fully initialized (e.g. REST controller are not reachable)
#Configuration
public class CustomCassandraConfiguration extends CassandraAutoConfiguration {
#Bean
public DriverConfigLoaderBuilderCustomizer driverConfigLoaderBuilderCustomizer() {
return builder -> builder.withBoolean(DefaultDriverOption.RECONNECT_ON_INIT, true);
}
}
EDIT2: I have now working example (application starts, custom health check for cassandra), but if feels pretty ugly:
CustomCassandraAutoConfiguration
#Configuration
public class CustomCassandraAutoConfiguration extends CassandraAutoConfiguration {
private final Logger logger = LoggerFactory.getLogger(getClass());
#Override
#Bean
public CqlSession cassandraSession(CqlSessionBuilder cqlSessionBuilder) {
try {
return super.cassandraSession(cqlSessionBuilder);
} catch (AllNodesFailedException e) {
logger.error("Failed to establish the database connection", e);
}
return new DatabaseNotConnectedFakeCqlSession();
}
#Bean
public CassandraReactiveHealthIndicator cassandraHealthIndicator(ReactiveCassandraOperations r, CqlSession session) {
if (session instanceof DatabaseNotConnectedFakeCqlSession) {
return new CassandraReactiveHealthIndicator(r) {
#Override
protected Mono<Health> doHealthCheck(Health.Builder builder) {
return Mono.just(builder.down().withDetail("connection", "was not available on startup").build());
}
};
}
return new CassandraReactiveHealthIndicator(r);
}
}
CustomCassandraDataAutoConfiguration
#Configuration
public class CustomCassandraDataAutoConfiguration extends CassandraDataAutoConfiguration {
public CustomCassandraDataAutoConfiguration(CqlSession session) {
super(session);
}
#Bean
public SessionFactoryFactoryBean cassandraSessionFactory(CqlSession session, Environment environment, CassandraConverter converter) {
SessionFactoryFactoryBean sessionFactoryFactoryBean = super.cassandraSessionFactory(environment, converter);
// Disable schema action if database is not available
if (session instanceof DatabaseNotConnectedFakeCqlSession) {
sessionFactoryFactoryBean.setSchemaAction(SchemaAction.NONE);
}
return sessionFactoryFactoryBean;
}
}
DatabaseNotConnectedFakeCqlSession (Fake session implementation)
public class DatabaseNotConnectedFakeCqlSession implements CqlSession {
#Override
public String getName() {
return null;
}
#Override
public Metadata getMetadata() {
return null;
}
#Override
public boolean isSchemaMetadataEnabled() {
return false;
}
#Override
public CompletionStage<Metadata> setSchemaMetadataEnabled( Boolean newValue) {
return null;
}
#Override
public CompletionStage<Metadata> refreshSchemaAsync() {
return null;
}
#Override
public CompletionStage<Boolean> checkSchemaAgreementAsync() {
return null;
}
#Override
public DriverContext getContext() {
return new DefaultDriverContext(new DefaultDriverConfigLoader(), ProgrammaticArguments.builder().build());
}
#Override
public Optional<CqlIdentifier> getKeyspace() {
return Optional.empty();
}
#Override
public Optional<Metrics> getMetrics() {
return Optional.empty();
}
#Override
public <RequestT extends Request, ResultT> ResultT execute( RequestT request, GenericType<ResultT> resultType) {
return null;
}
#Override
public CompletionStage<Void> closeFuture() {
return null;
}
#Override
public CompletionStage<Void> closeAsync() {
return null;
}
#Override
public CompletionStage<Void> forceCloseAsync() {
return null;
}
#Override
public Metadata refreshSchema() {
return null;
}
}
Any suggestions?
You can set the option datastax-java-driver.advanced.reconnect-on-init to true to achieve the effect you want. Its usage is explained in the configuration reference page in the driver docs:
Whether to schedule reconnection attempts if all contact points are unreachable on the first initialization attempt.
If this is true, the driver will retry according to the reconnection policy. The SessionBuilder.build() call - or the future returned by SessionBuilder.buildAsync() - won't complete until a contact point has been reached. If this is false and no contact points are available, the driver will fail with an AllNodesFailedException.
However be careful: with this option set to true, as stated above, any component trying to access a CqlSession bean, even if the session bean is lazy, will block until the driver is able to connect, and might block forever if the contact points are wrong.
If that's not acceptable for you, I would suggest that you wrap the CqlSession bean in another bean that will check if the future returned by SessionBuilder.buildAsync() is done or not, and either block, throw or return null, depending on the caller's expectations.
[EDIT] I've reached out internally to the DataStax Drivers team last night and adutra has responded so I'm withdrawing my response.

Spring Boot Apache Camel Routes testing

I have a Springboot application, where I have some Camel routes configured.
public class CamelConfig {
private static final Logger LOG = LoggerFactory.getLogger(CamelConfig.class);
#Value("${activemq.broker.url:tcp://localhost:61616}")
String brokerUrl;
#Value("${activemq.broker.maxconnections:1}")
int maxConnections;
#Bean
ConnectionFactory jmsConnectionFactory() {
PooledConnectionFactory pooledConnectionFactory = new PooledConnectionFactory(new ActiveMQConnectionFactory(brokerUrl));
pooledConnectionFactory.setMaxConnections(maxConnections);
return pooledConnectionFactory;
}
#Bean
public RoutesBuilder route() {
LOG.info("Initializing camel routes......................");
return new SpringRouteBuilder() {
#Override
public void configure() throws Exception {
from("activemq:testQueue")
.to("bean:queueEventHandler?method=handleQueueEvent");
}
};
}
}
I want to test this route from activemq:testQueue to queueEventHandler::handleQueueEvent.
I tried different things mentioned here http://camel.apache.org/camel-test.html, but doesn't seem to get it working.
I am trying to do something like this:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {CamelConfig.class, CamelTestContextBootstrapper.class})
public class CamelRouteConfigTest {
#Produce(uri = "activemq:testQueue")
protected ProducerTemplate template;
#Test
public void testSendMatchingMessage() throws Exception {
template.sendBodyAndHeader("testJson", "foo", "bar");
// Verify handleQueueEvent(...) method is called on bean queueEventHandler by mocking
}
But my ProducerTemplate is always null. I tried auto-wiring CamelContext, for which I get an exception saying it cannot resolve camelContext. But that can be resolved by adding SpringCamelContext.class to #SpringBootTest classes. But my ProducerTemplate is still null.
Please suggest. I am using Camel 2.18 and Spring Boot 1.4.
In Camel 2.22.0 and ongoing, which supports Spring Boot 2 you can use the following template to test your routes with Spring Boot 2 support:
#RunWith(CamelSpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.NONE, classes = {
Route1.class,
Route2.class,
...
})
#EnableAutoConfiguration
#DisableJmx
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
public class RouteTest {
#TestConfiguration
static class Config {
#Bean
CamelContextConfiguration contextConfiguration() {
return new CamelContextConfiguration() {
#Override
public void beforeApplicationStart(CamelContext camelContext) {
// configure Camel here
}
#Override
public void afterApplicationStart(CamelContext camelContext) {
// Start your manual routes here
}
};
}
#Bean
RouteBuilder routeBuilder() {
return new RouteBuilder() {
#Override
public void configure() {
from("direct:someEndpoint").to("mock:done");
}
};
}
// further beans ...
}
#Produce(uri = "direct:start")
private ProducerTemplate template;
#EndpointInject(uri = "mock:done")
private MockEndpoint mockDone;
#Test
public void testCamelRoute() throws Exception {
mockDone.expectedMessageCount(1);
Map<String, Object> headers = new HashMap<>();
...
template.sendBodyAndHeaders("test", headers);
mockDone.assertIsSatisfied();
}
}
Spring Boot distinguishes between #Configuration and #TestConfiguration. The primer one will replace any existing configuration, if annotated on a top-level class, while #TestConfiguration will be run in addition to the other configurations.
Further, in larger projects you might run into auto-configuration issues as you can't rely on Spring Boot 2 to configure your custom database pooling or what not correctly or in cases where you have a specific directory structure and the configurations are not located within a direct ancestor directory. In that case it is proabably preferable to omit the #EnableAutoConfiguration annotation. In order to tell Spring to still auto-configure Camel you can simply pass CamelAutoConfiguration.class to the classes mentioned in #SpringBootTest
#SpringBootTest(webEnvironment = WebEnvironment.NONE, classes = {
Route1.class,
Route2.class,
RouteTest.Config.class,
CamelAutoConfiguration.class
}
As no automatic configuration is performed, Spring won't load the test configuration inside your test class nor initialize Camel as well. By adding those configs to the boot classes manually Spring will do it for you.
For one route with MQ and Spring Boot like this:
#Component
public class InboundRoute extends RouteBuilder {
#Override
public void configure() {
JaxbDataFormat personDataFormat = new JaxbDataFormat();
personDataFormat.setContextPath(Person.class.getPackage().getName());
personDataFormat.setPrettyPrint(true);
from("direct:start").id("InboundRoute")
.log("inbound route")
.marshal(personDataFormat)
.to("log:com.company.app?showAll=true&multiline=true")
.convertBodyTo(String.class)
.inOnly("mq:q.empi.deim.in")
.transform(constant("DONE"));
}
}
I use adviceWith in order to replace the endpoint and use only mocks:
#RunWith(CamelSpringBootRunner.class)
#UseAdviceWith
#SpringBootTest(classes = InboundApp.class)
#MockEndpoints("mock:a")
public class InboundRouteCamelTest {
#EndpointInject(uri = "mock:a")
private MockEndpoint mock;
#Produce(uri = "direct:start")
private ProducerTemplate template;
#Autowired
private CamelContext context;
#Test
public void whenInboundRouteIsCalled_thenSuccess() throws Exception {
mock.expectedMinimumMessageCount(1);
RouteDefinition route = context.getRouteDefinition("InboundRoute");
route.adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() {
weaveByToUri("mq:q.empi.deim.in").replace().to("mock:a");
}
});
context.start();
String response = (String) template.requestBodyAndHeader("direct:start",
getSampleMessage("/SimplePatient.xml"), Exchange.CONTENT_TYPE, MediaType.APPLICATION_XML);
assertThat(response).isEqualTo("DONE");
mock.assertIsSatisfied();
}
private String getSampleMessage(String filename) throws Exception {
return IOUtils
.toString(this.getClass().getResourceAsStream(filename), StandardCharsets.UTF_8.name());
}
}
I use the following dependencies: Spring Boot 2.1.4-RELEASE and Camel 2.23.2. The complete source code is available on Github.
This is how I did this finally:
#RunWith(SpringRunner.class)
public class CamelRouteConfigTest extends CamelTestSupport {
private static final Logger LOG = LoggerFactory.getLogger(CamelRouteConfigTest.class);
private static BrokerService brokerSvc = new BrokerService();
#Mock
private QueueEventHandler queueEventHandler;
#BeforeClass
// Sets up an embedded broker
public static void setUpBroker() throws Exception {
brokerSvc.setBrokerName("TestBroker");
brokerSvc.addConnector("tcp://localhost:61616");
brokerSvc.setPersistent(false);
brokerSvc.setUseJmx(false);
brokerSvc.start();
}
#Override
protected RoutesBuilder createRouteBuilder() throws Exception {
return new CamelConfig().route();
}
// properties in .yml has to be loaded manually. Not sure of .properties file
#Override
protected Properties useOverridePropertiesWithPropertiesComponent() {
YamlPropertySourceLoader loader = new YamlPropertySourceLoader();
try {
PropertySource<?> applicationYamlPropertySource = loader.load(
"properties", new ClassPathResource("application.yml"),null);// null indicated common properties for all profiles.
Map source = ((MapPropertySource) applicationYamlPropertySource).getSource();
Properties properties = new Properties();
properties.putAll(source);
return properties;
} catch (IOException e) {
LOG.error("application.yml file cannot be found.");
}
return null;
}
#Override
protected JndiRegistry createRegistry() throws Exception {
JndiRegistry jndi = super.createRegistry();
MockitoAnnotations.initMocks(this);
jndi.bind("queueEventHandler", queueEventHandler);
return jndi;
}
#Test
// Sleeping for a few seconds is necessary, because this line template.sendBody runs in a different thread and
// CamelTest takes a few seconds to do the routing.
public void testRoute() throws InterruptedException {
template.sendBody("activemq:productpushevent", "HelloWorld!");
Thread.sleep(2000);
verify(queueEventHandler, times(1)).handleQueueEvent(any());
}
#AfterClass
public static void shutDownBroker() throws Exception {
brokerSvc.stop();
}
}
Did you try using Camel test runner?
#RunWith(CamelSpringJUnit4ClassRunner.class)
If you are using camel-spring-boot dependency, you may know that it uses auto configuration to setup Camel:
CamelAutoConfiguration.java
It means that you may also need to add #EnableAutoConfiguration to your test.

Springs "#MessagingGateway" annotation on external interfaces

I am about to migrate a project created with an old version of Spring (using XML config) to Spring Boot (using Java config).
The project is using Spring Integration for communication via JMS and AMQP. As far as I understood, I have to replace the
<int:gateway id="someID" service-interface="MyMessageGateway"
default-request-channel="myRequestChannel"
default-reply-channel="myResponseChannel"
default-reply-timeout="20000" />
with
#MessagingGateway(name="someID", defaultRequestChannel = "myRequestChannel",
defaultReplyChannel = "myResponseChannel", defaultReplyTimeout = "20000")
public interface MyMessageGateway{ ...... }
My problem is, that the interface, that is in use now, is placed in a library I can't access.
How can I define this Interface as my MessagingGateway?
Thanks in advance!
I have just tested this trick:
interface IControlBusGateway {
void send(String command);
}
#MessagingGateway(defaultRequestChannel = "controlBus")
interface ControlBusGateway extends IControlBusGateway {
}
...
#Autowired
private IControlBusGateway controlBus;
...
try {
this.bridgeFlow2Input.send(message);
fail("Expected MessageDispatchingException");
}
catch (Exception e) {
assertThat(e, instanceOf(MessageDeliveryException.class));
assertThat(e.getCause(), instanceOf(MessageDispatchingException.class));
assertThat(e.getMessage(), containsString("Dispatcher has no subscribers"));
}
this.controlBus.send("#bridge.start()");
this.bridgeFlow2Input.send(message);
reply = this.bridgeFlow2Output.receive(5000);
assertNotNull(reply);
In other words you can just extends that external interface to your local one. The GatewayProxyFactoryBean will do the proxy magic for you underneath.
Also we have this JIRA for similar use-case: https://jira.spring.io/browse/INT-4134
Use a GatewayProxyFactoryBean; here's a simple example:
#SpringBootApplication
public class So41162166Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(So41162166Application.class, args);
context.getBean(NoAnnotationsAllowed.class).foo("foo");
context.close();
}
#Bean
public GatewayProxyFactoryBean gateway() {
GatewayProxyFactoryBean gateway = new GatewayProxyFactoryBean(NoAnnotationsAllowed.class);
gateway.setDefaultRequestChannel(channel());
return gateway;
}
#Bean
public MessageChannel channel() {
return new DirectChannel();
}
#ServiceActivator(inputChannel = "channel")
public void out(String foo) {
System.out.println(foo);
}
public static interface NoAnnotationsAllowed {
public void foo(String out);
}
}

Expose/Filter Controller Request Mappings by Port/Connector

I have a relatively simple Spring Boot application that, by default, is secured over SSL on port 9443 using a self-signed certificate, which works great for serving up APIs to, say, a mobile app. However, I would now like to develop an unsecured web application with its own frontend and serve up a subset of the content I allow over SSL.
This is what I've come up with so far, which enables port 8080 over HTTP in addition to port 9443, the latter I've defined in application.properties:
#SpringBootApplication
#ComponentScan
public class Application extends WebMvcConfigurerAdapter {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public EmbeddedServletContainerFactory servletContainer() {
TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory();
tomcat.addAdditionalTomcatConnectors(createWebsiteConnector());
return tomcat;
}
private Connector createWebsiteConnector() {
Connector connector = new Connector(TomcatEmbeddedServletContainerFactory.DEFAULT_PROTOCOL);
connector.setPort(8080);
return connector;
}
}
I am now faced with the task of only exposing endpoints to the 8080 connection, and all of them to 9443. Obviously, the latter currently works by default, but right now 8080 can access everything 9443 can. Ideally, I would like to control access to certain request mappings defined in a "shared" controller that both connections have access to, i.e. something like this:
#RequestMapping(value = "/public", method = RequestMethod.GET)
public List<String> getPublicInfo() {
// ...
}
#HTTPSOnly
#RequestMapping(value = "/secured", method = RequestMethod.GET)
public List<String> getSecuredInfo() {
// ...
}
I assume something like what I have above isn't actually possible, but does anyone know how I could achieve the same effect?
Thanks in advance for any help!
Alright, I think I actually managed to solve this myself, but I'm open to other suggestions if anyone thinks they have a better solution:
#SpringBootApplication
#ComponentScan
public class Application extends WebMvcConfigurerAdapter {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public EmbeddedServletContainerFactory servletContainer() {
TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory();
tomcat.addAdditionalTomcatConnectors(createWebsiteConnector());
return tomcat;
}
private Connector createWebsiteConnector() {
Connector connector = new Connector(TomcatEmbeddedServletContainerFactory.DEFAULT_PROTOCOL);
connector.setPort(8080);
return connector;
}
private static HashSet<String> uriWhitelist = new HashSet<>(4);
static {
// static website content
uriWhitelist.add("/");
uriWhitelist.add("/index.html");
uriWhitelist.add("/logo_48x48.png");
// public APIs
uriWhitelist.add("/public");
}
private static class PortFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (request instanceof RequestFacade) {
RequestFacade requestFacade = (RequestFacade) request;
if (requestFacade.getServerPort() != 9443 && !uriWhitelist.contains(requestFacade.getRequestURI())) {
// only allow unsecured requests to access whitelisted endpoints
return;
}
}
filterChain.doFilter(request, response);
}
}
#Bean
public FilterRegistrationBean portFilter() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
PortFilter filter = new PortFilter();
filterRegistrationBean.setFilter(filter);
return filterRegistrationBean;
}
}

Should the Spring Boot shutdown endpoint shut down the entire JVM process, or just the application context?

I am getting a 200 response from Spring Boot's shutdown endpoint, and I am seeing that the application context shuts down as expected, but then the JVM process itself remains alive forever. Is this the expected behavior of the shutdown endpoint, or is it expected that the process itself would also terminate gracefully?
In http://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-endpoints.html, it says that the shutdown endpoint "allows the application to be gracefully shutdown (not enabled by default)".
Thanks Stéphane, I found what was preventing the JVM process from terminating after hitting the /shutdown endpoint. There was a ScheduledExecutor in one of my dependencies that was not being shut down with the application context, and it was preventing the JVM process from shutting down (even after the application context was closed). I wrote a simple example to show how to reproduce the behavior, and another example showing how to resolve it.
This example will NOT terminate the JVM process when you hit /shutdown endpoint:
#SpringBootApplication
public class AppSpringConfiguration {
public static void main(String[] args) {
SpringApplication.run(AppSpringConfiguration.class);
}
#Bean
public ClassWithExecutor ce() {
return new ClassWithExecutor();
}
#PostConstruct
public void startScheduledTask() {
ce().startScheduledTask();
}
#RestController
public static class BusinessLogicController {
#RequestMapping(value = "/hi")
public String businessLogic() {
return "hi";
}
}
public static class ClassWithExecutor {
ScheduledExecutorService es;
ClassWithExecutor() {
this.es = Executors.newSingleThreadScheduledExecutor();
}
public void startScheduledTask() {
es.scheduleAtFixedRate(new Runnable() {
#Override
public void run() {
System.out.println("Printing this every minute");
}
}, 0, 3, TimeUnit.SECONDS);
}
}
}
By adding a shutdown hook that shuts down the ScheduledExecutor when the application context is closing, the JVM process now gets terminated after hitting the /shutdown endpoint:
#SpringBootApplication
public class AppSpringConfiguration {
public static void main(String[] args) {
SpringApplication.run(AppSpringConfiguration.class);
}
#Bean
public ClassWithExecutor ce() {
return new ClassWithExecutor();
}
#Bean
ShutdownAction sa() {
return new ShutdownAction(ce());
}
#PostConstruct
public void startScheduledTask() {
ce().startScheduledTask();
}
#RestController
public static class BusinessLogicController {
#RequestMapping(value = "/hi")
public String businessLogic() {
return "hi";
}
}
public static class ShutdownAction implements ApplicationListener<ContextClosedEvent> {
private ClassWithExecutor classWithExecutor;
ShutdownAction(ClassWithExecutor classWithExecutor) {
this.classWithExecutor = classWithExecutor;
}
#Override
public void onApplicationEvent(ContextClosedEvent event) {
classWithExecutor.shutdown();
}
}
public static class ClassWithExecutor {
ScheduledExecutorService es;
ClassWithExecutor() {
this.es = Executors.newSingleThreadScheduledExecutor();
}
public void startScheduledTask() {
es.scheduleAtFixedRate(new Runnable() {
#Override
public void run() {
System.out.println("Printing this every minute");
}
}, 0, 3, TimeUnit.SECONDS);
}
public void shutdown() {
es.shutdownNow();
}
}
}
You have something that prevents the JVM to exit besides your Spring Boot application. If you don't and you have a sample projet that demonstrates the problem, then please create an issue and we'll have a look.
Instead of using the shutdown endpoint, you can use the spring-boot-maven-plugin as of 1.3 that has a start and stop goals to be used in typical integration tests scenarios.
If you have a scheduled executor running you should specify destroy method:
#Bean(destroyMethod = "shutdown")

Resources