Search code examples
javamavenembedded-jettycustom-error-pages

Jetty 11.0.11 - 404 on html file in \src\main\webapp\static - maven embedded fat jar


I have a file in my Netbeans 14 Jetty 11.0.11 project at \src\main\webapp\static\index.html that I want to serve to web-browsers using Jetty.

Here is my pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>verishare</groupId>
    <artifactId>verdi</artifactId>
    <version>12-JDK17</version>
    <packaging>jar</packaging>

    <name>verdi</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <jettyVersion>11.0.11</jettyVersion>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.21</version>
        </dependency>                                       
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <!--<version>2.11.0</version>-->
            <version>2.17.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <!--<version>2.11.0</version>-->
            <version>2.17.1</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        
        <dependency>
            <groupId>org.eclipse.jetty.websocket</groupId>
            <artifactId>websocket-jetty-server</artifactId>
            <version>${jettyVersion}</version>
        </dependency>        
        <dependency>
            <groupId>org.eclipse.jetty.websocket</groupId>
            <artifactId>websocket-jetty-client</artifactId>
            <version>${jettyVersion}</version>
        </dependency>


        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-server</artifactId>
            <version>${jettyVersion}</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-servlet</artifactId>
            <version>${jettyVersion}</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-webapp</artifactId>
            <version>${jettyVersion}</version>
        </dependency>        
        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-annotations</artifactId>
            <version>${jettyVersion}</version>
        </dependency>        
        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>apache-jsp</artifactId>
            <version>${jettyVersion}</version>
        </dependency>
        
        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>apache-jstl</artifactId>
            <version>11.0.0</version>        
        </dependency>
        
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.2.4</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish</groupId>
            <artifactId>javax.json</artifactId>
            <version>1.0.4</version>
            <type>jar</type>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-dbcp2</artifactId>
            <!--<version>2.0.1</version>-->
            <version>2.5.0</version>
            <type>jar</type>
        </dependency>
        <dependency>
            <groupId>postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <version>9.1-901-1.jdbc4</version>
        </dependency>
        <!--
        <dependency>
            <groupId>org.xerial</groupId>
            <artifactId>sqlite-jdbc</artifactId>
            <version>3.8.6</version>
        </dependency>
        -->
        <dependency>
            <groupId>com.microsoft.sqlserver</groupId>
            <artifactId>mssql-jdbc</artifactId>
            <version>6.4.0.jre8</version>
            <!--<scope>test</scope>-->
        </dependency>
        <dependency>
            <groupId>org.mariadb.jdbc</groupId>
            <artifactId>mariadb-java-client</artifactId>
            <!--<version>1.1.8</version>-->
            <version>2.7.4</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.1</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.core</groupId>
            <artifactId>jersey-server</artifactId>
            <version>2.22.1</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.containers</groupId>
            <artifactId>jersey-container-servlet-core</artifactId>
            <version>2.22.1</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.media</groupId>
            <artifactId>jersey-media-multipart</artifactId>
            <version>2.22.1</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.media</groupId>
            <artifactId>jersey-media-json-jackson</artifactId>
            <version>2.22.1</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.containers</groupId>
            <artifactId>jersey-container-jetty-http</artifactId>
            <version>2.22.1</version>
        </dependency>
        <dependency>
            <groupId>org.reflections</groupId>
            <artifactId>reflections</artifactId>
            <version>0.9.10</version>
        </dependency>
        <dependency>
            <groupId>com.jcraft</groupId>
            <artifactId>jsch</artifactId>
            <version>0.1.53</version>
        </dependency>
        <dependency>
            <groupId>ie.corballis</groupId>
            <artifactId>sox-java</artifactId>
            <version>1.0.1</version>
        </dependency>
        <dependency>
            <groupId>com.sun.mail</groupId>
            <artifactId>javax.mail</artifactId>
            <version>1.5.0</version>
            <type>jar</type>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>5.3.0</version>
            <type>jar</type>
        </dependency>        
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.11.3</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.11.3</version>
        </dependency>       
        <!-- Required so Verdi can compile in JDK's higher than 1.8 -->         
        <dependency> 
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.3.1</version>
        </dependency>        
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <!--<scope>provided</scope>-->
        </dependency>        
    </dependencies>
    <build>
        <plugins>            
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId> 
                <version>2.5</version>       
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <mainClass>verishare.App</mainClass>
                        </manifest>
                    </archive>
                </configuration>        
            </plugin>
            
            <plugin>                
                <artifactId>maven-assembly-plugin</artifactId>
                <version>2.5.2</version>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>verishare.App</mainClass>
                        </manifest>
                    </archive>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>  <!--this is used for inheritance merges-->
                        <phase>package</phase>  <!--bind to the packaging phase -->
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>            
        </plugins>    
        <resources>
            <resource>
                <directory>src/main/webapp</directory>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
            </resource>
        </resources>
    </build>
</project>

Here's how I start Jetty:

public boolean startJetty(Server server) throws Exception, InterruptedException {
        boolean retVal = false;

        try {            
            server = new Server(AppSettings.getJettyServerPort());

            jettyServer = server;       

            server.setAttribute("org.eclipse.jetty.server.Request.maxFormContentSize", -1);                      

            String webDir = this.getClass().getClassLoader().getResource("static").toExternalForm();            

            SecurityHandler basicSecurity = getBasicAuthHandler("abc", "def");            

            WebAppContext waContext = new WebAppContext(webDir, "/");            
            waContext.setInitParameter("org.eclipse.jetty.servlet.Default.dirAllowed", "false");
            waContext.setSecurityHandler(basicSecurity);  
            
            waContext.setAttribute("org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern", ".*/[^/]*servlet-api-[^/]*\\.jar$|.*/javax.servlet.jsp.jstl-.*\\.jar$|.*/[^/]*taglibs.*\\.jar$");                            
            
            ServletContextHandler servletContext = new ServletContextHandler(ServletContextHandler.SESSIONS);
            servletContext.setMaxFormKeys(1000000000);
            servletContext.setContextPath("/api");
            servletContext.addServlet(new ServletHolder(new WebApiServlet()), "/*");

            String cn = "";

            Reflections rf = new Reflections("proj");
            Set<Class<?>> classWithPath = rf.getTypesAnnotatedWith(javax.ws.rs.Path.class);
            for (Class c : classWithPath) {
                if (cn.length() > 0) {
                    cn += ";";
                }
                cn += c.getCanonicalName();

                localLogger.info((String) logEntryRefNumLocal.get() + "Adding class: " + c.getCanonicalName());
            }
                        
           
            HandlerList handlers = new HandlerList();            
            handlers.setHandlers(new Handler[]{servletContext, waContext});

            server.setHandler(handlers);
            
            HttpConfiguration httpConfig = new HttpConfiguration();
            httpConfig.setSendServerVersion(false);
            HttpConnectionFactory httpFactory = new HttpConnectionFactory(httpConfig);
            ServerConnector httpConnector = new ServerConnector(server, httpFactory);
            httpConnector.setPort(AppSettings.getJettyServerPort());
            server.setConnectors(new Connector[]{httpConnector});

            server.setStopAtShutdown(true);
            server.setStopTimeout(0x2710L);

            server.start();

        } catch (InterruptedException iex) {
            localLogger.error((String) logEntryRefNumLocal.get() + "InterrupedException in WebHost.java startJetty method.", iex);

            retVal = false; 

            throw iex;
        } catch (RuntimeException rex) {
            localLogger.error((String) logEntryRefNumLocal.get() + "Runtime  exception in WebHost.java startJetty method.", rex);

            retVal = false;

            throw rex;
        } catch (Exception ex) {            
            localLogger.error((String) logEntryRefNumLocal.get() + "General exception in WebHost.java startJetty method.", ex);

            retVal = false;

            throw ex;
        }

        return retVal;
    }

If I run the above in Windows inside Netbeans 14, I can see the content of the .html file in /usr/src/webapp/static by visiting

http://127.0.0.1:8086/index.html

However, if I run the .jar in Ubuntu under the same version of the official Oracle JDK (JDK 17) using

/usr/lib/jvm/jdk-17/bin/java -Djavax.net.ssl.trustStore=/usr/lib/jvm/java-17-openjdk-amd64/lib/security/cacerts -cp /usr/src/verdi/verdi-12-JDK17-jar-with-dependencies.jar verishare.App

and I try to visit

http://172.16.1.33/index.html

Jetty 11.0.11 replies

HTTP ERROR 404 Not Found
URI:    /
STATUS: 404
MESSAGE:    Not Found
SERVLET:    org.eclipse.jetty.servlet.ServletHandler$Default404Servlet-726a17c4

What am I doing wrong that the above works in Windows inside NetBeans 14 with JDK 17, but it gives a 404 error with the compiled JAR in Linux (Ubuntu 20.04 LTS) with JDK 17?

Thanks

EDIT: I have refined the issue further. When run under Windows inside NetBeans 14 in JDK 17, in the code above, the "webDir" becomes

file:/D:/Projects/verdi_2/target/classes/static

When run under Linux in JDK 17, in the code above, the "webDir" becomes

jar:file:/usr/src/verdi/verdi-12-JDK17-jar-with-dependencies.jar!/static

It appears that the Linux versions of neither OpenJDK 17 nor the Oracle JDK 17, can interpret the above jar:file reference, leading to the 404.

How can this be fixed, though??? It is as if jar:file is completely opaque to Linux JREs of either flavor. No exceptions are raised, and I have confirmed over and over that the directory /static exists in the .JAR concerned.

EDIT: As I understand the answers below, this is then clearly invalid and the wrong way to access a directory full of html, Javascript, and images in a Jetty webAppContext that Jetty needs to serve to browser:

jar:file:/usr/src/verdi/verdi-12-JDK17-jar-with-dependencies.jar!/static

I still have no idea how to correct this, but can someone maybe comment: if the above is INCORRECT, what would a correct reference look like for a /static folder in the root of a JAR file?

EDIT: I have confirmed that if I regress the Jetty version back far enough to older versions, the reference jar:file:/usr/src/verdi/verdi-12-JDK17-jar-with-dependencies.jar!/static -does- start working and functions perfectly, and the older Jetty instance can serve the resources in the /static folder to any web-browsers that request them

EDIT: GitHub issue opened

https://github.com/eclipse/jetty.project/issues/8549


Solution

  • The answer to this question is in the long series of posts beneath this GitHub issue for jetty:

    https://github.com/eclipse/jetty.project/issues/8549

    In essence, eventually I had to first clean up my Maven pom.xml (see this thread for the discussion and for links to a pom.xml example that is compliant with Maven Shade plugin and Jetty 11.0.11 requirements and standards) then at the end of the day hardcode a link to the JAR file to find the HTML, JS, etc. resources Jetty was to serve out as a webpage. Also put in a conditional where, on compiling, I need to specify if the code will run "in-IDE" (in my case, Netbeans 14) or "in-JAR" - e. g. in a detached JRE elsewhere than the Netbeans 14 IDE.

    Also dropped using the Jetty WebAppContext class and started rendering web content out of a normal ServletContextHandler.

    Hopefully this may help someone upgrading Jetty from Jetty 9.xxx to 11 and finding that it all falls apart.

    For details as to why they changed so much, see the GitHub link (the last few entries are apropos.)

    The github discussion also contains full working source code (startJettyc method) that solved the issue of getting a 404 in a detached, non-IDE modality where the JAR was being run in an JRE separate from an IDE.

    Stefan