0

I am trying to migrate my application from tomcat to embedded jetty. I have created an entrypoint in web module following some guides (this, this, this and etc...). Resulting file is presented below:

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

object EPILauncher extends App {
  val server: Server = new Server(8080)
  val coolWebApplication = new WebAppContext()
  coolWebApplication.setResourceBase("warehouse/src/main/webapp/")
  coolWebApplication.setContextPath("/api")
  coolWebApplication.setDescriptor("warehouse/src/main/webapp/WEB-INF/web.xml")
  coolWebApplication.setParentLoaderPriority(true)
  server.start()
  System.out.println("Started!")
  server.join()
}

I have following servlet declaration in my web.xml file

     <servlet>
        <servlet-name>CustomerApplication</servlet-name>
        <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
        <init-param>
            <param-name>javax.ws.rs.Application</param-name>
            <param-value>com.xxxx.yyyy.warehouse.web.Root</param-value>
        </init-param>
        <init-param>
            <param-name>jersey.config.server.provider.packages</param-name>
            <param-value>
                io.swagger.jaxrs.listing,
                org.owasp.csrfguard.servlet,
                com.xxxx.yyyy.warehouse.resource
            </param-value>
        </init-param>
        <init-param>
            <param-name>jersey.config.server.provider.scanning.recursive</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
            <param-name>jersey.config.servlet.filter.staticContentRegex</param-name>
            <param-value>.*(html|css|js|eot|svg|ttf|woff)</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>CustomerApplication</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

Package com.xxxx.yyyy.warehouse.resource contains implementations, e.g.:

@Singleton
@Path("/settings")
@Api("/settings")
class SettingsResource @Inject()(config: Config) {
  @GET
  @Path("/version")
  @Produces(Array(MediaType.APPLICATION_JSON))
  def getBackendVersion(@Context servletContext: ServletContext): Response = {
    val manifestStream = servletContext.getResourceAsStream("META-INF/MANIFEST.MF")
    val version: String = Option(manifestStream)
      .map(Utils.using(_) { is =>
        val attrs = new java.util.jar.Manifest(is).getMainAttributes
        val version = attrs.getOrDefault(new Attributes.Name("Specification-Version"), "local-version").toString
        val build = attrs.getOrDefault(new Attributes.Name("Implementation-Version"), "local-build").toString
        s"$version.$build"
      }).getOrElse("local-version.local-build")
    Response.ok(VersionInfo(version)).build()
  }
}

So, when I run my app and navigate to localhost:8080/api/settings/version, all I see is:

URI:    /api/settings/version
STATUS: 404
MESSAGE:    Not Found
SERVLET:    -

So, I think that I don't understand some concepts properly. Should I explicitly point in my main method, which servlets I want to use? Can they load automatically from web.xml file? Thanks.

Community
  • 1
  • 1
likeanowl
  • 35
  • 1
  • 10

1 Answers1

2

You can have servlets load from WEB-INF/web.xml or from Annotations. But if your going embedded, the use of annotations is often not the most ideal way to use an embedded server.

For Jetty, to use either WEB-INF/web.xml or Annotations you have use a WebAppContext. Which is a heavyweight component with full Servlet rules and classloader behaviors (re: classloader isolation).

This is often overkill for an embedded server, and many projects that start this way eventually move away from it.

If you rely on Annotations, then you'll have the added requirement for bytecode scanning of the classes you have, exposing them in a way that all libraries that do bytecode scanning can find them. Servlet does bytecode scanning once in it's own way, and the JAXRS layer will do it again it's own unique way. Both pretty much expect to find their resources via the ServletContext interface and its associated descriptor information, that points to where to find the classes.

A more traditional setup on Jetty is to use ServletContextHandler. Which is essentially a subset of the WebAppContext with no classloader isolation, and allows you to manually declare all of the Servlets and Filters and their mappings entirely in code.

This makes startup very quick as well (suitable for a docker environment or microservices). Think sub-second startup. A 100ms startup is achievable with very little effort this way.

If you must stick with a war and WebAppContext, consider doing a build time scan of the war configuration and resources, then adding the jetty-quickstart metadata into your war. That little piece of XML, plus the quickstart runtime will also let you have blazingly fast startup with no webapp discovery step.

If you want to merge embedded with webapps and have the option of using the application in a traditional sense without repackaging, then consider setting up a "live war", which is a war that is directly executable via java -jar /path/to/myapp.war.

There are past answers on using a Live WAR ...

Joakim Erdfelt
  • 46,896
  • 7
  • 86
  • 136
  • Thanks, @joakim-erdfelt ! However, I will keep `web.xml` due to some security filters defined in it. – likeanowl Oct 07 '19 at 15:13
  • All concepts in `WEB-INF/web.xml` can be expressed in code against `ServletContextHandler` in an embedded-jetty usage. Including your servlet constraints, security filters, mime-types, etc.. – Joakim Erdfelt Oct 07 '19 at 16:17
  • @JoakimErdfelt Hej! Well, we have an existing web.xml running fine on standalone tomcat and ideally, we would have liked and still like be able to use embedded jetty (or tomcat, which works first) but really with a 1 - 1 mapping. Ideally, we would have liked to retain the option to stay with a war deployment alternative, and not like now, having to rewrite and use container specific code for each. Ideally you should have been able to supply Jetty with the web.xml and it should go on from there to figure out and set up the required configuration. Not like now, I have to manually configure it .. – mjs Jun 23 '21 at 19:59
  • .. using specific jetty code which is difficult to get right. we also have to set up annotations scanning and get web sockets working. The web.xml has a listener tag that there is no equivalent for in jetty. With tomcat we got it almost up all the way, although it was not 1-1 with tomcat standalone. With Jetty we are thus far unable to get even the basics up. If we do get this up and running, we would still have liked to retain the original web.xml and war option although I can see that being potentially difficult to achieve. – mjs Jun 23 '21 at 20:01
  • Also, we have tests that now is only fed the web.xml and it goes on to start a web server. I don't get why jetty can not do that either. I don't want to have to learn and be isolated into jetty. web.xml is supposed to be universal. – mjs Jun 23 '21 at 20:02
  • I posted this on tomcat, https://stackoverflow.com/questions/68100400/embedded-tomcat-how-to-replace-existing-web-xml-meant-for-standard-tomcat-with?noredirect=1#comment120364189_68100400 and I have gotten all of that almost all of it resolved although missing web sockets as well as missing the HttpSessionListener implemented. In web.xml we had one Listener that implemented both, but seems tomcat is unable to register more than one type at a time. – mjs Jun 23 '21 at 20:04
  • You always could use `WebAppContext`, you just have to come into it fully aware of what that means in an embedded scenario (you have to express your application as a webapp, either as an exploded webapp directory, or a war, no including it in your uber-jar). You only needed to add the jetty-annotations configuration for bytecode scanning of the servlet layer. The JAX-RS bytecode scanning is unrelated and handled entirely within JAXRS libraries. – Joakim Erdfelt Jun 23 '21 at 22:57
  • I added a few links to the answer for details. – Joakim Erdfelt Jun 23 '21 at 23:00