This quickstart will get you going with Scala and the twitter-finagle web library on the Cedar stack. Prerequisites
- Basic Scala knowledge, including an installed version of sbt 0.10.x and a JVM.
- Basic Git knowledge, including an installed version of Git.
- Your application must be compatible with sbt 0.10.1 or higher and scala 2.8.1.
- Your application must run on the OpenJDK version 6.
- An installed version of Ruby.
If you have Rubygems on your system, you can install the Heroku client with:
$ gem install heroku
Otherwise, download this tarball, extract it, and put the resulting directory into your $PATH:
$ wget http://assets.heroku.com/heroku-client/heroku-client.tgz
Saving to: `heroku-client.tgz'
100%[==================================================================>] 412,661 535K/s in 0.8s
$ tar xzf heroku-client.tgz && echo "Add $PWD/heroku-client to your \$PATH."
Add /Users/adam/heroku-client to your $PATH.
You will need ruby in your path, which is available by default on Mac OS X, can be installed on Ubuntu with apt-get install ruby-dev, or on Windows with RubyInstaller. Write Your App
You may be starting from an existing app. If not, here’s a simple “hello, world” sourcefile you can use:
src/main/scala/Web.scala
import com.twitter.finagle.builder.ServerBuilder
import com.twitter.finagle.http.Http
import com.twitter.finagle.Service
import com.twitter.util.Future
import java.net.InetSocketAddress
import org.jboss.netty.buffer.ChannelBuffers
import org.jboss.netty.handler.codec.http.{DefaultHttpResponse, HttpResponse, HttpRequest, HttpVersion, HttpResponseStatus}
import util.Properties
object Web {
def main(args: Array[String]) {
val address = new InetSocketAddress(Properties.envOrElse("PORT", "8080").toInt)
println("Starting on port:"+address.getPort)
val server = ServerBuilder().codec(Http()).name("hello-server").bindTo(address).build(new Hello)
println("Started.")
}
}
class Hello extends Service[HttpRequest, HttpResponse] {
def apply(req: HttpRequest): Future[HttpResponse] = {
val response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK)
response.setContent(ChannelBuffers.wrappedBuffer("Hello World".getBytes))
Future.value(response)
}
}
##Declare the sbt version in project/build.properties
sbt.version=0.10.1
Cedar recognizes an app as Scala by the existence of a project/build.properties
file.
##Add the Typesafe start-script plugin to your project.
This plugin will add a stage
task to sbt that generates start scripts for your application.
Heroku's Scala support runs the tasks clean compile stage
to build your SBT application.
While the start-script plugin provides an implementation of the stage task, you are free to provide your own.
./project/plugins/build.sbt
resolvers += {
val typesafeRepoUrl = new java.net.URL("http://repo.typesafe.com/typesafe/ivy-snapshots")
val pattern = Patterns(false, "[organisation]/[module]/[sbtversion]/[revision]/[type]s/[module](-[classifier])-[revision].[ext]")
Resolver.url("Typesafe Ivy Snapshot Repository", typesafeRepoUrl)(pattern)
}
libraryDependencies <<= (libraryDependencies, sbtVersion) { (deps, version) =>
deps :+ ("com.typesafe.startscript" %% "xsbt-start-script-plugin" % "0.1-SNAPSHOT" extra("sbtversion" -> version))
}
##Declare Dependencies In build.sbt
Here’s an example build.sbt
for the Scala/Finagle app we created above:
build.sbt
import com.typesafe.startscript.StartScriptPlugin
seq(StartScriptPlugin.startScriptForClassesSettings: _*)
name := "hello"
version := "1.0"
scalaVersion := "2.8.1"
resolvers += "twitter-repo" at "http://maven.twttr.com"
libraryDependencies += "com.twitter" % "finagle-core" % "1.7.5"
Prevent build artifacts from going into revision control by creating this file:
.gitignore
target
project/boot
project/target
Run the following command to build your app locally. Again this assumes you have the typesafe xsbt-start-script-plugin installed.
$sbt clean compile stage
...
[info] Compiling 2 Scala sources to .../target/scala-2.8.1.final/classes...
[success] Total time: 7 s, completed Aug 18, 2011 12:23:04 PM
[info] Aliasing start-script to start-script-for-jar in hello
[info] Set current project to default-f0c56e (in build file:...)
[info] Packaging .../target/scala-2.8.1.final/hello_2.8.1-1.0.jar ...
[info] Done packaging.
[info] Wrote start script for jar .../target/scala-2.8.1.final/hello_2.8.1-1.0.jar to.../target/start
[success] Total time: 0 s, completed Aug 18, 2011 12:23:05 PM
##Declare Process Types With Foreman/Procfile
To run your web process, you need to declare what command to use. In this case, we simply need to execute our Web main method. We’ll use Procfile
to declare how our web process type is run.
The typesafe xsbt-start-script-plugin will generate a start script which will set up your runtime classpath, and execute the main class you specify.
Here’s a Procfile for the sample app we’ve been working on:
web: target/start Web
Now that you have a Procfile
, you can start your application with the Foreman gem:
$ gem install foreman
$ foreman start
Your app will come up on port 5000. Test that it’s working with curl or a web browser, then Ctrl-C to exit.
##Store Your App in Git
We now have the three major components of our app: dependencies in build.sbt
, process types in Procfile
, and our application source in src/main/Web.scala
. Let’s put it into Git:
$ git init
$ git add .
$ git commit -m "init"
##Deploy to Heroku/Cedar
Create the app on the Cedar stack:
$ heroku create --stack cedar
Creating glowing-snow-27... done
Created http://glowing-snow-27.herokuapp.com/ | [email protected]:glowing-snow-27.git
Git remote heroku added
Deploy your code:
$ git push heroku master
Counting objects: 31, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (24/24), done.
Writing objects: 100% (31/31), 5.42 KiB, done.
Total 31 (delta 14), reused 0 (delta 0)
-----> Heroku receiving push
-----> Updating alpha language packs... done
-----> Scala (SBT) app detected
-----> Selecting SBT 0.10
-----> Installing SBT from http://s3.amazonaws.com/sclasen-langpack-scala/sbt-launch-0.10.1.jar... done
-----> Building app with SBT
-----> executing sbt clean compile stage
Getting net.java.dev.jna jna 3.2.3 ...
downloading http://s3pository.heroku.com/maven-central/net/java/dev/jna/jna/3.2.3/jna-3.2.3.jar
...
[info] Compiling 1 Scala source to /tmp/build_2hojbcloqzzaf/target/scala-2.8.1.final/classes...
[success] Total time: 25 s, completed Aug 18, 2011 12:08:52 AM
-----> Discovering process types
Procfile declares types -> web
-----> Compiled slug size is 23.8MB
-----> Launching... done, v5
http://gentle-autumn-906.herokuapp.com deployed to Heroku
To [email protected]:gentle-autumn-906.git
* [new branch] master -> master
$
Now, let’s check the state of the app’s processes:
$ heroku ps
Process State Command
------------ ------------------ --------------------------------------------
web.1 up for 10s target/start Web
The web process is up. Review the logs for more information:
$ heroku logs
2011-08-18T00:13:41+00:00 heroku[web.1]: Starting process with command `target/start Web `
2011-08-18T00:14:18+00:00 app[web.1]: Starting on port:28328
2011-08-18T00:14:18+00:00 app[web.1]: Started.
2011-08-18T00:14:19+00:00 heroku[web.1]: State changed from starting to up
...
Looks good. We can now visit the app with heroku open
.
##Console
Cedar allows you to launch a REPL process attached to your local terminal for experimenting in your app’s environment:
$ heroku run sbt console
Running sbt console attached to terminal... up, run.1
[info] Set current project to default (in build file:/app/)
[info] Updating...
[info] Done updating.
[info] Compiling 1 Scala source to /app/target/scala-2.8.1.final/classes...
[info] Starting scala interpreter...
[info]
Welcome to Scala version 2.8.1.final (OpenJDK 64-Bit Server VM, Java 1.6.0_20).
Type in expressions to have them evaluated.
Type :help for more information.
scala>
This console has your application code available. For example:
scala> Web.main(Array.empty[String])
Starting on port:33418
Started.
scala>
##One-off Processes
You can run a one-off Processes attached to the terminal in the same way, as long as the Main Class exists in your deployed app. Try making a small class that prints to the console and exits:
src/main/scala/Demo.scala
object Demo {
def main(args:Array[String]){
println("Hello From Demo")
}
}
Commit and deploy this new code:
$ git add src/main/scala/Demo.scala
$ git commit -m "hi"
$ git push heroku master
Run with heroku run:
$heroku run 'target/start Demo'
Running target/start Demo attached to terminal... up, run.6
Hello From Demo
$