Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Spruce up the README.

  • Loading branch information...
commit 96d7d1cd7348ec1d696d56c7c2912bf523846be2 1 parent 6ec3957
@rossabaker rossabaker authored
Showing with 298 additions and 170 deletions.
  1. +298 −170 README.markdown
View
468 README.markdown
@@ -1,225 +1,327 @@
-About
-=====
+Scalatra is a tiny, [Sinatra](http://www.sinatrarb.com/)-like web framework for [Scala](http://www.scala-lang.org/).
-Scalatra is a tiny Scala web framework inspired by [Sinatra](http://www.sinatrarb.com/) and originally based on some code I found on an [awesome blog post](http://www.riffraff.info/2009/4/11/step-a-scala-web-picoframework).
+## Example
-Comments, issues, and pull requests are welcome. Please also see the [scalatra-user](http://groups.google.com/group/scalatra-user) mailing list, or drop in on IRC at #scalatra on irc.freenode.org
+ import org.scalatra._
-Example
-=======
+ class ScalatraExample extends ScalatraServlet {
+ get("/") {
+ <h1>Hello, world!</h1>
+ }
+ }
- package org.scalatra
+## Quick start
- class ScalatraExample extends ScalatraServlet {
+ 1. Git-clone the prototype. Alternatively, download and extract a [tarball](https://github.com/scalatra/scalatra-sbt-prototype/tarball/master) or [zip](https://github.com/scalatra/scalatra-sbt-prototype/zipball/master).
- // send a text/html content type back each time
- before {
- contentType = "text/html"
- }
+ $ git clone git://github.com/scalatra/scalatra-sbt-prototype.git my-app
- // parse matching requests, saving things prefixed with ':' as params
- get("/date/:year/:month/:day") {
- <ul>
- <li>Year: {params("year")}</li>
- <li>Month: {params("month")}</li>
- <li>Day: {params("day")}</li>
- </ul>
- }
+ 2. Change directory into your clone.
- // produce a simple HTML form
- get("/form") {
- <form action='/post' method='POST'>
- Post something: <input name='submission' type='text'/>
- <input type='submit'/>
- </form>
- }
+ $ cd my-app
- // handle POSTs from the form generated above
- post("/post") {
- <h1>You posted: {params("submission")}</h1>
- }
+ 3. Launch [SBT](http://code.google.com/p/simple-build-tool).
- // respond to '/' with a greeting
- get("/") {
- <h1>Hello world!</h1>
- }
+ $ sbt
- // send redirect headers
- get("/see_ya") {
- redirect("http://google.com")
- }
+ 4. Fetch the dependencies.
- // set a session var
- get("/set/:session_val") {
- session("val") = params("session_val")
- <h1>Session var set</h1>
- }
+ > update
- // see session var
- get("/see") {
- session.getOrElse("val", "No session var set")
- }
+ 5. Start Jetty, enabling continuous compilation and reloading.
- // Actions that return byte arrays render a binary response
- get("/report.pdf") {
- contentType = "application/pdf"
- val pdf = generatePdf()
- pdf.toBytes
- }
+ > jetty-run
+ > ~prepare-webapp
- notFound {
- response.setStatus(404)
- "Not found"
- }
+ 6. Browse to http://localhost:8080/.
+
+ 7. Start hacking on `src/main/scala/MyScalatraFilter.scala`.
+
+## Community
+
+### Mailing list
+
+The [scalatra-user](http://groups.google.com/group/scalatra-user) mailing list is open to anybody. It is the best place to ask questions, so everybody can see the answer.
+
+### IRC channel
+
+For those topics that are easier to discuss in real time, or just to hang out with some fun people, join us on the #scalatra channel on irc.freenode.org.
+
+## Routes
+
+In Scalatra, a route is an HTTP method paired with a URL matching pattern.
+
+ get("/") {
+ // show something
+ }
+
+ post("/") {
+ // submit/create something
}
+ put("/") {
+ // update something
+ }
-Quick Start
-===========
-1. __Install Java__
+ delete("/") {
+ // delete something
+ }
- You'll need Java installed; I have it running with 1.5
+### Route order
-2. __Install simple-build-tool__
+The first matching route is invoked. Routes are matched from the bottom up. _This is the opposite of Sinatra._ Route definitions are executed as part of a Scala constructor; by matching from the bottom up, routes can be overridden in child classes.
- Scalatra uses sbt (0.7 or above), a fantastic tool for building Scala programs. For instructions, see [the sbt site](http://code.google.com/p/simple-build-tool/wiki/Setup)
+### Path patterns
-3. __Run sbt__
+Path patterns add parameters to the `params` map. Repeated values are accessible through the `multiParams` map.
- In the directory you downloaded Scalatra to, run `sbt`.
- sbt will download core dependencies, and Scala itself if it needs to.
+#### Named parameters
-4. __Download dependencies__
+Route patterns may include named parameters:
- At the sbt prompt, type `update`. This will download required dependencies.
+ get("/hello/:name") {
+ // Matches "GET /hello/foo" and "GET /hello/bar"
+ // params("name") is "foo" or "bar"
+ <p>Hello, {params("name")}</p>
+ }
-5. __Try it out__
+#### Wildcards
- At the sbt prompt, type `jetty-run`. This will run Scalatra with the example servlet on port 8080.
+Route patterns may also include wildcard parameters, accessible through the `splat` key.
-6. __Navigate to http://localhost:8080__
+ get("/say/*/to/*) {
+ // Matches "GET /say/hello/to/world"
+ multiParams("splat") # == Seq("hello", "world")
+ }
- You should see "Hello world." You can poke around the example code in example/src/main/scala/TemplateExample.scala to see what's going on.
+ get("/download/*.*) {
+ // Matches "GET /download/path/to/file.xml"
+ multiParams("splat") # == Seq("path/to/file", "xml")
+ }
+#### Regular expressions
-Maven Repository
-================
+The route matcher may also be a regular expression. Capture groups are accessible through the `captures` key.
-To make usage of Scalatra as a dependency convenient, Maven hosting is now available courtesy of [Sonatype](https://docs.sonatype.com/display/NX/OSS+Repository+Hosting).
+ get("""^\/f(.*)/b(.*)""".r) {
+ // Matches "GET /foo/bar"
+ multiParams("captures") # == Seq("oo", "ar")
+ }
-* [Releases](https://oss.sonatype.org/content/repositories/releases)
-* [Snapshots](https://oss.sonatype.org/content/repositories/snapshots)
+### Conditions
-Supported Methods
-=================
+Routes may include conditions. A condition is any expression that returns Boolean. Conditions are evaluated by-name each time the route matcher runs.
-* __before__
+ get("/foo") {
+ // Matches "GET /foo"
+ }
- Run some block before a request is returned.
+ get("/foo", request.getRemoteHost == "127.0.0.1") {
+ // Overrides "GET /foo" for local users
+ }
-* __get(`path`)__
+Multiple conditions can be chained together. A route must match all conditions:
- Respond to a GET request.
+ get("/foo", request.getRemoteHost == "127.0.0.1", request.getRemoteUser == "admin") {
+ // Only matches if you're the admin, and you're localhost
+ }
- Specify the route to match, with parameters to store prefixed with : like Sinatra.
- "/match/this/path/and/save/:this" would match that GET request, and provide you with a
- params("this") in your block.
+No path pattern is necessary. A route may consist of solely a condition:
-* __post(`path`)__
+ get(isMaintenanceMode) {
+ <h1>Go away!</h1>
+ }
- Respond to a POST request.
+### Actions
- Posted variables are available in the `params` hash.
+Each route is followed by an action. An Action may return any value, which is then rendered to the response according to the following rules:
-* __delete(`path`)__
+<dl>
+ <dt>`Array[Byte]`</dt>
+ <dd>If no content-type is set, it is set to `application/octet-stream`. The byte array is written to the response's output stream.</dd>
- Respond to a DELETE request.
+ <dt>`NodeSeq`</dt>
+ <dd>If no content-type is set, it is set to`text/html`. The node sequence is converted to a string and written to the response's writer.</dd>
-* __put(`path`)__
+ <dt>`Unit`</dt>
+ <dd>This signifies that the action has rendered the entire response, and no further action is taken.</dd>
- Respond to a PUT request.
+ <dt>Any</dt>
+ <dd>For any other value, if the content type is not set, it is set to `text/plain`. The value is converted to a string and written to the response's writer</dd>.
+</dl>
-* __error__
+This behavior may be customized for these or other return types by overriding `renderResponse`.
- Run some block when an error is caught. The error is available in the variable `caughtThrowable`.
-* __after__
+## Filters
- Run some block after the matching get/post/delete/put block is run.
+Before filters are evaluated before each request within the same context as the routes.
-Sessions
-========
-Session support has recently been added. To see how to use sessions in your Scalatra apps, check out the test servlet, at core/src/test/scala/ScalatraTest.scala
+ before {
+ // Default all responses to text/html
+ contentType = "text/html"
+ }
-Flash scope
-===========
-Flash scope is available by mixing in FlashScopeSupport, which provides a mutable map named `flash`. Values put into flash scope during the current request are stored in the session through the next request and then discarded. This is particularly useful for messages when using the [Post/Redirect/Get](http://en.wikipedia.org/wiki/Post/Redirect/Get) pattern.
+After filters are evaluated after each request, but before the action result is rendered, within the same context as the routes.
-File Upload Support
-===================
-File uploads are now supported.
+ after {
+ if (response.status >= 500)
+ println("OMG! ONOZ!")
+ }
-File upload dependencies
-------------------------
-- scalatra-fileupload.jar
-- commons-fileupload.jar
-- commons-io.jar
+## Halting
-Usage
------
-* Mix in `org.scalatra.fileupload.FileUploadSupport`.
-* Be sure that your form has an enctype of `multipart/form-data`
-* Uploaded files will be available in a map of `fileParams` or `fileMultiParams`
+To immeidately stop a request within a filter or route:
-Example
--------
+ halt()
- import org.scalatra.ScalatraServlet
- import org.scalatra.fileupload.FileUploadSupport
+You can also specify the status:
- class MyApp extends ScalatraServlet with FileUploadSupport {
- get("/") {
- <form method="post" enctype="multipart/form-data">
- <input type="file" name="foo" />
- <input type="submit" />
- </form>
- }
+ halt(410)
- post("/") {
- processFile(fileParams("file"))
+Or the body:
+
+ halt("This will be the body")
+
+Or both:
+
+ halt(401, "Go away!")
+
+## Passing
+
+A route can punt processing to the next matching route using pass. Remember, unlike Sinatra, routes are matched from the bottom up.
+
+ get("/guess/*") {
+ "You missed!"
+ }
+
+ get("/guess/:who") {
+ params("who") match {
+ case "Frank" => pass()
+ case _ => "You got me!"
}
}
-Scalate Integration
-===================
-Scalatra has experimental support for integration with [Scalate](http://scalate.fusesource.org).
+The route block is immediately exited and control continues with the next matching route. If no matching route is found, a 404 is returned.
-Scalate Dependencies
---------------------
-- scalatra-scalate.jar
-- scalate-core-1.2.jar
-- a [slf4j binding](http://www.slf4j.org/manual.html#binding); I like logback
+## Accessing the Servlet API
-Setup
------
-* Mix in ScalateSupport
-* Create template in src/main/webapp
-* Call renderTemplate with a path to the template (relative to webapp) and attributes
+### HttpServletRequest
+
+The request is available through the `request` variable. The request is implicitly extended with the following methods:
+
+1. `body`: to get the request body as a string
+2. `isAjax`: to detect AJAX requests
+3. `cookies` and `multiCookies`: a Map view of the request's cookies
+4. Implements `scala.collection.mutable.Map` backed by request attributes
+
+### HttpServletResponse
+
+The response is available through the `response` variable.
+
+### HttpSession
-Example
--------
+The session is available through the `session` variable. The session implicitly implements `scala.collection.mutable.Map` backed by session attributes. To avoid creating a session, it may be accessed through `sessionOption`.
- import org.scalatra.ScalatraServlet
- import org.scalatra.scalate.ScalateSupport
+### ServletContext
+
+The servlet context is available through the `servletContext` variable. The servlet context implicitly implements `scala.collection.mutable.Map` backed by servlet context attributes.
+
+## Configuration
+
+The environment is defined by:
+1. The `org.scalatra.environment` system property.
+2. The `org.scalatra.environment` init property.
+3. A default of `development`.
+
+If the environment starts with "dev", then `isDevelopmentMode` returns true. This flag may be used by other modules, for example, to enable the Scalate console.
+
+## Error handling
+
+Error handlers run within the same context as routes and before filters.
+
+### Not Found
+
+Whenever no route matches, the `notFound` handler is invoked:
+
+ notFound {
+ <h1>Not found. Bummer.</h1>
+ }
+
+### Error
+
+The `error` handler is invoked any time an exception is raised from a route block or a filter. The throwable can be obtained from the `caughtThrowable` instance variable. This variable is not defined outside the `error` block.
+
+ error {
+ log.error(caughtThrowable)
+ redirect("http://www.sadtrombone.com/")
+ }
+
+## Flash scope
+
+Flash scope is available by mixing in `FlashScopeSupport`, which provides a mutable map named `flash`. Values put into flash scope during the current request are stored in the session through the next request and then discarded. This is particularly useful for messages when using the [Post/Redirect/Get](http://en.wikipedia.org/wiki/Post/Redirect/Get) pattern.
+
+## Templating with Scalate
+
+Scalatra provides optional support for <a href="http://scalate.org/">Scalate</a>, a Scala template engine.
+
+1. Depend on scalatra-scalate.jar and a [slf4j binding](http://www.slf4j.org/manual.html#binding). In your SBT build:
+
+ val scalatraScalate = "org.scalatra" % "scalatra-scalate" % scalatraVersion
+ val slf4jBinding = "ch.qos.logback" % "logback-classic" % "0.9.25" % runtime
+
+2. Extend your application with `ScalateSupport`
+
+ import org.scalatra._
+ import org.scalatra.scalate._
+
+ class MyApplication extends ScalatraServlet with ScalateSupport {
+ // ....
+ }
+
+3. A template engine is created as the `templateEngine` variable. This can be used to render templates and call layouts.
+
+ get("/") {
+ templateEngine.layout("index.scaml", "content" -> "yada yada yada")
+ }
+
+Additionally, `createRenderContext` may be used to create a render context for the current request and response.
+
+Finally, the [Scalate Console](http://scalate.fusesource.org/documentation/console.html) is enabled in development mode to display any unhandled exceptions.
+
+## File upload support
+
+Scalatra provides optional support for file uploads with <a href="http://commons.apache.org/fileupload/">Commons FileUpload</a>.
+
+1. Depend on scalatra-fileupload.jar. In your SBT build:
+
+ val scalatraFileUpload = "org.scalatra" % "scalatra-fileupload" % scalatraVersion
+
+2. Extend your application with `FileUploadSupport`
+
+ import org.scalatra.ScalatraServlet
+ import org.scalatra.fileupload.FileUploadSupport
+
+ class MyApp extends ScalatraServlet with FileUploadSupport {
+ // ...
+ }
+
+3. Be sure that your form is of type `multipart/form-data`:
- class MyApp extends ScalatraServlet with ScalateSupport {
get("/") {
- renderTemplate("index.scaml", "content" -> "yada yada yada")
+ <form method="post" enctype="multipart/form-data">
+ <input type="file" name="foo" />
+ <input type="submit" />
+ </form>
+ }
+
+4. Your files are available through the `fileParams` or `fileMultiParams` maps:
+
+ post("/") {
+ processFile(fileParams("file"))
}
- }
-Testing Your Scalatra Applications
-==================================
+## Testing Your Scalatra Applications
Scalatra includes a test framework for writing the unit tests for your Scalatra application. The framework lets you send requests to your app and examine the response. It can be mixed into the test framework of your choosing; integration with [ScalaTest](http://www.scalatest.org/) and [Specs](http://code.google.com/p/specs/) is already provided. ScalatraTests supports HTTP GET/POST tests with or without request parameters and sessions. For more examples, please refer to core/src/test/scala.
@@ -277,6 +379,13 @@ Other test frameworks
### Usage guide
Create an instance of org.scalatra.test.ScalatraTests. Be sure to call `start()` and `stop()` before and after your test suite.
+## Maven Repository
+
+To make usage of Scalatra as a dependency convenient, Maven hosting is now available courtesy of [Sonatype](https://docs.sonatype.com/display/NX/OSS+Repository+Hosting).
+
+* [Releases](https://oss.sonatype.org/content/repositories/releases)
+* [Snapshots](https://oss.sonatype.org/content/repositories/snapshots)
+
Authentication
==============
@@ -284,43 +393,62 @@ There is a new authentication middleware in the auth directory, to be documented
To use it from an SBT project, add the following to your project:
- val auth = "org.scalatra" %% "scalatra-auth" % "2.0.0-SNAPSHOT"
+ val auth = "org.scalatra" %% "scalatra-auth" % scalatraVersion
+
+## FAQ
+
+### It looks neat, but is it production-ready?
+
+- It is use in the backend for (LinkedIn Signal)[http://sna-projects.com/blog/2010/10/linkedin-signal-a-look-under-the-hood/].
+
+- [ChaCha](http://www.chacha.com/) is using it in multiple internal applications.
-ScalatraServlet vs. ScalatraFilter
-==================================
+- A project is in currently development to support a site with over one million unique users.
+
+Are you using Scalatra in production? Tell us your story on the [mailing list](http://groups.google.com/group/scalatra-user/).
+
+### ScalatraServlet vs. ScalatraFilter
The main difference is the default behavior when a route is not found. A filter will delegate to the next filter or servlet in the chain (as configured by web.xml), whereas a ScalatraServlet will return a 404 response.
Another difference is that ScalatraFilter matches routes relative to the WAR's context path. ScalatraServlet matches routes relative to the servlet path. This allows you to mount multiple servlets under in different namespaces in the same WAR.
-Use ScalatraFilter if:
+### Use ScalatraFilter if:
- You are migrating a legacy application inside the same URL space
- You want to serve static content from the WAR rather than a dedicated web server
-Use ScalatraServlet if:
+### Use ScalatraServlet if:
- You want to match routes with a prefix deeper than the context path.
-Miscellaneous
-=============
-While Scalatra can be run standalone for testing and meddling, you can also package it up in a .jar for use in other projects. At the sbt prompt, type `package`. Scalatra's only dependency is a recent version of the servlet API. For more information, see the [sbt site](http://code.google.com/p/simple-build-tool/)
+## Migrations
-Migrating from Step to Scalatra
-===============================
-Scalatra was renamed from Step to Scalatra to avoid a naming conflict with (an unrelated web framework)[http://sourceforge.net/stepframework]. scalatra-1.2.0 is identical to step-1.2.0 with the following exceptions:
+### Step to Scalatra
+
+Scalatra was renamed from Step to Scalatra to avoid a naming conflict with (an unrelated web framework)[http://sourceforge.net/stepframework]. scalatra-1.2.1 is identical to step-1.2.0 with the following exceptions:
1. The package has changed from `com.thinkminimo.step` to `org.scalatra`.
1. The `Step` class has been renamed to `ScalatraServlet`.
1. All other `Step*` classes have been renamed to `Scalatra*`.
-Props
-=====
+### scalatra-2.0.0.M1 to scalatra-2.0.0.M2
+
+1. Session has been retrofitted to a Map interface. `get` now returns an option instead of the value.
+
+## Credits
+
+### Committers
+
+- [Gabriele Renzi](http://www.riffraff.info/), who started it all with his [blog posts](http://www.riffraff.info/tags/step)
+- [Alan Dipert](http://alan.dipert.org/)
+- [Ross A. Baker](http://www.rossabaker.com/)
+- [Hiram Chirino](http://hiramchirino.com)
+- [Ivan Porto Carrero](http://flanders.co.nz)
+
+### Other contributors
-- [Gabriele Renzi](http://www.riffraff.info/) for the inspirational blog post and continual help
-- [Ross A. Baker](http://www.rossabaker.com/) for porting to 2.8 and loads of other patches and help
-- [Mark Harrah](http://github.com/harrah) for help on the sbt mailing list and for creating sbt. Ant+Ivy by itself was a total bitch.
+- [The Sinatra Project](http://sinatrarb.com/), whose excellent framework, test suite, and documentation we've shamelessly copied.
+- [Mark Harrah](http://github.com/harrah) for his support on the SBT mailing list.
- [Yusuke Kuoka](http://github.com/mumoshu) for adding sessions and header support
- [Miso Korkiakoski](http://github.com/mwing) for various patches.
- [Ivan Willig](http://github.com/iwillig) for his work on [Scalate](http://scalate.fusesource.org/) integration.
-- [Hiram Chirino](http://hiramchirino.com) for Maven integration and the new name.
- [Phil Wills](http://github.com/philwills) for the path parser cleanup.
-- [Ivan Porto Carrero](http://flanders.co.nz) for the authentication framework.
Please sign in to comment.
Something went wrong with that request. Please try again.