6 votes

App Engine Java 11 n'a pas pu trouver ou charger la classe principale sur le serveur live

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 :

app engine files

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 :

app engine files

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 ?

4voto

jccampanero Points 21211

Je pense que votre problème réside dans le fait que vous incluez l'élément Main dans la guerre elle-même, et App Engine n'est pas en mesure de la trouver.

Comme vous pouvez le voir dans le Guide de migration GCP , le Main est définie dans une dépendance externe nommée simple-jetty-main .

Avec l'exécution de la maven-dependency-plugin cette dépendance est copiée dans le appengine-staging ce qui le rend accessible à partir du chemin de classe Java.

C'est la raison pour laquelle le Main peut être trouvée dans l'exemple proposé dans le guide lors de l'exécution de la commande à partir de l'application app.yaml entrypoint :

entrypoint: 'java -cp "*" com.example.appengine.demo.jettymain.Main helloworld.war'

La solution consistera donc à inclure votre Main dans une autre bibliothèque, indépendamment du fichier war que vous devez déployer.

Vous pouvez peut-être créer une bibliothèque, comme le fait Google avec simple-jetty-main - que vous pouvez réutiliser dans vos projets GCP pour cette tâche.

À titre d'essai, pour confirmer ce point, vous pouvez utiliser la fonction simple-jetty-main elle-même (vous pouvez cloner le code nécessaire à partir de https://github.com/GoogleCloudPlatform/java-docs-samples/tree/master/appengine-java11/appengine-simple-jetty-main ). Installez-le, incluez la dépendance dans votre pom.xml , inclure également le maven-dependency-plugin et définissez votre entrypoint comme suit :

entrypoint: 'java -cp "*" com.example.appengine.demo.jettymain.Main app-engine-hello-world-1.war'

Pour vos commentaires, vous préférerez ne pas avoir la séparation entre les Main et le reste du code.

Pour répondre à cette exigence, nous devons d'abord modifier le Main afin que Jetty puisse servir HelloWorldSevlet et le contenu statique. Le code est en fait très similaire à celui que vous avez fourni. Veuillez excuser la simplicité de la configuration, elle est basée sur web.xml Si nécessaire, d'autres développements peuvent être effectués pour traiter les annotations ou tout autre élément jugé approprié :

package io.happycoding;

import java.net.URL;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.webapp.WebAppContext;

public class Main {

  public static final String WEBAPP_RESOURCES_LOCATION = "META-INF/resources";

  public static void main(String[] args) throws Exception {
    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);

    URL webAppDir = Thread.currentThread().getContextClassLoader().getResource(WEBAPP_RESOURCES_LOCATION);
    if (webAppDir == null) {
      throw new RuntimeException(String.format("Unable to find %s directory into the JAR file", WEBAPP_RESOURCES_LOCATION));
    }

    WebAppContext webAppContext = new WebAppContext();
    webAppContext.setContextPath("/");
    webAppContext.setDescriptor(WEBAPP_RESOURCES_LOCATION + "/WEB-INF/web.xml");
    webAppContext.setResourceBase(webAppDir.toURI().toString());
    webAppContext.setParentLoaderPriority(true);

    server.setHandler(webAppContext);

    server.start();

    server.join();
  }
}

Les ressources statiques peuvent être chargées à partir d'un répertoire de votre choix (il sera paramétré dans la directive pom.xml ).

Par exemple, j'ai créé le fichier src/main/webapp pour stocker le contenu statique.

Dans ce dossier, vous devez également définir - dans ce cas, en raison de la façon dont nous configurons Jetty - un fichier WEB-INF à l'aide de ce fichier web.xml à l'intérieur :

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

    <servlet>
        <servlet-name>HelloWorldServlet</servlet-name>
        <servlet-class>io.happycoding.servlets.HelloWorldServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>HelloWorldServlet</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>

    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>

</web-app>

Il s'agit d'un tree de ma configuration du code source :

source code setup

Les pom.xml est très similaire à celui que vous avez fourni. Je n'ai inclus que l'élément maven-resources-plugin pour copier le contenu statique de l'application web dans le fichier jar, et la commande maven-shade-plugin pour générer un UberJar :

<?xml version="1.0" encoding="UTF-8"?>
<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>jar</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>
        <!-- Directory where static content resides -->
        <webapp.dir>./src/main/webapp</webapp.dir>
    </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>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <version>2.7</version>
                <executions>
                    <execution>
                        <id>copy-web-resources</id>
                        <phase>compile</phase>
                        <goals>
                            <goal>copy-resources</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${project.build.directory}/classes/META-INF/resources</outputDirectory>
                            <resources>
                                <resource>
                                    <directory>${webapp.dir}</directory>
                                </resource>
                            </resources>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.2.4</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <transformers>
                                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>io.happycoding.Main</mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </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>

Avec cette configuration, vous pouvez exécuter l'application localement en exécutant la commande suivante :

mvn exec:java

Vous pouvez également exécuter le programme localement à partir de l'outil Java :

java -jar appengine-deploy-sample-1.jar

Désolé, je ne peux pas tester la configuration dans GCP mais je pense que, selon le guide de migration, vous pouvez essayer de déployer l'application sans indiquer le paramètre entrypoint dans votre app.yaml .

Si cela ne fonctionne pas, vous pouvez essayer d'exécuter l'application en configurant un fichier entrypoint similaire à ce qui suit :

entrypoint: 'java -jar appengine-deploy-sample-1.jar'

Ou peut-être :

entrypoint: 'java -cp "*" -jar appengine-deploy-sample-1.jar'

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X