Version 2.3
xsbt-web-plugin is an sbt extension for building J2EE Web applications in Scala and Java.
- Scala 2.10.2+
- sbt 0.13.6+
Scala 2.11 and 2.12 are not yet supported by sbt.
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
forJettyPlugin
- Substitute
tomcat:start
forjetty:start
- Substitute
tomcat:stop
forjetty:stop
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>
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.
To pass extra arguments to the Jetty or Tomcat container, set
containerArgs
:
containerArgs := Seq("--path", "/myservice")
- For available Jetty arguments, see the Jetty Runner docs
- For available Tomcat arguments, see webapp-runner#options
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
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.
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"
)
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:
By default, the container runs on port 8080. To use a different port,
set containerPort
:
containerPort := 9090
Examples:
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:
Examples:
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"
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.
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:
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
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
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:
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
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
For situations when the prepared /target/webapp directory is needed, but the packaged .war file isn't.
sbt console:
webapp:prepare
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" )
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
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
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
See sbt-heroku-deploy.
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).
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
.