Issue
I am trying to migrate my application from tomcat to embedded jetty. I have created an entrypoint in web module following some guides (rel="nofollow noreferrer">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.
Solution
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 ...
- How do I configure embedded jetty to used war file in executable jar as resource
- https://github.com/jetty-project/embedded-jetty-live-war - example showing a live executable war file with a built-in Jetty server with annotation scanning.
Answered By - Joakim Erdfelt
Answer Checked By - Cary Denson (JavaFixing Admin)