tl;dr : Pourquoi cela fonctionne-t-il localement mais pas lorsque je déploie mon projet App Engine ?
J'essaie de créer une application web basée sur des servlets en utilisant la version Java 11 d'App Engine. Je mets à jour quelques projets de Java 8 à Java 11 comme suit ce guide . J'utilise également ce guide et cet exemple . Mon objectif est d'utiliser Jetty pour exécuter une application web très simple qui sert un seul fichier HTML statique et un seul fichier servlet dans App Engine.
Mon application web fonctionne correctement lorsque je l'exécute localement :
mvn clean install
mvn exec:java -Dexec.args="target/app-engine-hello-world-1.war"
Lorsque j'exécute ces commandes, mes index.html
et l'URL de mon servlet fonctionne correctement.
Mais lorsque je le déploie sur mon site réel :
mvn package appengine:deploy
...la commande réussit, mais lorsque je navigue vers mon URL live, j'obtiens cette erreur à la fois pour le fichier HTML et pour l'URL du servlet : "Error: Server Error. The server encountered an error and could not complete your request. Please try again in 30 seconds."
Si je regarde les journaux dans la console Cloud, je vois cette erreur :
Error: Could not find or load main class io.happycoding.Main
Caused by: java.lang.ClassNotFoundException: io.happycoding.Main
Il y a quelque chose qui cloche dans ma configuration, mais je ne vois rien d'anormal.
Voici les fichiers de mon projet :
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>io.happycoding</groupId>
<artifactId>app-engine-hello-world</artifactId>
<version>1</version>
<packaging>war</packaging>
<properties>
<!-- App Engine currently supports Java 11 -->
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<failOnMissingWebXml>false</failOnMissingWebXml>
</properties>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>9.4.31.v20200723</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>
<version>9.4.31.v20200723</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
<version>9.4.31.v20200723</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-annotations</artifactId>
<version>9.4.31.v20200723</version>
<type>jar</type>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>io.happycoding.Main</mainClass>
</configuration>
</plugin>
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>appengine-maven-plugin</artifactId>
<version>2.2.0</version>
<configuration>
<projectId>happy-coding-gcloud</projectId>
<version>1</version>
</configuration>
</plugin>
</plugins>
</build>
</project>
src/main/appengine/app.yaml
runtime: java11
entrypoint: 'java -cp "*" io.happycoding.Main app-engine-hello-world-1.war'
src/main/java/io/happycoding/Main.java
package io.happycoding;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.webapp.Configuration.ClassList;
import org.eclipse.jetty.webapp.WebAppContext;
import io.happycoding.servlets.HelloWorldServlet;
/** Simple Jetty Main that can execute a WAR file when passed as an argument. */
public class Main {
public static void main(String[] args) throws Exception {
if (args.length != 1) {
System.err.println("Usage: need a relative path to the war file to execute");
System.exit(1);
}
System.setProperty("org.eclipse.jetty.util.log.class", "org.eclipse.jetty.util.log.StrErrLog");
System.setProperty("org.eclipse.jetty.LEVEL", "INFO");
Server server = new Server(8080);
WebAppContext webapp = new WebAppContext();
webapp.setContextPath("/");
webapp.setWar(args[0]);
ClassList classlist = ClassList.setServerDefault(server);
// Enable Annotation Scanning.
classlist.addBefore(
"org.eclipse.jetty.webapp.JettyWebXmlConfiguration",
"org.eclipse.jetty.annotations.AnnotationConfiguration");
server.setHandler(webapp);
server.join();
}
}
src/main/webapp/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Google Cloud Hello World</title>
</head>
<body>
<h1>Google Cloud Hello World</h1>
<p>This is a sample HTML file. Click <a href="http://stackoverflow.com/hello">here</a> to see content served from a servlet.</p>
<p>Learn more at <a href="https://happycoding.io">HappyCoding.io</a>.</p>
</body>
</html>
src/main/java/io/happycoding/servlets/HelloWorldServlet.java
package io.happycoding.servlets;
import java.io.IOException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/hello")
public class HelloWorldServlet extends HttpServlet {
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setContentType("text/html;");
response.getOutputStream().println("<h1>Hello world!</h1>");
}
}
Je suppose que quelque chose ne va pas dans la façon dont je configure le classpath du site live, mais je ne vois rien de manifestement erroné.
Avec la packaging
propriété en pom.xml
fixé à war
Je reçois un .war
avec ce contenu :
index.html
META-INF/MANIFEST.MF
META-INF/maven/io.happycoding/app-engine-hello-world/pom.properties
META-INF/maven/io.happycoding/app-engine-hello-world/pom.xml
WEB-INF/classes/io/happycoding/Main.class
WEB-INF/classes/io/happycoding/servlets/HelloWorldServlet.class
WEB-INF/classes/lib/asm-7.3.1.jar
WEB-INF/classes/lib/asm-analysis-7.3.1.jar
WEB-INF/classes/lib/asm-commons-7.3.1.jar
WEB-INF/classes/lib/asm-tree-7.3.1.jar
WEB-INF/classes/lib/javax.annotation-api-1.3.jar
WEB-INF/classes/lib/javax.servlet-api-4.0.1.jar
WEB-INF/classes/lib/jetty-annotations-9.4.31.v20200723.jar
WEB-INF/classes/lib/jetty-http-9.4.31.v20200723.jar
WEB-INF/classes/lib/jetty-io-9.4.31.v20200723.jar
WEB-INF/classes/lib/jetty-jndi-9.4.31.v20200723.jar
WEB-INF/classes/lib/jetty-plus-9.4.31.v20200723.jar
WEB-INF/classes/lib/jetty-security-9.4.31.v20200723.jar
WEB-INF/classes/lib/jetty-server-9.4.31.v20200723.jar
WEB-INF/classes/lib/jetty-servlet-9.4.31.v20200723.jar
WEB-INF/classes/lib/jetty-util-9.4.31.v20200723.jar
WEB-INF/classes/lib/jetty-webapp-9.4.31.v20200723.jar
WEB-INF/classes/lib/jetty-xml-9.4.31.v20200723.jar
Si je modifie le packaging
propriété en pom.xml
a jar
puis j'obtiens un .jar
avec ce contenu :
io/happycoding/Main.class
io/happycoding/servlets/HelloWorldServlet.class
META-INF/MANIFEST.MF
META-INF/maven/io.happycoding/app-engine-hello-world/pom.properties
META-INF/maven/io.happycoding/app-engine-hello-world/pom.xml
Et j'obtiens cette erreur dans les logs du site live à la place :
Error: Unable to initialize main class io.happycoding.Main
Caused by: java.lang.NoClassDefFoundError: org/eclipse/jetty/server/Handler
J'ai l'impression de progresser, mais je reçois aussi des erreurs 404 sur mon serveur live, et je me sens donc plutôt coincé.
Que dois-je modifier dans ma configuration ci-dessus pour qu'elle fonctionne à la fois localement et sur mon serveur live ?
Editer : Je peux voir les fichiers suivants dans le débogueur d'App Engine :
J'ai essayé d'ajouter ceci à mon pom.xml
archivo:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.1.2</version>
<executions>
<execution>
<id>copy</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>
${project.build.directory}/appengine-staging
</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
Ensuite, je vois ces fichiers dans le débogueur d'App Engine :
Mais j'obtiens toujours la même erreur.
Je pense que le problème est dû à mon Main
à l'intérieur d'une classe .war
qui n'a aucun effet sur le chemin d'accès (classpath), ce qui explique pourquoi il est introuvable.
Comment préparer mon projet pour qu'il fonctionne localement et sur mon serveur live ?