Wednesday, September 10, 2014

Getting Scalatra to work on Google App Engine - Part 1

Scalatra is a neat lightweight Scala web framework. Scalatra 2.3.0 is based on the Servlet API 3.1 , while App Engine Java runtime only supports Servlet API 2.5 . App Engine imposes additional restrictions as described here, but they are not as significant as lack of support for Servlet 3.0/3.1 .

After digging and experimenting, I succeeded in getting Scalatra to work on App Engine with a few workarounds. Basically we need to refrain from using Servlet 3.0/3.1 specific features in our code, and override Scalatra's parts that do use them with Servlet 2.5 compatible alternatives.

The first obstacle you'll encounter is that you can't mount your servlets and filters programmatically in your ScalatraBootstrap class like this:

import org.scalatra.LifeCycle

import javax.servlet.ServletContext

class ScalatraBootstrap extends LifeCycle {

   override def init(context: ServletContext) {

      // This will fail
      context mount (new MyCustomServlet, "/*")
   }
}

Instead, you leave the init() method empty, and add your servlets and filters to web.xml :

<servlet>
   <servlet-name>my-custom-servlet</servlet-name>
   <servlet-class>MyCustomServlet</servlet-class>
</servlet>
<servlet-mapping>
   <servlet-name>my-custom-servlet</servlet-name>
   <url-pattern>/*</url-pattern>
</servlet-mapping>

Then, at runtime when accessing a path which is mapped by your Scalatra endpoints, you will encounter a NoSuchMethodError because Scalatra attempts to call HttpServletResponse.getStatus(), which was added in Servlet API 3.0 . The problematic code is in RichResponse.status(). RichResponse is an implicit replacement for HttpServletResponse as defined in ServletApiImplicits.enrichResponse(). To overcome the reading of the status from the original response, we'll subclass RichResponse and change the setter and getter of the status to use a local variable:

import org.scalatra.servlet.RichResponse
import javax.servlet.http.HttpServletResponse
import org.scalatra.ResponseStatus

class RichResponseLocalStatus(response:HttpServletResponse) extends RichResponse(response) {

   var statusCode:Int = _

   override def status = ResponseStatus(statusCode)

   override def status_=(statusLine: ResponseStatus) {
      statusCode = statusLine.code
      super.status_=(statusLine)

   }
}

The Scalatra code made it impossible to override the implicit definition for our need, so we have to rewrite ServletApiImplicits to look like this:

package org.scalatra
package servlet

import javax.servlet.ServletContext
import javax.servlet.http.{HttpServletRequest, HttpServletResponse, HttpSession}
import test.RichResponseCustom

trait ServletApiImplicits {

   implicit def enrichRequest(request: HttpServletRequest): RichRequest =
      RichRequest(request)

   implicit def enrichResponse(response: HttpServletResponse): RichResponse =

      // This is changed
      new RichResponseLocalStatus(response)

   implicit def enrichSession(session: HttpSession): RichSession =
   RichSession(session)


   implicit def enrichServletContext(servletContext: ServletContext): RichServletContext =
      RichServletContext(servletContext)
}

object ServletApiImplicits extends ServletApiImplicits

Remember to place the changed ServletApiImplicits with your sources in the "org.scalatra.servlet" package. If you place it in another JAR, remember to make sure it takes precedence over Scalatra's JAR using App Engine's classloader JAR ordering.

That's it. It's not a perfect solution as it won't support Servlet 3.0/3.1 features such as asynchronous processing, and you have to define and map each servlet and filter in web.xml, but these are limitations you have to live with anyway on App Engine and this solution does let you use the nice and clean Scalatra code to write your web tier.

In the next post we will implement an alternative mapping mechanism, which will allow us to reduce the web.xml code and call mount operations in our ScalatraBootstrap code.

No comments:

Post a Comment