Fork me on GitHub

Documentation

Documentation here is always for the latest version of Spark. We don’t have the capacity to maintain separate docs for each version, but Spark is always backwards compatible.

Docs for (spark-kotlin) will arrive here ASAP. You can follow the progress of spark-kotlin on (GitHub)

If you like spark-kotlin star us and help the community grow:

Getting started

1: Create a new gradle project and add the dependency to your build.gradle file.

'com.spinoza:spinoza:1.0.0-alpha'

Not familiar with Maven? Click here for more detailed instructions.

Other dependency managers:

Gradle : compile "com.sparkjava:spark-core:2.7.1" // add to build.gradle (for Java users)
Gradle : compile "com.sparkjava:spark-kotlin:1.0.0-alpha" // add to build.gradle (for Kotlin users)
   Ivy : <dependency org="com.sparkjava" name="spark-core" rev="2.7.1" conf="build" /> // ivy.xml
   SBT : libraryDependencies += "com.sparkjava" % "spark-core" % "2.7.1" // build.sbt

2: Start coding:

import spinoza.*

fun main(args: Array<String>) {
    get("/salve") {
        "Salve Orbis Terrarum"
    }
}

3: Run and view:

http://localhost:4567/salve

Stopping the Server

By calling the stop() method the server is stopped and all routes are cleared.

stop()

Wait, what about starting the server?

The server is automatically started when you do something that requires the server to be started (i.e. declaring a route or setting the port).
You can also manually start the server by calling init().

Routes

The main building block of a Spinoza application is a set of routes. A route is made up of three simple pieces:

Routes are matched in the order they are defined. The first route that matches the request is invoked.
Always statically import Spark methods to ensure good readability:

get("/") {
    // Show something
}

post("/") {
    // Create something
}

put("/") {
    // Update something
}

delete("/") {
    // Annihilate something
}

options("/") -> {
    // Appease something
}

Route patterns can include named parameters, accessible via params():

// matches "GET /saymy/foo" and "GET /saymy/bar"
// params(":name") is 'foo' or 'bar'
get("/saymy/:name") {
    params(":name")
}

Route patterns can also include splat (or wildcard) parameters. These parameters can be accessed by using the splat() method on the request object:

// matches "GET /say/hello/to/world"
// TODO: splat is not done
get("/say/*/to/*") {
    "Number of splat parameters: " + splat()?.size
}

Path groups

If you have a lot of routes, it can be helpful to separate them into groups. This can be done by calling the path() method, which takes a String prefix and gives you a scope to declare routes and filters (or nested paths) in:

// TODO: NOT DONE IN SPINOZA YET!
path("/api", () -> {
    before("/*", (q, a) -> log.info("Received api call"));
    path("/email", () -> {
        post("/add",       EmailApi.addEmail);
        put("/change",     EmailApi.changeEmail);
        delete("/remove",  EmailApi.deleteEmail);
    });
    path("/username", () -> {
        post("/add",       UserApi.addUsername);
        put("/change",     UserApi.changeUsername);
        delete("/remove",  UserApi.deleteUsername);
    });
});

Implicitly available functionality in route context

Request and response functionality is provided implicitly in the route context:

// request related
attributes()             // the attributes list
attribute("foo")         // value of foo attribute
attribute("A", "V")      // sets value of attribute A to V
contentLength()          // length of request body
contentType()            // content type of request.body
contextPath()            // the context path, e.g. "/hello"
host()                   // the host, e.g. "example.com"
ip()                     // client IP address
params("foo")            // value of foo path parameter
params()                 // map with all parameters
pathInfo()               // the path info
port()                   // the server port
protocol()               // the protocol, e.g. HTTP/1.1
queryMap()               // the query map
queryMap("foo")          // query map for a certain parameter
queryParams()            // the query param list
queryParams("FOO")       // value of FOO query param
queryParamsValues("FOO") // all values of FOO query param
requestMethod()          // The HTTP method (GET, ..etc)
scheme()                 // "http"
servletPath()            // the servlet path, e.g. /result.jsp
session()                // session management
splat()                  // splat (*) parameters
uri()                    // the uri, e.g. "http://example.com/foo"
url()                    // the url. e.g. "http://example.com/foo"
userAgent()              // user agent

request                  // the request itself
    .headers()           // the HTTP header list
    .headers("BAR")      // value of BAR header
    .body()              // request body sent by the client
    .bodyAsBytes()       // request body as bytes
    .cookies("foo")      // the cookie `foo`
    .cookies()           // request cookies sent by the client

// response related
redirect("/example");        // browser redirect to /example
redirect("/example", 302)    // redirect to /example with status code 302
status()                     // get the response status
status(401)                  // set status code to 401
type()                       // get the response content type
type("text/xml")             // set the response content type to text/xml

response                              // the response itself
    .body()                           // get response content
    .body("Hello")                    // sets content to Hello
    cookies()                         // get map of all request cookies
    cookie("foo");                    // access request cookie by name
    cookie("foo", "bar")              // set cookie with a value
    cookie("foo", "bar", 3600)        // set cookie with a max-age
    cookie("foo", "bar", 3600, true)  // secure cookie
    removeCookie("foo")               // remove cookie
    .header("FOO", "bar")             // sets header FOO with value bar

Sessions

Server created session functionality is implicitly available in route countext

session()                         // get session
session(create = true)            // create and return session
session().attribute("user")       // Get session attribute 'user'
session().attribute("user","foo") // Set session attribute 'user'
session().removeAttribute("user") // Remove session attribute 'user'
session().attributes()            // Get all session attributes
session().id()                    // Get session id
session().isNew()                 // Check if session is new
session().raw()                   // Return servlet object

Halting

To immediately stop a request within a filter or route use halt():

halt()                // halt 
halt(401)             // halt with status
halt("Body Message")  // halt with message
halt(401, "Go away!") // halt with status and message

halt() is not intended to be used inside exception-mappers. Halt throws a halt exception so if the halt is within a try-catch catching this type of exception the halt won’t work.

Filters

Before-filters are evaluated before each request, and can read the request and read/modify the response.
To stop execution, use halt():

// matches all requests
before {
    if (!authenticated()) {
        halt(401, "Go Away!")
    }
}

// matches requests on /protected/*
before("/protected/*") {
    if (!authenticated()) {
        halt(401, "Go Away!")
    }
}

After-filters are evaluated after each request, and can read the request and read/modify the response:

after {
    response.header("foo", "bar")
}

Finally-filters are evaluated after after-filters. Think of it as a “finally” block.

finally { 
    response.header("foo", "bar")    
}
    
finally("/foo") {
    response.header("foo", "bar")
}

Redirects

You can trigger a browser redirect with the redirect method on the response:

redirect("/bar")

You can also trigger a browser redirect with specific HTTP 3XX status code:

redirect("/bar", 301) // moved permanently

Redirect API

There is also a convenience API for redirects which can be used directly without the response:

redirect {
    any(
            "/from" to "/hello",
            "/hi" to "/hello"
    )
    get(
            "/source" to "/target"
    )
    post(
            "/gone" to "/new"
    )
}

Custom error handling

Not found (code 404) handling

notFound {
    "404 - Spinoza couldn't find what you're looking for"        
}

Internal server error (code 500) handling

internalServerError {
    "Very internal server error"        
}

Exception Mapping

To handle exceptions of a configured type for all routes and filters:

get("/exception") {
    throw AuthException("protection")
}

exception(AuthException::class) {
    status(401)
    response.body(exception.message)
}

Configuration DSL

If you need to configure Spinoza use the DSL for this where you can configure port, ip, threadpool, HTTPS and static files.

// Static API
config {
    port = 5500
    ipAddress = "0.0.0.0"
    threadPool {
        maxThreads = 10
        minThreads = 5
        idleTimeoutMillis = 1000
    }
    secure {
        keystore {
            file = "/etc/secure/keystore"
            password = "hardtocrack"
        }
        truststore {
            file = "/etc/secure/truststore"
            password = "otherdifficultpassword"
        }
        needsClientCert = false
    }
    staticFiles {
        location = "/public"
        externalLocation "/var/static"
        expiryTime = 36000.seconds
        headers(
                "description" to "static content",
                "licence" to "free to use"
        )
        mimeTypes(
                "cxt" to "text/html"
        )
    }
}

// Instance API
val http = ignite {
    port = 5500
    ipAddress = "0.0.0.0"
    threadPool {
        maxThreads = 10
        minThreads = 5
        idleTimeoutMillis = 1000
    }
    secure {
        keystore {
            file = "/etc/secure/keystore"
            password = "hardtocrack"
        }
        truststore {
            file = "/etc/secure/truststore"
            password = "otherdifficultpassword"
        }
        needsClientCert = false
    }
    staticFiles {
        location = "/public"
        externalLocation "/var/static"
        expiryTime = 36000.seconds
        headers( // custom headers
                "description" to "static content",
                "licence" to "free to use"
        )
        mimeTypes(
                "cxt" to "text/html"
        )
    }
}

Initialization must be configured before route mapping. If your application has no routes, init() must be called manually after location is set.

Embedded web server

Standalone Spinoza runs on an embedded Jetty web server.

Port

By default, Spinoza runs on port 4567. If you want to set another port, use init DSL.

Waiting for Initialization

You can use the method awaitInitialization() to check if the server is ready to handle requests. This is usually done in a separate thread, for example to run a health check module after your server has started.
The method causes the current thread to wait until the embedded Jetty server has been initialized. Initialization is triggered by defining routes and/or filters. So, if you’re using just one thread don’t put this before you define your routes and/or filters.

awaitInitialization() // Wait for server to be initialized

WebSockets

WebSockets provide a protocol full-duplex communication channel over a single TCP connection, meaning you can send message back and forth over the same connection.

WebSockets only works with the embedded Jetty server, and must be defined before regular HTTP routes. To create a WebSocket route, use the webSocket DSL

webSocket("/ws") {
    opened {
        println("[opened] session = " + session)
    }
    received {
        println("[received] message = $message, session = $session")
    }
    closed {
        println("[closed] session = $session, reason = $reason")
    }
    error {
        println("[error] cause = " + cause)
    }
}
    
init() // Needed only if you don't define any HTTP routes after your WebSocket routes

Other web server

To run Spinoza on another web server (instead of the embedded jetty server), an implementation of the interface spark.servlet.SparkApplication is needed. You have to initialize your routes in the init() method, and the following filter might have to be configured in your web.xml:

<filter>
    <filter-name>SparkFilter</filter-name>
    <filter-class>spark.servlet.SparkFilter</filter-class>
    <init-param>
        <param-name>applicationClass</param-name>
        <param-value>com.company.YourApplication</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>SparkFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

GZIP

GZIP is done automatically if it’s in both the request and the response headers. This usually only means that you have to set it in your response headers.

If you want to GZIP a single response, you can add it manually to your route:

get("/aliquid") {
    // ...
    response.header("Content-Encoding", "gzip");
}

If you want to GZIP everything, you can use an after or finally filter

finally {
    response.header("Content-Encoding", "gzip");
}

Examples and FAQ

Examples can be found on the project’s page on GitHub.