xsbt-web-plugin
Current release: 3.0.3
xsbt-web-plugin is an sbt extension for building J2EE Web applications in Scala and Java. It is best suited for projects that:
- Need to be packaged as a .war file
- Deploy to common cloud platforms (e.g. Google App Engine, Heroku, Elastic Beanstalk, Jelastic)
- Deploy to production J2EE environments (e.g. Tomcat, Jetty, GlassFish, WebSphere)
- Incorporate J2EE libraries (e.g. JSP, JSF, EJB)
- Utilize J2EE technologies (e.g.
Servlet
,Filter
, JNDI)
For previous releases, see the docs directory. Releases follow Specified Versioning guidelines.
Requirements
- Scala 2.10.2+
- sbt 0.13.6+
Quick reference
Add xsbt-web-plugin to project/plugins.sbt:
addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "3.0.3")
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
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" % "3.0.3")
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")
- For available Jetty arguments, see the Jetty Runner docs
- For available Tomcat arguments, see webapp-runner#options
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 :=
{ (port, path) => Seq("runner.Run", port.toString, path) }
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"
Example: container/java-options
To attach a debugger, set -Xdebug
and -Xrunjdwp
:
build.sbt:
javaOptions in Jetty ++= Seq(
"-Xdebug",
"-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000"
)
In Eclipse:
- Create and run a new Remote Java Application launch configuration
- Set Connection Type to Scala debugger (Socket Attach)
- Configure to connect to localhost on port 8000
In IntelliJ IDEA:
- Add a Remote run configuration: Run -> Edit Configurations...
- Under Defaults select Remote and push
+
to add a new configuration - By default the configuration uses port 5005; update it to 8000 as above
- Name this configuration, and run it in debug mode
Debug mode
To enable debugging through JDWP, use jetty:debug
or
tomcat:debug
. Optionally set debugAddress
, which defaults to
"debug"
under Windows and "8888"
otherwise, and debugOptions
,
which defaults to:
port =>
Seq( "-Xdebug"
, Seq( "-Xrunjdwp:transport=dt_socket"
, "address=" + port
, "server=y"
, "suspend=n"
).mkString(",")
)
Jetty version
By default, Jetty 9.4.1 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.9.0 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).value / "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).value / "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:
webappPrepare
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
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
Enable the HerokuDeploy
plugin and configure your app name:
enablePlugins(HerokuDeploy)
herokuAppName := "my-heroku-app"
Either install the Heroku Toolbelt, or
set your Heroku API key as an environment variable, launch sbt, and
deploy with herokuDeploy
:
$ HEROKU_API_KEY="xxx-xxx-xxxx" sbt
> herokuDeploy
Check out your deployed application at
https://my-heroku-app.herokuapp.com
.
Deploying to Elastic Beanstalk
Before trying to deploy anything, create an application and a Tomcat-based environment for it in Elastic Beanstalk.
Enable the ElasticBeanstalkDeployPlugin
plugin, and configure your
application's name, environment, and region:
enablePlugins(ElasticBeanstalkDeployPlugin)
elasticBeanstalkAppName := "my-elastic-beanstalk-app"
elasticBeanstalkEnvName := "production"
elasticBeanstalkRegion := "us-west-1"
Add AWS credentials to your environment, launch sbt, and deploy with
elasticBeanstalkDeploy
:
$ AWS_ACCESS_KEY="xxx" AWS_SECRET_KEY="xxx" sbt
> elasticBeanstalkDeploy
Check out your deployed application at
http://my-elastic-beanstalk-app.us-west-1.elasticbeanstalk.com
.
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
.
Running multiple containers
To launch using more than a single container, set containerScale
:
containerScale := 5
This will configure the container to launch in five forked JVMs, using
five sequential ports starting from containerPort
.
In debug mode, five additional sequential debug ports starting from
debugPort
will be opened.
JRebel integration
The development cycle can be further sped up by skipping server restarts between code recompilation.
Add -agentpath
to the container's JVM options:
javaOptions in Jetty += "-agentpath:/path/to/jrebel/lib/libjrebel64.so"
Launch the container with quickstart
, and run triggered compilation:
> jetty:quickstart
> ~compile