Question

We found when moving from Jetty in development to Tomcat in production that our Swagger documentation stopped working -- no operations would show up in the Swagger docs until after the first call to the servlet providing the operation.

After some work in the debugger we traced the problem to the servlets' init() method not getting called. I assume that Jetty was treating all servlets as load-on-startup, whereas Tomcat isn't, unless you explicitly tell it to in the web.xml.

Unfortunately, in the web.xml, the only things you can set as load-on-startup are servlets, and there are no actual servlets in our Scalatra web.xml, only listeners and servlet-mappings.

How can we get Scalatra to initialize our various ScalatraServlets on startup?


Note: We're on Scalatra 2.2.2. It could very well be that moving to 2.3 snapshot or milestone would fix the Swagger problem, but we've done a lot of work to get the 2.2 Swagger integration working and we're too close to going live to rip it all out.

Was it helpful?

Solution

EDIT: this made its way into Scalatra and should be available soon, see https://github.com/scalatra/scalatra/pull/356

Yes, Tomcat by default does not load a Servlet until the first HTTP request to it is made. You can tell Tomcat nevertheless to load the Servlet by using load-on-startup. Here is a way to do this programmatically using a LifeCycle:

def mountServlet(sc: ServletContext, servlet: HttpServlet, urlPattern: String, loadOnStartup: Int = 1) {
  val name = servlet.getClass.getName
  val reg = Option(sc.getServletRegistration(name)) getOrElse {
    val r = sc.addServlet(name, servlet)
    servlet match {
      case s: HasMultipartConfig =>
        r.setMultipartConfig(s.multipartConfig.toMultipartConfigElement)
      case _ =>
    }
    if (servlet.isInstanceOf[ScalatraAsyncSupport])
      r.setAsyncSupported(true)
    r.setLoadOnStartup(loadOnStartup)
    r
  }

  reg.addMapping(urlPattern)
}

This defines an alternative to the mount method in RichServletContext. The important part is r.setLoadOnStartup(loadOnStartup).

You can use it like this:

override def init(context: ServletContext) {

  val comments = CommentsRepository(mongoColl)

  // mount the api + swagger docs
  mountServlet(context, new CommentsApi(comments), "/api/*", 1)
  mountServlet(context, new CommentsApiDoc(), "/api-docs/*", 2)

  mountServlet(context, new CommentsFrontend(comments), "/*")

}

The sample code is taken from https://github.com/scalatra/scalatra-in-action/blob/d325c85feaf10706951c8edb88a3d82d0488faf3/comments-collector/src/main/scala/ScalatraBootstrap.scala.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top