Respite controllers provide a powerful routing DSL & come standard with type-safe, JSON to case class conversion, including field-level validation.
Other out-of-the-box features include:
For out-of-the-box CRUD services for your Models, create and register an instance of a RestController
. This can be done in one of two ways: declaring a new Class
extending RestController
(recommended for all but the simplest of controllers) or creating a new value object of type RestController
:
The following examples highlight the two forms:
New Class
class ProductController(repository: ReactiveRepository[Product, BSONObjectID])(override implicit val bindingModule: BindingModule, override implicit val tag: ClassTag[Product], override implicit val objectIdConverter: String => BSONObjectID) extends RestController[Product, BSONObjectID]("products", Product.format, repository)
Value Object
def route = new RestController[Product, BSONObjectID]("products", Product.format, new ProductRepository) with MetricsRestSupport[Product, BSONObjectID], "/products")
The latter form is generally only useful for fairly uninteresting CRUD services.
If you don't require Model
persistence, mixin the PlayJsonSupport[T]
Trait, specifying the type that will be converted to/from JSON.
val playServlet = new TestServlet with PlayJsonSupport[User]
Once you have created your Controller
, simply add an instance of one with the context.mount()
method in your ScalatraBootstrap
class:
class ScalatraBootstrap extends LifeCycle {
protected implicit def executor: ExecutionContext = ExecutionContext.global
override def init(context: ServletContext) {
// Import implicit definitions into Scope
implicit val bindingModule = ProductionConfigurationModule // DI Configuration object
import au.com.onegeek.respite.models.ModelJsonExtensions._ // JSON extensions
// Add Controllers here
context.mount(new RestController[User, BSONObjectID]("users", User.format, new UserRepository), "/users/")
context.mount(new MyModelController(new MyRepository), "/mymodels/")
}
}
API Keys are a simple way to control access to your APIs, but
Useful during development, these keys are essentially an in-memory map that are not persisted between application restarts.
Create an instance of a ConfigAuthenticationStrategy
setting the keys in the map:
// Authentication API with default keys
object ConfigAuthStrategy extends ConfigAuthenticationStrategy {
override var keys = Map("admin" -> ApiKey(application = "admin", description = "Test App", key = "testkey")) ++
Map("murray" -> ApiKey(application = "bill", description = "Foo App", key = "murray"))
}
Ensure a local Mongo database is available with the table 'apikeys' available. Respite provides a ApiKeyRepository
class to persist keys, which you can use when creating the DatabaseAuthenticationStrategy
object:
val repository = new ApiKeyRepository
override implicit val authenticationStrategy = new DatabaseAuthenticationStrategy(repository)
...
}
Once you've created the AuthenticationStrategy
object, mixin the Authentication
Trait to your Controller
and set the authenticationStrategy
to your new configuration object:
class UserController(repository: ReactiveRepository[User, BSONObjectID])(override implicit val bindingModule: BindingModule, override implicit val tag: ClassTag[User], override implicit val objectIdConverter: String => BSONObjectID) extends RestController[User, BSONObjectID]("users", User.format, repository)[User, BSONObjectID] with Authentication {
override implicit val authenticationStrategy = ConfigAuthStrategy
}
Respite provides a simple REST API to manage API tokens at run time:
Method | Path | Body |
---|---|---|
GET | ||
DELETE | ||
POST | {"application" : "kickass", "key" : "pants", "description":"We sell awesome pants" } |
RestController
classes are already instrumented with the typed MetricsRestSupport[T]
Trait, containing a default health check implementation. For other Controller
classes, use the MetricsSupport
Trait.
As with metrics, RestController
classes require a the CachingRouteSupport
and non-RestController
Route
s should use CachingSupport
Trait. However, unlike Metrics, caching is disabled by default.
The caching DSL is fairly straightforward, wrapping a block with cache(key)
will return a Future[Any]
. The following example wraps the contents of a POST
operation (something which is not automatically cached for obvious reasons):
post("/foobar") {
cache("es") {
Future {
logger.info("About to find all")
repository.findAll
}
}
}
The cache can be completely expired via the clear
method on the cache
property. A REST service also exists at DELETE /cache/
and DELETE /cache/:key
.
By default, responses are cached forever, unless the cache is invalidated by way of a DELETE
or POST
operation.If you'd like to change this behaviour, simply override the following properties of any class instrumented via CachingSupport
.
override val timeToLive = 300 seconds
override val timeToIdle = 60 seconds
Mixin LoggingSupport
to access a Logback logger
object with the standard methods. By default, this will be sent to stdout
(see logs for best practice on 12 factor apps) but can be easily configured to log to another location.
// Creates a route on "/logme" which will log to console on request
get("/logme") {
logger.debug("This is a debug log on path `/logme`")
}