Note: this is the readme for the upcoming 1.0 release. For the (stable) 0.9 release, see the 0.9 branch.
xsbt-web-plugin is an extension to sbt for building enterprise Web applications based on the Java J2EE Servlet specification.
xsbt-web-plugin supports both Scala and Java, and is best suited for projects that:
- 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
s,Filter
s, JNDI) - Have a specific need to be packaged as a .war file
- sbt 0.13.x
- Scala 2.10.x
The quickest way to get started is to clone the xwp-template project, which sets up the necessary directories, files, and configuration for a basic xsbt-web-plugin project.
There are many examples in the form of tests in src/sbt-test.
xsbt-web-plugin consists of three modules: a webapp plugin, a war plugin, and a container plugin.
The webapp plugin is responsible for preparing a Servlet-based Web application as a directory, containing compiled project code, project resources, and a special webapp directory (which includes the web.xml configuration file, static HTML files, etc.).
The war plugin builds on the webapp plugin, adding a way to package the Web application directory as a .war file that can be published as an artifact, and deployed to a Servlet container.
The container plugin also builds on the webapp plugin, adding a way to launch a servlet container in a forked JVM to host the project as a Web application.
Put together, these compose xsb-web-plugin, and provide complete support for developing Servlet-based Web applications in Scala (and Java).
First, add xsbt-web-plugin:
project/plugins.sbt:
addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "1.0.0-M4")
Then choose either Jetty or Tomcat with default setings:
build.sbt:
jetty()
build.sbt:
tomcat()
Start (or restart) the container with container:start
:
sbt console:
> container:start
Stop the container with container:stop
:
sbt console:
> container:stop
Build a .war file with package
:
sbt console:
> package
sbt console:
> ~container:start
This starts the container, then monitors the sources, resources, and webapp directories for changes, which triggers a container restart.
build.sbt:
jetty(port = 9090)
build.sbt:
tomcat(port = 9090)
build.sbt:
jetty(config = "etc/jetty.xml")
The config
path can be either absolute or relative to the project directory.
build.sbt:
lazy val root = (project in file(".")) aggregate(mylib1, mylib2, mywebapp)
lazy val mylib1 = project
lazy val mylib2 = project
lazy val mywebapp = project dependsOn (mylib1, mylib2)
build.sbt:
// add <project>/src/main/extra as an additional source directory
unmanagedSourceDirectories in Compile <+= (sourceDirectory in Compile)(_ / "extra")
Scala files in the extra source directory are compiled, and bundled in the project artifact .jar file.
build.sbt:
// add <project>/src/main/extra as an additional resource directory
unmanagedResourceDirectories in Compile <+= (sourceDirectory in Compile)(_ / "extra")
Files in the extra resource directory are not compiled, and are bundled directly in the project artifact .jar file.
build.sbt:
// set <project>/src/main/WebContent as the webapp resources directory
webappSrc in webapp <<= (sourceDirectory in Compile) map { _ / "WebContent" }
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.
build.sbt:
// set <project>/target/WebContent as the webapp destination directory
webappDest in webapp <<= target map { _ / "WebContent" }
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.
After the /target/webapp directory is prepared, it can be modified
with an arbitrary File => Unit
function.
For example, here's how to minify JavaScript files using YUI Compressor:
project/plugins.sbt:
libraryDependencies += "com.yahoo.platform.yui" % "yuicompressor" % "2.4.7" intransitive()
build.sbt:
// minify the JavaScript file script.js to script-min.js
postProcess in webapp := {
webappDir =>
import java.io.File
import com.yahoo.platform.yui.compressor.YUICompressor
val src = new File(webappDir, "script.js")
val dest = new File(webappDir, "script-min.js")
YUICompressor.main(Array(src.getPath, "-o", dest.getPath))
}
By default, project classes and resources are packaged in the default .jar file artifact, which is copied to WEB-INF/lib. This file can optionally be ignored, and the project classes and resources copied directly to WEB-INF/classes.
build.sbt:
webInfClasses in webapp := true
For situations when the prepared /target/webapp directory is needed, but the packaged .war file isn't.
sbt console:
webapp:prepare
By default, either Jetty's jetty-runner
or Tomcat's webapp-runner will be
used to launch the container under container:start
.
To use a custom runner, use runnerContainer
with warSettings
and
webappSettings
:
build.sbt:
runnerContainer(
libs = Seq(
"org.eclipse.jetty" % "jetty-webapp" % "9.1.0.v20131115" % "container"
, "org.eclipse.jetty" % "jetty-plus" % "9.1.0.v20131115" % "container"
, "test" %% "runner" % "0.1.0-SNAPSHOT" % "container"
),
args = Seq("runner.Run", "8080")
) ++ warSettings ++ webappSettings
Here, libs
includes the ModuleID
s of libraries needed to make our runner,
which is invoked by calling the main method of runner.Run
with a single
argument to specify the server port.
build.sbt:
javaOptions += "-agentpath:/path/to/libyjpagent.jnilib"
By default, the .war file includes the same manifest attributes as the project's artifact:
- Implementation-Vendor
- Implementation-Title
- Implementation-Version
- Implementation-Vendor-Id
- Specification-Vendor
- Specification-Title
- Specification-Version
These can be changed in both artifacts:
build.sbt:
packageOptions in (Compile, packageBin) +=
Package.ManifestAttributes( java.util.jar.Attributes.Name.SEALED -> "true" )
Or in just the .war file:
build.sbt:
packageOptions in packageWar +=
Package.ManifestAttributes( java.util.jar.Attributes.Name.SEALED -> "true" )
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.5
project/plugins.sbt:
addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "1.0.0-M4")
build.sbt:
name := "myproject"
organization := "myorganization"
version := "0.1.0-SNAPSHOT"
scalaVersion := "2.10.4"
jetty()
libraryDependencies += "javax.servlet" % "javax.servlet-api" % "3.0.1" % "provided"
Add a servlet:
src/main/scala/servlets.scala:
package servlets
import scala.xml.NodeSeq
import scala.xml.PrettyPrinter
import javax.servlet.http.HttpServlet
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
class MyServlet extends HttpServlet {
override def doGet(request: HttpServletRequest, response: HttpServletResponse) {
response.setContentType("text/html")
response.setCharacterEncoding("UTF-8")
val responseBody: NodeSeq =
<html>
<body>
<h1>Hello, world!</h1>
</body>
</html>
val printer = new PrettyPrinter(80, 2)
response.getWriter.write(printer.formatNodes(responseBody))
}
}
src/main/webapp/WEB-INF/web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<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>
To disable publishing of the .war file, add the setting:
packagedArtifacts <<= packagedArtifacts map { as => as.filter(_._1.`type` != "war") }
Note that package
can still be used to create the .war file under the project target/ directory.
To disable publishing of the project's .jar file, add the setting:
publishArtifact in (Compile, packageBin) := false
On Ubuntu, install both tomcat7 and tomcat7-admin:
sudo apt-get install tomcat7 tomcat7-admin
Create a Tomcat user with the role manager-script in /etc/tomcat7/tomcat-users.xml:
<user username="manager" password="secret" roles="manager-script" />
Restart tomcat:
sudo service tomcat7 restart
Now a WAR file can be deployed using the Manager deploy command:
curl --upload-file myapp.war "http://manager:secret@myhost:8080/manager/text/deploy?path=/myapp&update=true"
The application will be available at myhost:8080/myapp.
Learn more about Manager commands here.
-
Install the Heroku Toolbelt
-
Install the
heroku-deploy
command line plugin:
heroku plugins:install https://github.com/heroku/heroku-deploy
- Create a WAR file:
sbt package
4 Deploy the WAR file:
heroku deploy:war --war <path_to_war_file> --app <app_name>
See devcenter.heroku.com for more information.
See developers.google.com for more information.