Unable to fetch random port when server.port is 0 - spring-boot

I need to fetch port number on which undertow was Started by my Sprint boot app. I have defined server.port=0 in application.properties. I cannot use fix port numbers like 8080.
package com.aggregate.application;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Properties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
#Configuration
#ComponentScan(basePackages = {"com.aggregate"})
#EnableAutoConfiguration
public class GServiceApplication extends SpringBootServletInitializer
implements ApplicationListener<ApplicationReadyEvent> {
#Autowired
private ApplicationContext applicationContext;
#Override
public void onApplicationEvent(ApplicationReadyEvent event) {
try {
String ip = InetAddress.getLocalHost().getHostAddress();
String port = applicationContext.getBean(Environment.class).getProperty("server.port");
System.out.printf("ip:port=" +ip+ ":"+port);
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws UnknownHostException
{
SpringApplication application = new SpringApplication(GServiceApplication.class);
Properties properties = new Properties();
properties.put("server.port", 0);
properties.put("server.address", InetAddress.getLocalHost().getHostAddress());
application.setDefaultProperties(properties);
application.run(args);
}
}
Undertow started:- o.s.b.w.e.u.UndertowServletWebServer : Undertow started on port(s) 55646 (http) with context path '' as printed in console
Expected result:- ip:port=xx.xx.x.1x1:55646
Actual result:- ip:port=xx.xx.x.1x1:0

Passing in the port number 0 is a trick that the Java core ServerSocket class can do. Undertow isn't aware of this; it just assumes that the port is always fixed. So there's no official API to read the port number that is actually used; but if you find the Undertow object, you can do:
// assuming you only have one:
ListenerInfo listenerInfo = undertow.getListenerInfo().iterator().next();
InetSocketAddress socketAddress = (InetSocketAddress) listenerInfo.getAddress();
URI uri = URI.create(listenerInfo.getProtcol() + "://" + socketAddress.getHostString() + ":" + socketAddress.getPort());
HTH

Related

Error creating bean with name 'primeLocateCometDService' when spring boot 1.5.19 with CometD 3.0.9 and tomcat 8.5.37

I just upgrade a tomcat project to spring boot project with CometD. Here are two services classes named: PrimeLocateCometDService & AbstractRealtimeCometDPublishService
When I start the project, error occurred.
public AbstractRealtimeCometDPublishService(BayeuxServer bayeuxServer, String sessionName) {
this.bayeuxServer = bayeuxServer;
this.localSession = bayeuxServer.newLocalSession(sessionName);
this.localSession.handshake();
}
Handshake will throw nullpointerexception.
package com.citi.pf.realtime;
import javax.servlet.ServletContext;
import javax.servlet.ServletRegistration;
import org.cometd.annotation.AnnotationCometDServlet;
import org.cometd.bayeux.server.BayeuxServer;
import org.cometd.server.BayeuxServerImpl;
import org.cometd.server.transport.JSONPTransport;
import org.cometd.server.transport.JSONTransport;
import org.cometd.websocket.server.WebSocketTransport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration;
import org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.boot.web.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.ImportResource;
import com.citi.pf.portal.lib.util.ENVUtils;
#SpringBootApplication
#ImportResource({
//webapp
"classpath:META-INF/realtime/pf-realtime-webapp-context.xml",
"classpath:WEB-INF/realtime/comet-config.xml",
"classpath:WEB-INF/realtime/webmvc-config.xml",
"classpath:pfGFIConfigSrvc.xml",
//core
"classpath:META-INF/realtime/pf-realtime-core-context.xml",
//security
"classpath:META-INF/realtime/pf-realtime-security-context.xml",
//prime-locate
"classpath:META-INF/realtime/prime-locate-integration-context.xml",
"classpath:META-INF/realtime/prime-locate-jndi-context.xml",
//prime-locate-cometd
"classpath:META-INF/realtime/prime-locate-cometd-integration-context.xml",
"classpath:META-INF/realtime/prime-locate-cometd-jndi-context.xml",
//prime-notification
"classpath:META-INF/realtime/prime-notification-cometd-integration-context.xml",
"classpath:META-INF/realtime/prime-notification-cometd-jndi-context.xml",
//prime-query
"classpath:META-INF/realtime/prime-query-integration-context.xml",
"classpath:META-INF/realtime/prime-query-jndi-context.xml",
//prime-wire
"classpath:META-INF/realtime/prime-wire-integration-context.xml",
"classpath:META-INF/realtime/prime-wire-jndi-context.xml"
})
#ServletComponentScan
#ComponentScan
#EnableAutoConfiguration(exclude= {
DataSourceAutoConfiguration.class,
JmsAutoConfiguration.class,
MongoAutoConfiguration.class,
MongoDataAutoConfiguration.class,
MultipartAutoConfiguration.class,
SecurityAutoConfiguration.class,
SecurityAutoConfiguration.class,
FallbackWebSecurityAutoConfiguration.class,
OAuth2AutoConfiguration.class})
public class PFRealtimeServicesApplication extends SpringBootServletInitializer implements ServletContextInitializer{
private static final Logger logger = LoggerFactory.getLogger(PFRealtimeServicesApplication.class);
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(PFRealtimeServicesApplication.class);
}
public static void main(String[] args) {
ENVUtils.registerEnvName("env");
ENVUtils.registerRunningSystem("REALTIME");
logger.info("Enter Realtime services application.");
SpringApplication.run(PFRealtimeServicesApplication.class,args);
}
#Override
public void onStartup(ServletContext servletContext) {
ServletRegistration.Dynamic cometdServlet = servletContext.addServlet("cometd", AnnotationCometDServlet.class);
cometdServlet.addMapping("/cometd/*");
cometdServlet.setAsyncSupported(true);
cometdServlet.setLoadOnStartup(1);
//cometdServlet.setInitParameter("PrimeLocateCometDService", PrimeLocateCometDService.class.getName());
//cometdServlet.setInitParameter("PrimeNotificationCometDService", PrimeNotificationCometDService.class.getName());
}
#Bean
protected ServletContextInitializer servletInitializer() {
return servletContext -> servletContext.setAttribute(BayeuxServer.ATTRIBUTE, bayeuxServer(servletContext));
}
#Bean
#DependsOn("servletInitializer")
protected BayeuxServer bayeuxServer(ServletContext servletContext) {
BayeuxServerImpl bean = new BayeuxServerImpl();
bean.setTransports(new WebSocketTransport(bean), new JSONTransport(bean), new JSONPTransport(bean));
servletContext.setAttribute(BayeuxServer.ATTRIBUTE, bean);
bean.setOption(ServletContext.class.getName(), servletContext);
bean.setOption("ws.cometdURLMapping", "/cometd/*");
return bean;
}
}
Hope the PrimeLocateCometDService Bean can be created successfully.
My project use Spring Boot 1.5.19 with CometD 3.0.9 and Tomcat 8.5.37.
(1) #ImportResource import all the xml files to the Application.java
(2) Remove (in xml file)
<bean id="websocketTransport" class="org.cometd.websocket.server.WebSocketTransport">
<constructor-arg ref="bayeux" />
</bean>
(3)Application extends SpringBootServletInitializer implements ServletContextInitializer
#Override
public void onStartup(ServletContext servletContext) {
ServletRegistration.Dynamic cometdServlet = servletContext.addServlet("cometd",
AnnotationCometDServlet.class);
cometdServlet.addMapping("/cometd/*");
cometdServlet.setAsyncSupported(true);
cometdServlet.setLoadOnStartup(1);
}
(4)pom.xml
Remove:
<dependency>
<groupId>org.cometd.java</groupId>
<artifactId>cometd-java-websocket-javax-server</artifactId>
<version>3.0.9</version>
</dependency>
Add:
<dependency>
<groupId>org.cometd.java</groupId>
<artifactId>cometd-java-websocket-javax-server</artifactId>
<version>3.0.9</version>
</dependency>
These are all I think will impact CometD with Spring Boot. Hope these will help someone.

Asserting log messages using Mockito, TestNG and Log4j2

I've been following asserting-log-messages-with-log4j2-and-mockito to write TestNG test to test logging for Log4j2. Most of what is written in the post seems to work. However, when I'm running my test I'm getting:
Wanted but not invoked:
appender.append(<Capturing argument>);
-> at LoggingTest.test(LoggingTest.java:105)
However, there were exactly 2 interactions with this mock:
appender.getName();
-> at org.apache.logging.log4j.core.config.AbstractConfiguration.addAppender(AbstractConfiguration.java:603)
appender.getName();
-> at org.apache.logging.log4j.core.config.AppenderControl.<init>(AppenderControl.java:51)
My TestNGclass is:
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.mockito.ArgumentCaptor;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class LoggingTest {
private Appender appender;
private ArgumentCaptor<LogEvent> captor;
#BeforeMethod
public void setUp() {
appender = mock(Appender.class);
captor = ArgumentCaptor.forClass(LogEvent.class);
reset(appender);
when(appender.getName()).thenReturn("Appender");
when(appender.isStarted()).thenReturn(true);
when(appender.isStopped()).thenReturn(false);
LoggerContext context = (LoggerContext)LogManager.getContext();
Configuration config = context.getConfiguration();
config.addAppender(appender);
LoggerConfig rootConfig = config.getRootLogger();
rootConfig.setLevel(Level.INFO);
rootConfig.addAppender(appender, Level.INFO, null);
context.updateLoggers();
}
#Test
public void test() {
LogManager.getLogger().info("testing");
verify(appender).append(captor.capture());
LogEvent logEvent = captor.getValue();
assertThat(logEvent.getMessage()).isEqualTo("test");
}
}
I've been looking at this for a while and have not been able to find a my error. Could someone point me in the right direction?

How to exclude some endpoints from server.servlet.path configuration?

I have Spring Boot application with a single index.html page.
I need to have server.servlet.path=/api setting.
In order to get index.html I have to go localhost:8080/api/ becase of my setting described above.
I want to be able to get index.html by localhost:8080/ and any else endpoints by localhost:8080/api/**.
How can I do it?
Thanks
Once you configured server.servlet.path=/api, DispatcherServlet is going to handle only request matching URL Patterns /api/**.
One way to achieve your requirement is to use a plain Servlet.
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.StreamUtils;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.Charset;
#WebServlet(urlPatterns = {"/"})
public class RootServlet extends HttpServlet {
#Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException {
ClassPathResource resource = new ClassPathResource("static/index.html");
String content = StreamUtils.copyToString(resource.getInputStream(), Charset.defaultCharset() );
resp.getWriter().write(content);
}
}
Now you can register the Servlet using #ServletComponentScan annotation. Assuming you put RootServlet in com.myapp.servlets package:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
#SpringBootApplication
#ServletComponentScan(basePackages = "com.myapp.servlets")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
For more info see https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-add-a-servlet-filter-or-listener

Configuring MessageTransformer for ActiveMQ/Stomp in SpringBoot

I have a springboot project with ActiveMQ and Stomp.
I want every Message sent or received to run through a MessageTransformer, to do specific serialization/deserialization stuff.
The Stomp Message exchange with my WebApp works, but the installed MessageTransformer is never called.
Does anybody have an idea what could be wrong? Thank you much!
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.MessageTransformer;
import org.apache.activemq.broker.BrokerFactory;
import org.apache.activemq.broker.BrokerService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Value("${stomp.port}") // 61616
int stompPort;
#Bean
public ActiveMQConnectionFactory activeMQConnectionFactory() {
ActiveMQConnectionFactory activeMQConnectionFactory =
new ActiveMQConnectionFactory("vm://localhost");
// Install Message Converter ## But does not work ##
MessageTransformer messageTransformer = new ClientMessageTransformer();
activeMQConnectionFactory.setTransformer(messageTransformer);
return activeMQConnectionFactory;
}
#Bean
public BrokerService brokerService() throws Exception {
BrokerService brokerService =
BrokerFactory.createBroker(String.format(
"broker:(stomp://localhost:%d)" +
"?persistent=false&useJmx=false&useShutdownHook=true",
stompPort));
return brokerService;
}

embedded tomcat valve spring boot

I'm trying to configure the LogbackValve for getting access logs in case my Spring Boot based web application is running from embedded Tomcat. Following is the code for configuration:
import javax.servlet.Servlet;
import org.apache.catalina.startup.Tomcat;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import ch.qos.logback.access.tomcat.LogbackValve;
#Configuration
public class EmbeddedTomcatConfigurator {
#Bean
#ConditionalOnClass({ Servlet.class, Tomcat.class })
#ConditionalOnBean(value = LogbackValve.class)
public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory(LogbackValve logbackValve) {
TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory();
factory.addContextValves(logbackValve);
return factory;
}
#Bean
#ConditionalOnProperty(name = "embedded.tomcat.logback.access.config.path")
public LogbackValve logbackValve(#Value("${embedded.tomcat.logback.access.config.path:}") String fileName) {
LogbackValve logbackValve = new LogbackValve();
logbackValve.setFilename(fileName);
return logbackValve;
}
}
However, everytime I start the application using "mvn spring-boot:run" in debug mode, I see logs saying, "LogbackValve not found" when trying to create instance of "tomcatEmbeddedServletContainerFactory" bean. However, another log statement indicates creation of this bean. Due to this, it always initializes the bean defined in the auto-configuration class "org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration".
For now, I've modified my class as :
import javax.servlet.Servlet;
import org.apache.catalina.startup.Tomcat;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import ch.qos.logback.access.tomcat.LogbackValve;
#Configuration
public class EmbeddedTomcatConfigurator {
#Bean
#ConditionalOnClass({ Servlet.class, Tomcat.class })
#ConditionalOnProperty(name = "embedded.tomcat.logback.access.config.path")
public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory(#Value("${embedded.tomcat.logback.access.config.path:}") String logbackAccessPath) {
TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory();
factory.addContextValves(getLogbackValve(logbackAccessPath));
return factory;
}
private LogbackValve getLogbackValve(String fileName) {
LogbackValve logbackValve = new LogbackValve();
logbackValve.setFilename(fileName);
return logbackValve;
}
}
I've already asked this question on Git and it has been resolved. But, here, the point I'm trying to bring up is, why the #ConditionalOnBean(value = LogbackValve.class) isn't detecting the bean, which has been defined as well.

Resources