Behavior differ between running application in IntelliJ and from jar - maven

I have written an application using Jetty as a server. I wish to package my application into a .jar, so I may put it in a Docker container later on.
When I run my application through IntelliJ, everything seems to be working as it should, and I can reach my index.html from localhost:2222. However, when I package my application (using mvn clean compile assembly:single) and run my jar by typing java -jar myapplication.jar in the shell, my index.html on localhost:2222 returns HTTP Error 404.
My App.java:
public class App {
public static void main( String[] args ) throws Exception {
ResourceConfig config = new ResourceConfig();
config.packages("mypackage");
ServletHolder servlet = new ServletHolder(new ServletContainer(config));
Server server = new Server(2222);
ResourceHandler handler = new ResourceHandler();
handler.setBaseResource(org.eclipse.jetty.util.resource.Resource
.newClassPathResource("index.html"));
ServletContextHandler context = new ServletContextHandler(server, "/*");
context.addServlet(servlet, "/*");
HandlerList handlers = new HandlerList();
handlers.addHandler(handler);
handlers.addHandler(context);
server.setHandler(handlers);
try {
server.start();
server.join();
} finally {
server.destroy();
}
}
}
When running the application in IntelliJ and as jar, I get the following output (may be of interest?):
2015-11-06 09:25:00.991:INFO::main: Logging initialized #279ms
2015-11-06 09:25:01.238:WARN:oejsh.ContextHandler:main: o.e.j.s.ServletContextHandler#490d6c15{/,null,null} contextPath ends with /*
2015-11-06 09:25:01.238:WARN:oejsh.ContextHandler:main: Empty contextPath
2015-11-06 09:25:01.250:INFO:oejs.Server:main: jetty-9.2.3.v20140905
Nov 06, 2015 9:25:01 AM org.glassfish.jersey.server.ApplicationHandler initialize
INFO: Initiating Jersey application, version Jersey: 2.7 2014-03-12 18:11:31...
2015-11-06 09:25:02.294:INFO:oejsh.ContextHandler:main: Started o.e.j.s.ServletContextHandler#490d6c15{/,null,AVAILABLE}
2015-11-06 09:25:02.306:INFO:oejs.ServerConnector:main: Started ServerConnector#268f106e{HTTP/1.1}{0.0.0.0:2222}
2015-11-06 09:25:02.306:INFO:oejs.Server:main: Started #1688ms
I am not posting my file of Jersey-methods, since I don't think it is relevant for my question.
So, what is the magic of packaging this up to a jar that can recognize my index.html at localhost:2222? Is it some environment settings in IntelliJ?
Cheers

First of all, don't mix ResourceHandler and ServletContextHandler, you are duplicating static content effort (3 times over with Jersey in play) and will get unreliable results.
Drop the ResourceHandler, set the required ServletContextHandler.setBaseResource(), and add a DefaultServlet to your context.
// Figure out what path to serve content from
ClassLoader cl = App.class.getClassLoader();
// We look for a file, as ClassLoader.getResource() is not
// designed to look for directories (we resolve the directory later)
URL f = cl.getResource("static-root/index.html");
if (f == null)
{
throw new RuntimeException("Unable to find resource directory");
}
// Resolve file to directory
URI webRootUri = f.toURI().resolve("./").normalize();
System.err.println("Main Base Resource is " + webRootUri);
// Setup the basic application "context" for this application at "/"
// This is also known as the handler tree (in jetty speak)
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/");
context.setBaseResource(Resource.newResource(webRootUri));
server.setHandler(context);
// Jersey setup
ResourceConfig config = new ResourceConfig();
config.packages("mypackage");
context.addServlet(new ServletHolder(new ServletContainer(config)), "/*");
// Lastly, the default servlet for root content (always needed, to satisfy servlet spec)
// It is important that this is last.
ServletHolder holderDef = new ServletHolder("default",DefaultServlet.class);
holderDef.setInitParameter("dirAllowed","true");
context.addServlet(holderDef,"/");
Note: since you setup Jersey at the url-pattern /* you should know that Jersey is responsible for serving all static content, not Jetty.

Related

Returning a different Tomcat session ID based on URI in Spring

I'm very new to Spring + Tomcat and trying to learn it while working on an existing spring boot (v. 2.6.x) web app running on a Tomcat server (tomcat-embed-core v. 9.0.x).
The application serves URLs of this type:
\{customer}\path\to\resource
\{customer}\newapp\path\to\resource
I can see this reported on the ModelAndView controller annotated with the likes of PostMapping and GetMapping.
Now, the service keeps tracks of sessions using JSESSION cookies and generating session IDs using tomcat's standard org.apache.catalina.SessionIdGenerator.
I would like to programmatically change the format of the session identifier generated by tomcat, depending on the request being served.
Given the paths above, for instance, I'd like to have:
request to 1. above, should be generating session identifiers like 905A6892CB2C12F84A331F58A6A2C382
requests to newapp, i.e. 2. above, should be generating session identifiers like NEWAPP_905A6892CB2C12F84A331F58A6A2C382
The format of the session is irrelevant, but they must have a different prefix.
It seems that one way to achieve this would be to have two different contexts, each one with a different catalina Manager, which can be set using org.apache.catalina.Context#setManager.
I'm not able to define two different contexts because the root in the path is the variable {customer}, nor I'm able to dynamically inject the context using an implementation of org.apache.catalina.valves.ValveBase, as there is no easy way to create a delegated context, where the only difference WRT the base tomcat context is the Manager.
I've tried changing the root of the newapp paths to be like newapp\{customer}\path\to\resource and creating a new Context for newapp, but this fails if I try to override org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory#getWebServer like
#Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
Tomcat tomcat = new Tomcat();
File baseDir = createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
prepareContext(tomcat.getHost(), initializers);
final String documentRoot = getValidDocumentRoot().getAbsolutePath();
final Context ctx = tomcat.addContext("/newapp", documentRoot);
ctx.setManager(new NewAppManager());
return getTomcatWebServer(tomcat);
}
returning a 404 error - it looks like the application is not able to find the mapping for the path, even if I successfully changed the GetMapping to newapp\{customer}\path\to\resource.
Can anyone suggest the best way to achieve this and if what I'm trying even makes sense?
Thank you so much!
I was able to achieve the required outcome by adding a few lines in the getWebServer method.
Adding the whole method below, with comments, for clarity.
#Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
Tomcat tomcat = new Tomcat();
File baseDir = createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
customizeEngine(tomcat.getEngine());
prepareContext(tomcat.getHost(), initializers);
// This is the ROOT context generated by the embedded tomcat
final StandardContext rootCtx = (StandardContext) tomcat.getHost().findChild("");
// Both context will be sharing the same war file
final String documentRoot = getValidDocumentRoot().getAbsolutePath();
// Creating a new context for the "newapp, serving the "/newapp" path
final StandardContext newappCtx = (StandardContext) tomcat.addContext("/newapp", documentRoot);
// This turned out to be very important, sets the classloader of the context to delegate to the application classloader, exactly as ROOT ctx does
newappCtx.setParentClassLoader(rootCtx.getParentClassLoader());
newappCtx.setDelegate(true);
// This was needed in our specific case, the default cookie path would be "/newapp"
newappCtx.setSessionCookiePath("/");
// And finally setting the session manager, generating a new session identifier
newappCtx.setManager(new NewappSessionManager());
final WebServer ws = getTomcatWebServer(tomcat);
// This is the default dispatcher created by the embedded tomcat
final Servlet dispatcherServlet = ((StandardWrapper) rootCtx.findChild("dispatcherServlet")).getServlet();
// We're now adding the default servlet to the "newappCtx"
Tomcat.addServlet(newappCtx, "dispatcherServlet", dispatcherServlet);
// it will serve all paths under "/newapp"
newappCtx.addServletMappingDecoded("/*", "dispatcherServlet");
return ws;
}
....
// Simple manager for the new context
private static final class NewappSessionManager extends StandardManager {
#Override
protected String getNextSessionId() {
return "NEWAPP_" + super.getNextSessionId();
}
}

Jersey on embedded Jetty with hk2 locator not loading ResourceConfig

I am using Jetty as an embedded server for Jersey.
ServletHolder jerseyServletHolder = new ServletHolder(ServletContainer.class);
jerseyServletHolder.setInitOrder(1);
jerseyServletHolder.setInitParameter(
org.glassfish.jersey.server.ServerProperties.PROVIDER_PACKAGES,
"com.my.package");
webAppContext.addServlet(jerseyServletHolder, "/rest/*");
I have a ResourceConfig implementation:
#ApplicationPath("/rest")
public class MyResourceConfig extends ResourceConfig {
static{
System.out.println("ResourceConfig loaded");
// this never gets calls
}
#Inject
public MyResourceConfig(ServiceLocator serviceLocator, Properties serverProps) {
packages("com.my.package");
}
}
The problem is that when I launch, the MyResourceConfig class is never loaded.
If I add:
jerseyServletHolder.setInitParameter(
ServletProperties.JAXRS_APPLICATION_CLASS,
MyResourceConfig.class.getName());
then the ResourceConfig does get loaded.
Why isn't MyResoureConfig getting picked up based on the #ApplicationPath annotation?
You just need annotation / bytecode scanning enabled.
Start by putting jetty-annotations-<version>.jar (and transitive dependencies) into your project.
Then, in your code, after you create your org.eclipse.jetty.server.Server object, do this.
Server server = new Server(8080);
// Enable parsing of jndi-related parts of web.xml and jetty-env.xml
Configuration.ClassList classlist = Configuration.ClassList
.setServerDefault(server);
classlist.addAfter("org.eclipse.jetty.webapp.FragmentConfiguration",
"org.eclipse.jetty.plus.webapp.EnvConfiguration",
"org.eclipse.jetty.plus.webapp.PlusConfiguration");
// Enable annotation/bytecode scanning and ServletContainerInitializer usages
classlist.addBefore(
"org.eclipse.jetty.webapp.JettyWebXmlConfiguration",
"org.eclipse.jetty.annotations.AnnotationConfiguration");
WebAppContext webAppContext = createWebAppContext();
// ....
server.start();
That will enable the configurations needed to perform bytecode scanning and annotation scanning, along with enabling the ability to load any javax.servlet.ServletContainerInitializer found within your webapp, including the critical one from Jersey (org.glassfish.jersey.servlet.init.JerseyServletContainerInitializer)

Using a custom classloader with Spring's ComponentScan and PropertyPlaceholderConfigurer

I want to create multiple application contexts in my Tomcat application.
Some of these application contexts have the same package and class names, but they all refer to different jars.
For example:
application0 use service.jar, model.jar
application1 use service-a.jar, model-a.jar
application2 use service-b.jar, model-b.jar
application0 context is OK because is in orign project.
I reference some web page to custom application1, I use my custom classloader to start applicationContext.
File file0 = new File("D://git/project1/service-a.jar");
File file1 = new File("D://git/project1/modele-a.jar");
// convert the file to URL format
URL url0 = file0.toURI().toURL();
URL url1 = file1.toURI().toURL();
List<URL> urls = new LinkedList<>();
List<File> libs = listFilesForFolder(new File("D://protal//apache-tomcat-8.0.39//lib"));
for(File lib : libs) {
urls.add(lib.toURI().toURL());
}
urls.add(url1);
urls.add(url0);
final URLClassLoader customClassLoader = new URLClassLoader(urls.toArray(new URL[urls.size()]));
ClassPathXmlApplicationContext context1 = new ClassPathXmlApplicationContext("applicationContext.xml") {
protected void initBeanDefinitionReader(XmlBeanDefinitionReader reader)
{
super.initBeanDefinitionReader(reader);
reader.setValidationMode(XmlBeanDefinitionReader.VALIDATION_NONE);
reader.setBeanClassLoader(customClassLoader);
}
};
allApplicationContexts.add(context1);
The Spring contexts start OK, but they fail to create the component-scan bean, and PropertyPlaceholderConfigurer isn't working. Everything else seems correct.
I sure my config is correct because it works without the custom classloader. Libs contains all spring lib.
Is it possible to get this working with multiple Spring contexts?

Embedded Jetty Error The absolute uri: http://java.sun.com/jsp/jstl/core cannot be resolved

I'm using Embedded Jetty server to run my tests and Maven for build.
Following is the code used to start Jetty before tests.
System.out.println("Initializing Jetty Server...");
jettyServer = new Server(0);
WebAppContext webapp = new WebAppContext("src/main/webapp", "/testApp");
jettyServer.addHandler(webapp);
jettyServer.start();
int actualPort = jettyServer.getConnectors()[0].getLocalPort();
String baseUrl = "http://localhost:" + actualPort + "/testApp";
All the tests passes if I run it with 'Run as Junit Test'. No problems here.
But if I run it as Maven Test or Maven Install The tests fails withe following cause
Caused by: com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException: 500 /WEB-INF/pages/test1.jsp(3,62) PWC6188: The absolute uri: http://java.sun.com/jsp/jstl/core cannot be resolved in either web.xml or the jar files deployed with this application
I have the JSTL dependancy added in POM.xml. Please help to resolve this..
You didn't initialize and setup the environment in a way that is suitable for JSP use.
It requires a bunch of extra work.
You'll need to manipulate classloaders, setup some initializers, declare the javac implementation behavior, and even declare the jsp servlet handling. (missing any one of these and you'll be subject to the environment that you executed under, which is different in your 3 examples)
For a complete example maven project see https://github.com/jetty-project/embedded-jetty-jsp
public class Main
{
// Resource path pointing to where the WEBROOT is
private static final String WEBROOT_INDEX = "/webroot/";
public static void main(String[] args) throws Exception
{
int port = 8080;
LoggingUtil.config();
Log.setLog(new JavaUtilLog());
Main main = new Main(port);
main.start();
main.waitForInterrupt();
}
private static final Logger LOG = Logger.getLogger(Main.class.getName());
private int port;
private Server server;
private URI serverURI;
public Main(int port)
{
this.port = port;
}
public URI getServerURI()
{
return serverURI;
}
public void start() throws Exception
{
server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(port);
server.addConnector(connector);
URL indexUri = this.getClass().getResource(WEBROOT_INDEX);
if (indexUri == null)
{
throw new FileNotFoundException("Unable to find resource " + WEBROOT_INDEX);
}
// Points to wherever /webroot/ (the resource) is
URI baseUri = indexUri.toURI();
// Establish Scratch directory for the servlet context (used by JSP compilation)
File tempDir = new File(System.getProperty("java.io.tmpdir"));
File scratchDir = new File(tempDir.toString(),"embedded-jetty-jsp");
if (!scratchDir.exists())
{
if (!scratchDir.mkdirs())
{
throw new IOException("Unable to create scratch directory: " + scratchDir);
}
}
// Set JSP to use Standard JavaC always
System.setProperty("org.apache.jasper.compiler.disablejsr199","false");
// Setup the basic application "context" for this application at "/"
// This is also known as the handler tree (in jetty speak)
WebAppContext context = new WebAppContext();
context.setContextPath("/");
context.setAttribute("javax.servlet.context.tempdir",scratchDir);
context.setResourceBase(baseUri.toASCIIString());
context.setAttribute(InstanceManager.class.getName(), new SimpleInstanceManager());
server.setHandler(context);
// Add Application Servlets
context.addServlet(DateServlet.class,"/date/");
//Ensure the jsp engine is initialized correctly
JettyJasperInitializer sci = new JettyJasperInitializer();
ServletContainerInitializersStarter sciStarter = new ServletContainerInitializersStarter(context);
ContainerInitializer initializer = new ContainerInitializer(sci, null);
List<ContainerInitializer> initializers = new ArrayList<ContainerInitializer>();
initializers.add(initializer);
context.setAttribute("org.eclipse.jetty.containerInitializers", initializers);
context.addBean(sciStarter, true);
// Set Classloader of Context to be sane (needed for JSTL)
// JSP requires a non-System classloader, this simply wraps the
// embedded System classloader in a way that makes it suitable
// for JSP to use
ClassLoader jspClassLoader = new URLClassLoader(new URL[0], this.getClass().getClassLoader());
context.setClassLoader(jspClassLoader);
// Add JSP Servlet (must be named "jsp")
ServletHolder holderJsp = new ServletHolder("jsp",JspServlet.class);
holderJsp.setInitOrder(0);
holderJsp.setInitParameter("logVerbosityLevel","DEBUG");
holderJsp.setInitParameter("fork","false");
holderJsp.setInitParameter("xpoweredBy","false");
holderJsp.setInitParameter("compilerTargetVM","1.7");
holderJsp.setInitParameter("compilerSourceVM","1.7");
holderJsp.setInitParameter("keepgenerated","true");
context.addServlet(holderJsp,"*.jsp");
//context.addServlet(holderJsp,"*.jspf");
//context.addServlet(holderJsp,"*.jspx");
// Add Example of mapping jsp to path spec
ServletHolder holderAltMapping = new ServletHolder("foo.jsp", JspServlet.class);
holderAltMapping.setForcedPath("/test/foo/foo.jsp");
context.addServlet(holderAltMapping,"/test/foo/");
// Add Default Servlet (must be named "default")
ServletHolder holderDefault = new ServletHolder("default",DefaultServlet.class);
LOG.info("Base URI: " + baseUri);
holderDefault.setInitParameter("resourceBase",baseUri.toASCIIString());
holderDefault.setInitParameter("dirAllowed","true");
context.addServlet(holderDefault,"/");
// Start Server
server.start();
// Show server state
if (LOG.isLoggable(Level.FINE))
{
LOG.fine(server.dump());
}
// Establish the Server URI
String scheme = "http";
for (ConnectionFactory connectFactory : connector.getConnectionFactories())
{
if (connectFactory.getProtocol().equals("SSL-http"))
{
scheme = "https";
}
}
String host = connector.getHost();
if (host == null)
{
host = "localhost";
}
int port = connector.getLocalPort();
serverURI = new URI(String.format("%s://%s:%d/",scheme,host,port));
LOG.info("Server URI: " + serverURI);
}
public void stop() throws Exception
{
server.stop();
}
/**
* Cause server to keep running until it receives a Interrupt.
* <p>
* Interrupt Signal, or SIGINT (Unix Signal), is typically seen as a result of a kill -TERM {pid} or Ctrl+C
*/
public void waitForInterrupt() throws InterruptedException
{
server.join();
}
}
In the following repo: https://github.com/ericminio/learning-jetty
You can find:
The JstlTest that demoes Jetty serving jsp containing a c:forEach tag
The pom needed to remove the error message about resolving http://java.sun.com/jsp/jstl/core
Hope it helps

Embedded Jetty to test the spring application startup

What I am trying to do is, to have a test to startup the whole application to see if there's any error. But I want to use the applicationContext.xml from the /src/resource folder and not form test/resource. How can I do that in JUnit?
My application is big and a lot of people share the same codebase. So, I just wanted to have a quick test to see if the checkin can start up the application.
This is my simple code but it looks like it's missing some of the autowire stuff, that's why I want to use the xml files from /src/resource, so I don't have to maintain two locations.
My application is plain Spring MVC 3.0
#Test(enabled = false)
public void shouldStartupTheApp() throws Exception {
Server server = new Server();
SelectChannelConnector connector = new SelectChannelConnector();
connector.setPort(9999);
server.setConnectors(new Connector[] {connector});
Context context = new Context(server, "/", Context.SESSIONS);
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setContextConfigLocation("classpath:/test-applicationContext.xml");
ServletHolder servletHolder = new ServletHolder(dispatcherServlet);
context.addServlet(servletHolder, "/*");
server.start();
}
You can import your src/resources/filename.xml in other xml-file using
<import resource="classpath:/filename.xml" />

Resources