Skip to content

Latest commit

 

History

History
593 lines (400 loc) · 14.5 KB

2.3.md

File metadata and controls

593 lines (400 loc) · 14.5 KB

xsbt-web-plugin

Version 2.3

xsbt-web-plugin is an sbt extension for building J2EE Web applications in Scala and Java.

Requirements

  • Scala 2.10.2+
  • sbt 0.13.6+

Scala 2.11 and 2.12 are not yet supported by sbt.

Quick reference

Add xsbt-web-plugin to project/plugins.sbt:

addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "2.3.0")

Enable the Jetty plugin:

build.sbt:

enablePlugins(JettyPlugin)

From the sbt console:

  • Start (or restart) the container with jetty:start
  • Stop the container with jetty:stop
  • Build a .war file with package

To use Tomcat instead of Jetty:

  • Substitute TomcatPlugin for JettyPlugin
  • Substitute tomcat:start for jetty:start
  • Substitute tomcat:stop for jetty:stop

Starting from scratch

Create a new empty project:

mkdir myproject
cd myproject

Set up the project structure:

mkdir project
mkdir -p src/main/scala
mkdir -p src/main/webapp/WEB-INF

Configure sbt:

project/build.properties:

sbt.version=0.13.8

project/build.sbt:

addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "2.3.0")

build.sbt:

scalaVersion := "2.11.6"

libraryDependencies += "javax.servlet" % "javax.servlet-api" % "3.0.1" % "provided"

enablePlugins(JettyPlugin)

Add a servlet:

src/main/scala/servlets.scala:

package servlets

import javax.servlet.http._

class MyServlet extends HttpServlet {

  override def doGet(request: HttpServletRequest, response: HttpServletResponse) {
    response.setContentType("text/html")
    response.setCharacterEncoding("UTF-8")
    response.getWriter.write("""<h1>Hello, world!</h1>""")
  }

}

src/main/webapp/WEB-INF/web.xml:

<web-app>

  <servlet>
    <servlet-name>my servlet</servlet-name>
    <servlet-class>servlets.MyServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>my servlet</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>

</web-app>

Configuration and use

Triggered execution

xsbt-web-plugin supports sbt's triggered execution by prefixing commands with ~.

sbt console:

> ~jetty:start

This starts the Jetty container, then monitors the sources, resources, and webapp directories for changes, which triggers a container restart.

Container arguments

To pass extra arguments to the Jetty or Tomcat container, set containerArgs:

containerArgs := Seq("--path", "/myservice")

Custom container

To use a custom J2EE container, e.g. a main class named runner.Run, enable ContainerPlugin and set containerLibs and containerLaunchCmd:

enablePlugins(ContainerPlugin)

containerLibs in Container := Seq(
    "org.eclipse.jetty" %  "jetty-webapp" % "9.1.0.v20131115"
  , "org.eclipse.jetty" %  "jetty-plus"   % "9.1.0.v20131115"
  , "test"              %% "runner"       % "0.1.0-SNAPSHOT"
)

containerLaunchCmd in Container := Seq("runner.Run", "8080", (target in webappPrepare).value.absolutePath)

sbt:

> container:start
> container:stop

Example: container/custom-runner

Forked JVM options

To set system properties for the forked container JVM, set containerForkOptions:

containerForkOptions := new ForkOptions(runJVMOptions = Seq("-Dh2g2=42"))

Example: container/fork-options

Alternatively, set javaOptions in the Jetty (or Tomcat) configuration:

javaOptions in Jetty += "-Dh2g2=42"

To attach an Eclipse debugger, set -Xdebug and -Xrunjdwp:

build.sbt:

javaOptions in Jetty ++= Seq(
  "-Xdebug",
  "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000"
)

This is a handy way to change configuration for local development and testing:

javaOptions in Jetty += "-DdbUrl=jdbc:sqlite:test.db"

In Eclipse, create and run a new Remote Java Application launch configuration with a Connection Type of Scala debugger (Socket Attach), set to connect to localhost on port 8000.

Example: container/java-options

Similarly, to attach an IntelliJ IDEA debugger, add a Remote run configuration: Run -> Edit Configurations... Under Defaults select Remote and push the "+" button to add a new configuration. By default the configuration will use port 5005. (Use the same port in the -Xrunjdwp address.) Name this configuration, and run it in debug mode.

Debug mode

To enable debugging through JDWP, use jetty:debug or tomcat:debug, and optionally set debugOptions, which defaults to:

Seq(
    "-Xdebug"
  , "-Xrunjdwp:transport=dt_socket,address=8888,server=y,suspend=n"
)

Jetty version

By default, Jetty 9.3.13 is used. To use a different version, set containerLibs:

containerLibs in Jetty := Seq("org.mortbay.jetty" % "jetty-runner" % "7.0.0.v20091005" intransitive())

Depending on the version, it may also be necessary to specify the name of Jetty's runner:

containerMain := "org.mortbay.jetty.runner.Runner"

Examples:

Container port

By default, the container runs on port 8080. To use a different port, set containerPort:

containerPort := 9090

Examples:

jetty.xml

To use a jetty.xml configuration file, set containerConfigFile:

containerConfigFile := Some(file("etc/jetty.xml"))

This option can be used to enable SSL and HTTPS.

Examples:

Multi-project applications

Examples:

Tomcat version

By default, Tomcat 8.5 is used. To use a different version, set containerLibs:

containerLibs in Tomcat := Seq("com.github.jsimone" % "webapp-runner" % "7.0.34.1" intransitive())

Depending on the version, it may also be necessary to specify the name of Tomcat's runner:

containerMain in Tomcat := "webapp.runner.launch.Main"

Renaming the .war file

This can be useful for keeping the version number out of the .war file name, using a non-conventional file name or path, adding additional information to the file name, etc.

artifactName := { (v: ScalaVersion, m: ModuleID, a: Artifact) =>
  a.name + "." + a.extension
}

See "Modifying default artifacts" in the sbt documentation for additional information.

Massaging the .war file

After the /target/webapp directory is prepared, it can be modified with an arbitrary File => Unit function by setting webappPostProcess.

To list the contents of the webapp directory after it is prepared:

webappPostProcess := {
  webappDir: File =>
    def listFiles(level: Int)(f: File): Unit = {
      val indent = ((1 until level) map { _ => "  " }).mkString
      if (f.isDirectory) {
        streams.value.log.info(indent + f.getName + "/")
        f.listFiles foreach { listFiles(level + 1) }
      } else streams.value.log.info(indent + f.getName)
    }
    listFiles(1)(webappDir)
}

To include webapp resources from multiple directories in the prepared webapp directory:

webappPostProcess := {
  webappDir: File =>
    val baseDir = baseDirectory.value / "src" / "main"
    IO.copyDirectory(baseDir / "webapp1", webappDir)
    IO.copyDirectory(baseDir / "webapp2", webappDir)
    IO.copyDirectory(baseDir / "webapp3", webappDir)
}

Examples:

Custom resources directory

Files in the extra resource directory are not compiled, and are bundled directly in the project artifact .jar file.

To add a custom resources directory, set unmanagedResourceDirectories:

unmanagedResourceDirectories in Compile <+= (sourceDirectory in Compile)(_ / "extra")

Example: webapp/unmanaged-resources

Custom sources directory

Scala files in the extra source directory are compiled, and bundled in the project artifact .jar file.

To add a custom sources directory, set unmanagedSourceDirectories:

unmanagedSourceDirectories in Compile <+= (sourceDirectory in Compile)(_ / "extra")

Example: webapp/unmanaged-sources

Utilizing WEB-INF/classes

By default, project classes are packaged into a .jar file, shipped in the WEB-INF/lib directory of the .war file. To instead keep them extracted in WEB-INF/classes, set webappWebInfClasses:

webappWebInfClasses := true

Examples:

Web application destination

The Web application destination directory is where the static Web content, compiled Scala classes, library .jar files, etc. are placed. By default, they go to /target/webapp.

To specify a different directory, set target in the webappPrepare configuration:

target in webappPrepare := target.value / "WebContent"

Example: webapp/webapp-dest

Web application resources

The Web application resources directory is where static Web content (including .html, .css, and .js files, the web.xml container configuration file, etc. By default, this is kept in /src/main/webapp.

To specify a different directory, set sourceDirectory in the webappPrepare configuration:

sourceDirectory in webappPrepare := (sourceDirectory in Compile).value / "WebContent"

Example: webapp/webapp-src

Prepare the Web application for execution and deployment

For situations when the prepared /target/webapp directory is needed, but the packaged .war file isn't.

sbt console:

webapp:prepare

Add manifest attributes

Manifest attributes of the .war file can be configured via packageOptions in sbt.Keys.package in build.sbt:

packageOptions in sbt.Keys.`package` +=
  Package.ManifestAttributes( java.util.jar.Attributes.Name.SEALED -> "true" )

Inherit manifest attributes

To configure the .war file to inherit the manifest attributes of the .jar file, typically set via packageOptions in (Compile, packageBin), set inheritJarManifest to true:

inheritJarManifest := true

Using JRebel

Add the JRebel sbt plugin (which generates jrebel.xml) to project/plugins.sbt:

addSbtPlugin("fi.gekkio.sbtplugins" % "sbt-jrebel-plugin" % "0.10.0")

Add the following lines to build.sbt, making sure to specify the correct path to JRebel:

jrebelSettings

jrebel.webLinks += (sourceDirectory in Compile).value / "webapp"

jrebel.enabled := true

javaOptions in Jetty ++= Seq(
    "-javaagent:/path/to/jrebel/jrebel.jar",
    "-noverify",
    "-XX:+UseConcMarkSweepGC",
    "-XX:+CMSClassUnloadingEnabled"
)

Start the container, and trigger ~compile, and your changes should be picked up automatically:

> jetty:start
> ~compile

Container shutdown and sbt

By default, sbt will shutdown the running container when exiting sbt.

To allow the container to continue running after sbt exits, set containerShutdownOnExit:

containerShutdownOnExit := false

Deploying to Heroku

See sbt-heroku-deploy.

Block sbt on running container

To start the container from the command line and block sbt from exiting prematurely, use jetty:join:

$ sbt jetty:start jetty:join

This is useful for running sbt in production (e.g. in a Docker container).

Quickstart mode

The development cycle can be sped up by serving static resources directly from source, and avoiding packaging of compiled artifacts.

Use <container>:quickstart in place of <container>:start to run the container in quickstart mode:

> jetty:quickstart

Note that this necessarily circumvents any behavior set in webappPostProcess.