Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/takezoe/resty

Super easy REST API framework for Scala
https://github.com/takezoe/resty

hystrix microservices scala swagger web webframework zipkin

Last synced: 3 months ago
JSON representation

Super easy REST API framework for Scala

Awesome Lists containing this project

README

        

Resty [![Build Status](https://travis-ci.org/takezoe/resty.svg?branch=master)](https://travis-ci.org/takezoe/resty) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.takezoe/resty_2.12/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.takezoe/resty_2.12) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/takezoe/resty/blob/master/LICENSE)
========

Super easy REST API framework for Scala

You can run the sample project by hitting following commands:

```
$ git clone https://github.com/takezoe/resty-sample.git
$ cd resty-sample/
$ sbt ~jetty:start
```

Check APIs via Swagger UI at: `http://localhost:8080/swagger-ui/`.

## Getting started

This is a simplest controller example:

```scala
import com.github.takezoe.resty._

class HelloController {
@Action(method = "GET", path = "/hello/{name}")
def hello(name: String): Message = {
Message(s"Hello ${name}!")
}
}

case class Message(message: String)
```

Define a web listener that registers your controller.

```scala
@WebListener
class InitializeListener extends ServletContextListener {
override def contextDestroyed(sce: ServletContextEvent): Unit = {
}
override def contextInitialized(sce: ServletContextEvent): Unit = {
Resty.register(new HelloController())
}
}
```

Let's test this controller.

```
$ curl -XGET http://localhost:8080/hello/resty
{"message": "Hello resty!" }
```

## Annotations

Resty provides some annotations including `@Action`.

### @Controller

You can add `@Controller` to the controller class to define the controller name and description. They are applied to Swagger JSON.

|parameter |required |description |
|------------|---------|------------------------------|
|name |optional |name of the controller |
|description |optional |description of the controller |

```scala
@Controller(name = "hello", description = "HelloWorld API")
class HelloController {
...
}
```

### @Action

We already looked `@Action` to annotate the action method. It has some more parameters to add more information about the action.

|parameter |required |description |
|------------|---------|-----------------------------------------------------|
|method |required |GET, POST, PUT or DELETE |
|path |required |path of the action (`{name}` defines path parameter) |
|description |optional |description of the method |
|deprecated |optional |if true then deprecated (default is false) |

```scala
class HelloController {
@Action(method = "GET", path = "/v1/hello",
description = "Old version of HelloWorld API", deprecated = true)
def hello() = {
...
}
}
```

### @Param

`@Param` is added to the arguments of the action method to define advanced parameter binding.

|parameter |required |description |
|------------|---------|-----------------------------------------------------|
|from |optional |query, path, header or body |
|name |optional |parameter or header name (default is arg name) |
|description |optional |description of the parameter |

```scala
class HelloController {
@Action(method = "GET", path = "/hello")
def hello(
@Param(from = "query", name = "user-name") userName: String,
@Param(from = "header", name = "User-Agent") userAgent: String
) = {
...
}
}
```

## Types

Resty supports following types as the parameter argument:

- `Unit`
- `String`
- `Int`
- `Long`
- `Boolean`
- `Option[T]`
- `Seq[T]`
- `Array[T]`
- `Array[Byte]` (for Base64 encoded string)
- `AnyRef` (for JSON in the request body)

Also following types are supported as the return value of the action method:

- `String` is responded as `text/plain; charset=UTF-8`
- `Array[Byte]`, `InputStream`, `java.io.File` are responded as `application/octet-stream`
- `AnyRef` is responded as `application/json`
- `ActionResult[_]` is responded as specified status, headers and body
- `Future[_]` is processed asynchronously using `AsyncContext`

## Servlet API

You can access Servlet API by defining method arguments with following types:

- `HttpServletRequest`
- `HttpServletResponse`
- `HttpSession`
- `ServletContext`

```scala
class HelloController {
@Action(method = "GET", path = "/hello")
def hello(request: HttpServletRequest): Message = {
val name = request.getParameter("name")
Message(s"Hello ${name}!")
}
}
```

## Validation

It's possible to validate JSON properties by asserting properties in the constructor of the mapped case class.

```scala
case class Message(message: String){
assert(message.length < 10, "message must be less than 10 charactors.")
}
```

When the parameter value is invalid, Resty responds the following response with the `400 BadRequest` status:

```javascript
{
"errors": [
"message must be less than 10 charactors."
]
}
```

## HTTP client

`HttpClientSupport` trait offers methods to send HTTP request. You can call other Web APIs easily using these methods.

```scala
class HelloController extends HttpClientSupport {
@Action(method = "GET", path = "/hello/{id}")
def hello(id: Int): Message = {
// Call other API using methods provided by HttpClientSupport
val user: User = httpGet[User](s"http://localhost:8080/user/${id}")
Message(s"Hello ${user.name}!")
}

@Action(method = "GET", path = "/hello-async/{id}")
def helloAsync(id: Int): Future[Message] = {
// HttpClientSupport also supports asynchronous communication
val future: Future[Either[ErrorModel, User]] = httpGetAsync[User](s"http://localhost:8080/user/${id}")
future.map {
case Right(user) => Message(s"Hello ${user.name}!")
case Left(error) => throw new ActionResultException(InternalServerError(error))
}
}
}
```

These methods have retrying ability and circuit breaker. You can configure these behavior by defining `HttpClientConfig` as an implicit value.

```scala
class HelloController extends HttpClientSupport {

implicit override val httpClientConfig = HttpClientConfig(
maxRetry = 5, // max number of retry. default is 0 (no retry)
retryInterval = 500, // interval of retry (msec). default is 0 (retry immediately)
maxFailure = 3, // max number until open circuit breaker. default is 0 (disabling circuit breaker)
resetInterval = 60000 // interval to reset closed circuit breaker (msec). default is 60000
)

...
}
```

## Swagger integration

Resty provides [Swagger](http://swagger.io/) integration in default. Swagger JSON is provided at `http://localhost:8080/swagger.json` and also Swagger UI is available at `http://localhost:8080/swagger-ui/`.

![Swagger integration](swagger.png)

Add following parameter to `web.xml` to enable Swagger integration:

```xml

resty.swagger
enable

```

By enabling [runtime-scaladoc-reader](https://github.com/takezoe/runtime-scaladoc-reader) plugin in your project, Scaladoc of controller classes is reflected to Swagger JSON. For example, Scaladoc of the controller class is used as the tag description, and Scaladoc of the method is used as the operation description, the parameter description and the response description.

```scala
addCompilerPlugin("com.github.takezoe" %% "runtime-scaladoc-reader" % "1.0.1")
```

## Hystrix integration

Resty also provides [Hystrix](https://github.com/Netflix/Hystrix) integration in default. Metrics are published for each operations. The stream endpoint is available at `http://localhost:8080/hystrix.stream`. Register this endpoint to the Hystrix dashboard.

![Hystrix integration](hystrix.png)

Add following parameter to `web.xml` to enable Hystrix integration:

```xml

resty.hystrix
enable

```

## Zipkin integration

Furthermore, Resty supports [Zipkin](http://zipkin.io/) as well. You can send execution results to the Zipkin server by enabling Zipkin support and using `HttpClientSupport` for calling other APIs.

Add following parameters to `web.xml` to enable Zipkin integration:

```xml

resty.zipkin
enable

resty.zipkin.service.name
resty-sample

resty.zipkin.sample.rate
1.0

resty.zipkin.server.url
http://127.0.0.1:9411/api/v1/spans

```

## WebJars support

[WebJars](http://www.webjars.org/) is a cool stuff to integrate frontend libraries with JVM based applications. Resty can host static files that provided by WebJars for frontend applications.

Add a following parameter to `web.xml` to enable WebJars hosting:

```xml

resty.wabjars
enable

resty.wabjars.path
/public/assets/*

```

You can add WebJars dependencies in your application as following:

```scala
libraryDependencies += "org.webjars" % "jquery" % "3.1.1-1"
```

Then import JavaScript library as following:

```html

```

## Static files hosting

Resty is including some base servlets to host static files. You can provide a frontend application through Resty application from the classpath or the file system by defining following servlet based on these classes.

```scala
// Host static files on the file system
@WebServlet(name="FileResourceServlet", urlPatterns=Array("/public/*"))
class MyFileResourceServlet extends FileResourceServlet("src/main/webapp")

// Host static files in the classpath
@WebServlet(name="ClasspathResourceServlet", urlPatterns=Array("/public/*"))
class MyClasspathResourceServlet extends ResourceServlet("com/github/resty/sample/public")
```

## CORS support

CORS support can be enabled by adding following parameters to `web.xml`:

```xml

resty.cors
enable

resty.cors.allowedOrigins
http://localhost:8080

resty.cors.allowedMethods
GET, POST

resty.cors.allowedHeaders
Content-Type

resty.cors.allowCredentials
true

resty.cors.preflightMaxAge
1800

```

Description about optional parameters:

- `resty.cors.allowedOrigins`: Comma separated list of hosts and ports which will be allowed to make cross-origin requests (default is `*`).
- `resty.cors.allowedMethods`: Comma separated list of HTTP methods will be allowed (default is `GET, POST, PUT, DELETE`).
- `resty.cors.allowedHeaders`: Comma separated list of allowed HTTP headers (most headers are allowed in default).
- `resty.cors.allowCredentials`: Set this parameter to true to allow cookies in CORS requests (default is `false`).
- `resty.cors.preflightMaxAge`: Number of seconds that preflight request can be cached in the client (default is `0`).