Documentation

Getting Started

Getting started with SparkGS could not be easier. Follow these four steps:

1. Install Gosu

2. Save this code to spark.gsp


    classpath "org.gosu-lang.sparkgs:sparkgs:0.1-alpha2"
    extends sparkgs.SparkGSFile

    get('/', \-> "Hello World")
      	

3. Fire it up: $ gosu spark.gsp

4. Check it out at http://localhost:4567/

Defining Routes

In SparkGS, as with SinatraRB and SparkJava, HTTP routes are defined with three components:

In SparkGS, the HTTP verb is a method call, the path is a string argument, and the handler is (usually) a block.

Here are a few example routes:


  // Routes a GET root of the webserver to a block, which simply returns "Hello World"
  get('/', \-> "Hello World")

  //  Routes a POST to /inbox to the the InboxHandler class
  post('/inbox', \-> InboxHandler.acceptInput(Request.Body) )

  //  Routes all verbs to /contacts to the the ContactsHandler class
  handle('/contacts', \-> new ContactsHandler().handleRequest(Request, Response) )

  //  Routes a GET to a Gosu Template
  get('/input_form', \-> view.InputForm.render(Writer) )

  

So, for each HTTP verb, there is a corresponding method, and each method accepts two arguments:

  • A path defined by a string
  • A handler (typically a block)

Path Variables

SparkJava (and, hence, SparkGS) support path variables and splats, which become available via the Params property or Splat property during a request.


  get('/contact/:id', \-> "You asked for a contact with the ID ${Params['id']}")

  get('/file/*', \-> "You asked for the file with path ${Request.Splat}")
  

Request Properties

In SparkGS programs, there are a few properties and methods available to for used in your handlers:

  • Request - Access to the HTTP Request info
  • Response - Access to the HTTP Response
  • Writer - Access to the Writer for the HTTP Response
  • Cookies - Access to the Cookies for the HTTP Request and Response
  • Session - Access to the HTTP Session
  • redirect(url) - Redirect to the given URL

These objects are documented more fully below, but most of them behave in the way you would expect.

Accesing Request Info Elsewhere

Note that any object can get access to these properties by simply implementing the IHasRequestContext interface, which mixes these properties in via the RequestContextEnhancement enhancement.

So you can move complicated logic out to

Requests

The Request object in SparkGS is mostly a wrapper around the request object in SparkJava, but it surfaces data via properties rather than as methods.

So this SparkJava code:


    System.out.println(request.pathInfo());
  

Becomes:


    print(Request.PathInfo)
  

Here is a list of all the properties and methods of a request object:


  Request.Params                // returns a parameter map
  Request.Attributes            // returns the attributes map
  Request.SparkJavaRequest      // returns the raw SparkJava request
  Request.Session               // returns the session
  Request.IsGet                 // returns true if the current request is an HTTP GET
  Request.IsPost                // returns true if the current request is an HTTP POST
  Request.IsPut                 // returns true if the current request is an HTTP PUT
  Request.IsPatch               // returns true if the current request is an HTTP PATCH
  Request.IsDelete              // returns true if the current request is an HTTP DELETE
  Request.IsHead                // returns true if the current request is an HTTP HEAD
  Request.IsTrace               // returns true if the current request is an HTTP TRACE
  Request.IsConnect             // returns true if the current request is an HTTP CONNECT
  Request.IsOptions             // returns true if the current request is an HTTP OPTIONS
  Request.ContentType           // returns the content type of the request
  Request.Body                  // returns the body of the request
  Request.ContentLength         // returns the length of the body content
  Request.ContextPath           // returns the context path of the request
  Request.Cookies               // returns cookies of the request (see also the CookieJar, which abstracts cookies)
  Request.Headers               // returns a map of all request headers
  Request.Host                  // returns the host of the request
  Request.Ip                    // returns the IP of the request
  Request.PathInfo              // returns the path of the request
  Request.UserAgent             // returns the user agent of the request
  Request.Splat                 // returns the splat of the request, or null if none exists
  Request.Scheme                // returns the scheme of the request (e.g. "http")
  Request.URL                   // returns the full URL of the request
  Request.Port                  // returns the port of the request
  

Responses

Like Requests, SparkGS Responses are largely wrappers around the SparkJava response.

Here is an outline of the available functions:


  Response.Writer                               // returns the writer for this response
  Response.SparkJavaResponse                    // returns the raw SparkJava response object
  Response.Committed                            // true if the current response has been committed
  Response.redirect(to, [optional] code)        // redirects to the given url with an optional code (defaults to 302)
  Response.Status = 401                         // sets the response status to 401
  Response.Type = "application/json"            // sets the response type
  

Handlers Explained

Response handlers are a bit different in SparkGS than they are in SparkJava: they are typically (but not always blocks) rather than anonymous implementations of an interface. As such, they have slightly different characteristics:

  • A handler can simply be a raw string:
    
            get("/foo", "Foo!")
          
  • A handler can be a block that returns a string:
    
            get("/foo", \-> "Foo!")
          
  • A handler can be a block that works with a writer and doesn't actually return anything:
    
            get("/foo", \-> { Writer.append("Foo!") } )
          
  • A handler can be a method reference to any method that does not take any argumetns:
    
            get("/foo", MyController#foo() )
          
    In this case, a new instance of MyController will be created (if the method is non-static) and the foo() method will be invoked.

These are all perfectly reasonable ways to use SparkGS.

Which one you use will depend on how complicated the logic is in your handler.

Templates

One of the best extensions that SparkGS makes to SparkJava isn't even part of the SparkGS codebase: it's the fact that Gosu comes with type-safe templating built into the language via Gosu Template Files!

This allows you to define a Gosu Template file (say at /src/view/MyTemplate.gst)

<%@ params( name : String ) %>
<html>
  <body>
    <h1>Hello there ${name}!</h1>
  </body>
</html>

And then invoke the template from your handler:


  get("/hello", \-> view.MyTemplate.render(Writer, Params['name']) )

Very clean!

Layouts

SparkGS supports a simple layout mechanism:

In your spark file, set the Layout property to be anything that satisfies the sparkgs.Layout structure. This is typically via a Gosu Template File:


  Layout =  view.MyLayout
  //... route definitions

The layout should output the body string where it wants to include the body of the response:

<%@ params( body : String ) %>
<html>
  <body>
    ${body}
  </body>
</html>

Note that you can override the layout at the beginning of an action as well.

Form Helpers

SparkGS has tools for generating some common HTML elements in Gosu templates.

For example, this boilerplate code to list off all possible values of an enum:

  <form action="/root">
    <label for='Country[Continent]'>Continent</label>
    <select name='Country[Continent]'>
      <option value='Africa'>Africa</option>
      //.. 5 more continents
      <option value='Australia'>Asia</option>
    </select>
    <input type='submit' value='Submit'/>
  </form>

Becomes:

  <form action="/root">
    ${SparkGSTemplate.selectInput(Country#Continent)}
    ${SparkGSTemplate.submitInput()}
  </form>

Using Gosu's feature literals you are able to keep your views clean and intuitive.

Cookies

SparkGS abstracts cookies much the same way that Rails does, by unifying both the request and response cookies under one API, the CookieJar.

The Cookie property is a cookie jar. It can be treated as a map, where reads read the request cookies and writes set response cookies:


  get("/get_and_set_cookies", \-> {
    print( Cookies["Cookie1"] )   // reads the request cookie
    Cookies["Cookie2"] = "Yay!"   // writes a response cookie

    // An advanced cookie write, setting timeout, secure and path as well
    Cookies.set("Cookie3", "Yay!", Integer.MAX_INT, true, "/get_and_set_cookies)
  })

Configuration

Although SparkGS is XML-free, there are a few properties you can use to configure your server via the spark file

Port

The port of the server can be set via the Port property. SparkGS will also inspect the environment and if a $PORT variable is set, it will use that as the default.

Layout

As was covered above in the Templates section, you can set the layout by using the Layout property.

Static Files

If you want SparkGS to serve your static assets as well, you can set the directory that they should be served out of via the StaticFiles property:


  StaticFiles = "/public"

  // route definitions...

Advanced Routing

SparkGS gives you some additional route definition tools so you can easily adopt standard URL conventions in your application.

RESTful routing

If you want to define a RESTful set of URLs, you can use the resource() method, which takes a path string and controller object.

  resource("/contacts", new ContactsController())
  

The controller must implement the IResourceController interface. Following Rails conventions, methods are mapped like so:

Verb + Route Method
GET root index()
GET root/new _new()
POST root create()
GET root/:id show(id)
GET root/:id/edit edit(id)
PUT root/:id
POST root/:id
update(id)

Additionally, all additional declared public methods on the controller are mapped to URLs like so:

  • A declared public method foo() is made available at root/foo for all HTTP verbs
  • A declared public method bar(id:String) is made available at root/:id/foo for all HTTP verbs

Almost all controllers will want to implement IHasRequestContext so they can access request values

RPC-style routing

If you wish to simply expose a controllers methods RPC style, you can use the rpc() method:


  rpc("/rpc", new RPCController())

All declared public methods on the class will be made available in the following manner:

For a declared public method methodN() with parameters p1,...,pN, the path root/methodN will handle all HTTP verbs by invoking the method, converting the string value of the HTTP parameter into the type of the the parameter to the method.

Note that RPC controllers do not need to implement any interface, but may want to implement IHasRequestContext in order to get access to the request information.

Deploying To Heroku

To deploy a SparkGS application on Heroku, you will need three additional files:

  • A /Procfile file with the following content:
    
     web:    java -cp "target/dependency/*":target/classes sparkgs.Bootstrap
          
  • A /system.properties file with the following content:
    
     java.runtime.version=1.7
          
  • A /pom.xml file with the following contents:
    ...
        <dependency>
          <groupId>org.gosu-lang.sparkgs</groupId>
          <artifactId>sparkgs</artifactId>
          <version>0.1-alpha</version>
        </dependency>
    ...
      <build>
        <resources>
          <resource>
            <directory>src/main/resources</directory>
          </resource>
          <resource>
            <filtering>false</filtering>
            <directory>src/main/java</directory>
            <includes>
              <include>**/*.gs</include>
              <include>**/*.gsp</include>
              <include>**/*.gst</include>
              <include>**/*.gsx</include>
            </includes>
            <excludes>
              <exclude>**/*.java</exclude>
            </excludes>
          </resource>
        </resources>
        <plugins>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-dependency-plugin</artifactId>
            <version>2.4</version>
            <executions>
              <execution>
                <id>copy-dependencies</id>
                <phase>package</phase>
                <goals>
                  <goal>copy-dependencies</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
        </plugins>
      </build>
    ...
          

With that done, you should be able to simply push to your heroku git URL, and the app will come up!

Examples

Here is an example spark file, showing some basic route definitions:

  classpath "org.gosu-lang.gosu:sparkgs:0.10"
  extends sparkgs.SparkFile

  uses controller.*
  uses view.*
  uses view.layout.*
  uses java.util.*

  //------------------------------------
  // Config
  //------------------------------------

  DefaultLayout = AppLayout // A Gosu Template layout
  StaticFiles = "/public"

  //------------------------------------
  // Routes
  //------------------------------------

  // Root example using a writer
  get("/", \-> Sample.render(Writer))

  // Raw string example
  get("/foo", "Foo!")

  // Post example
  post("/post_to", \-> Params['foo'] )

  // Handle all verbs example
  handle("/handle", \-> Request.IsGet )

  // Handle specific verbs example
  handle("/handle2", \-> Request.IsGet, :verbs = {GET, POST} )

  // Redirect example
  get("/redirect", \-> redirect("/foo") )

  // REST-ful resource example
  resource("/contacts", new ContactsController())

  // RPC example
  rpc("/rpc", new RPCExample())

  // Custom layout example
  get("/custom_layout", \-> {
    Layout = CustomLayout
    Writer.append("Check out my custom layout!")
  })

  // Cookie example
  get("/cookie1", \-> {
    Cookies["Foo"] =  UUID.randomUUID() as String
    redirect("/cookie2")
  })
  get("/cookie2", \-> Cookies["Foo"] )

  // Header example
  get("/header", \-> {
    Headers["X-Foo"] = "Bar"
    return "Sent the header 'X-Foo' along..."
  })