Skip to content

version 2.0.0.M2

Compare
Choose a tag to compare
@seratch seratch released this 25 Jul 04:28
· 432 commits to master since this release

This is the first milestone package release of Skinny 2.0.

Topics

Replace Scalatra with skinny-engine

Skinny 2 has its own Servlet wrapper implementation. Skinny 2 won't be compatible with any versions of Scalatra. Skinny 1.x will keep compatible with Scalatra 2.3.x.

https://github.com/skinny-framework/skinny-framework/tree/master/engine

Mostly DSLs are compatible but many improvements and minor API changes, renaming modules/classes and re-packaging have been applied. If you explicitly import org.scalatra package things into your applications, fixing packages or class/module names will be needed. Migration guide for details will come later.

  • SkinnyEngineServlet instead of ScalatraServlet
  • SkinnyEngineFilter instead of ScalatraFilter

Skinny 2 has its own forked scalatra-test named skinny-engine-test. If you're using ScalaTest, switching to skinny-engine-test will be so easy. Good to hear, your existing tests written with scalatra-test will work fine with Skinny 2 apps.

A solution for Servlet request reuse issue

When Servlet applications are running in async supported mode, Servlet containers sometimes re-use their HttpServletRequest objects in order to avoid the performance overhead of request object creation.

skinny-engine wraps HttpServletRequest like this.

engine/src/main/scala/skinny/engine/request/StableHttpServletRequest.scala

StableHttpServletRequest basically works for you even when the original request has been recycled. Actually, it's difficult to make all the APIs stable. However, our StableHttpServletRequest makes basic metadata of the request, request attributes and request headers always stable. We believe that will be enough.

A solution for accessing DynamicScope from other threads

Scalatra was originally designed to provide Sinatra-ish APIs for simple Servlet use cases. Traditionally Servlet apps allow thread-local approarch as same as other Java standard things.

Scalatra's DynamicScope can be a cause of trouble especially when you're dealing with Future values. For example, this code is very fragile because of accessing other thread's variable from other threads.

class Sample extends ScalatraServlet with FutureSupport {
  get("/") {
     new AsyncResult {
       override val is = {
         // the "request" is a thread-local value of the main thread which is came from the above DynamicScope
         // NullPointerException occasionally happens here
         request.getContextPath 
       }
     }
  }
}

Scala 2.10 introduced Future APIs after successful Twitter's its own Future APIs and Finagle library. Thinking back now, that was a major event for Scala eco-system. Recently so many libraries and coding practices expect seamlessly using Future everywhere that the DynamicScope issue is no longer a small issue now.

The root cause of the issue is that we can easily (or unexpectedly) access the main-thread's thread-local variable via implicit conversions everywhere and we cannot disable it in some way.

So skinny-engine is going to solve the issue by following approarch:

  • By adding implicit skinny.engine.Context parameter to all the DSL methods, we no longer need to be careful to access unstable request reference (in the DynamicScope)
  • Add Async* traits to disable the global thread-local variable (e.g.) AsyncSkinnyEngineServlet, AsyncSkinnyEngineFilter
class Sample extends AsyncSkinnyEngineServlet {
  before() { implicit ctx =>
    // never directly access dynamic scope here
    contentType = "application/json; charset=utf-8"
  }

  get("/") { implicit ctx =>
    // never directly access dynamic scope here

    // you no longer need to specify AsyncResult by yourself
    Future {
      // never directly access dynamic scope here
    }
  }
}

If you need to mix non-async and async, define implicit value in the main thread side and fix error: ambiguous implicit values compilation errors:

class Sample extends SkinnyEngineServlet {
  get("/") {
    implicit val ctx = context

    // you no longer need to specify AsyncResult by yourself
    Future {
      // fix compilation errors like this:
      params(ctx).get("name")
    }
  }
}

Handy standalone web application builder

skinny-engine-server has its own embedded Jetty server. This library will be very handy when you need to quickly build a small web application.

https://github.com/skinny-framework/skinny-framework/tree/master/skinny-engine-blank-app

The following is the minimum scalas example:

#!/usr/bin/env scalas
/***
scalaVersion := "2.11.7"
resolvers += "sonatype releases" at "https://oss.sonatype.org/content/repositories/releases"
libraryDependencies += "org.skinny-framework" %% "skinny-engine-server" % "2.0.0.M2-20150725"
*/
import skinny.engine._
import scala.concurrent._

WebServer.mount(new WebApp {
  get("/") {
    val name = params.getOrElse("name", "Anonymous")
    s"Hello, $name"
  }
}).mount(new AsyncWebApp {
  get("/async") { implicit ctx =>
    Future {
      responseAsJSON(params)
    }
  }
}).port(8081).run()

NOTICE: WebApp is a type alias for SkinnyEngineFilter and AsyncWebApp is one for AsyncSkinnyEngineFilter.