vlingo/http

Reactive REST using vlingo/http.

Reactive REST

The vlingo/http component supports reactive, scalable, and resilient HTTP servers and RESTful services running on vlingo/cluster and vlingo/actors. Thus, this component does not run standalone, but is meant to provide very lightweight and high-performing HTTP support within a microservice-based Bounded Context.

Although this does not suggest that your services should be primarily REST-based, it is quite common for user interfaces and even distribution of event streams to be based on REST. The vlingo/http component can get you there rapidly and with great simplicity. One glance at the REST request mappings to Java objects is all it takes to understand this.

action.user.register.method = POST
action.user.register.uri = /users
action.user.register.to = register(body:sample.user.UserData userData)
action.user.contact.method = PATCH
action.user.contact.uri = /users/{userId}/contact
action.user.contact.to = changeContact(String userId, body:sample.user.ContactData contactData)
public class UserResource extends ResourceHandler {
public void register(final UserData userData) {
final User user =
User.from(
Name.from(userData.nameData.given, userData.nameData.family),
Contact.from(userData.contactData.emailAddress, userData.contactData.telephoneNumber));
repository.save(user);
completes().with(Response.of(Created, headers(of(Location, userLocation(user.id))), serialized(UserData.from(user))));
}
public void changeContact(final String userId, final ContactData contactData) {
final User user = repository.userOf(userId);
if (user.doesNotExist()) {
completes().with(Response.of(NotFound, userLocation(userId)));
return;
}
final User changedUser = user.withContact(new Contact(contactData.emailAddress, contactData.telephoneNumber));
repository.save(changedUser);
completes().with(Response.of(Ok, serialized(UserData.from(changedUser))));
}
}

Setting up vlingo/http

Using Maven

<repositories>
<repository>
<id>jcenter</id>
<url>https://jcenter.bintray.com/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>io.vlingo</groupId>
<artifactId>vlingo-http</artifactId>
<version>0.7.9</version>
<scope>compile</scope>
</dependency>
</dependencies>

Using Gradle

Groovy DSL
Kotlin DSL
dependencies {
compile 'io.vlingo:vlingo-http:0.7.9'
}
repositories {
jcenter()
}
compile(group = "io.vlingo", name = "vlingo-http", version = "0.7.9")

Hello World

We will start with the minimum code to create an endpoint that returns Hello World. I will be using the Java code as reference but you will have Kotlin example available as well.

Create a new project with your favorite Tool and create the Bootstrap class with the following content.

Java
Kotlin
Bootstrap.java
import io.vlingo.actors.World;
import io.vlingo.common.Completes;
import io.vlingo.http.Response;
import io.vlingo.http.resource.Configuration.Sizing;
import io.vlingo.http.resource.Configuration.Timing;
import io.vlingo.http.resource.Resource;
import io.vlingo.http.resource.Resources;
import io.vlingo.http.resource.Server;
import static io.vlingo.http.resource.ResourceBuilder.get;
import static io.vlingo.http.resource.ResourceBuilder.resource;
public class Bootstrap {
private final static int PORT = 8080;
private static Bootstrap instance;
public final Server server;
public final World world;
private Bootstrap() {
this.world = World.startWithDefaults("hello world example java");
final Resources resources = Resources.are(helloWorldResource());
this.server = Server.startWith(world.stage(),
resources,
PORT,
Sizing.define(),
Timing.define());
}
private Resource helloWorldResource() {
return resource("hello world",
get("/helloworld")
.handle(() -> withSuccess(of(Ok, "Hello World")))
);
}
public static final Bootstrap instance() {
if (instance == null) instance = new Bootstrap();
return instance;
}
public static void main(final String[] args) throws Exception {
System.out.println("=========================================");
System.out.println("service: started at http://localhost:" + Bootstrap.PORT);
System.out.println("check out http://localhost:" + Bootstrap.PORT + "/helloworld");
System.out.println("=========================================");
Bootstrap.instance();
}
}
Bootstrap.kt
import io.vlingo.actors.World
import io.vlingo.common.Completes.withSuccess
import io.vlingo.http.Response.of
import io.vlingo.http.Response.Status.Ok
import io.vlingo.http.resource.Configuration.Sizing
import io.vlingo.http.resource.Configuration.Timing
import io.vlingo.http.resource.Resource
import io.vlingo.http.resource.ResourceBuilder.get
import io.vlingo.http.resource.ResourceBuilder.resource
import io.vlingo.http.resource.Resources
import io.vlingo.http.resource.Server
class Bootstrap {
private val world = World.startWithDefaults("hello world example kotlin")
private val server: Server
init {
val resources = Resources.are(helloWorldResource())
server = Server.startWith(world.stage(),
resources,
PORT,
Sizing.define(),
Timing.define())
}
private fun helloWorldResource(): Resource<*> {
return resource("hello world resource",
get("/helloworld")
.handle { withSuccess(of(Ok, "Hello World")) })
}
companion object {
const val PORT = 8080
}
}
fun main(args: Array<String>) {
println("=========================================")
println("service: started at http://localhost:" + Bootstrap.PORT)
println("check out http://localhost:" + Bootstrap.PORT + "/helloworld")
println("=========================================")
Bootstrap()
}

Check the result doing

curl http://localhost:8080/helloworld

Server uses the World Logs configuration.

The Server class (24) is where the Resources live, Resources(22) is the group of HTTP endpoints. To start a Server you just need an Stage and the Resources, when it's created, it starts listening for HTTP Requests.

Sizing (27) is the configuration parameter with the processor pool size, dispatcher pool size , max buffer pool size and max message size. Timing (28) is the configuration with the timeout parameters. For now, we will use the default configuration.

The next sections uses the previous code as starting point.

Defining resources

vlingo-http provides a Fluent API to define HTTP endpoints and its method handlers. You have seen the first methods in the previous example, and here will see them in detail.

ResourceBuilder.resource(final String name, RequestHandler... requestHandlers) it returns a Resource. A set of Resource is what the Server needs to handle HTTP requests and response them.

HTTP Methods

The Fluent API supports the next HTTP methods

  • ResourceBuilder.get(final String uri)

  • ResourceBuilder.post(final String uri)

  • ResourceBuilder.put(final String uri)

  • ResourceBuilder.delete(final String uri)

  • ResourceBuilder.patch(final String uri)

  • ResourceBuilder.head(final String uri)

  • ResourceBuilder.options(final String uri)

  • ResourceBuilder.trace(final String uri)

  • ResourceBuilder.connect(final String uri)

All methods return a RequestHandler .

Request Handler

The Request Handler enforces type safety on the handler function method definition though your parameters. Let see them in action.

Path parameters

Java
Kotlin
get("/user/{userId}")
.path(String.class)
.handler((userId) -> /* */);

userId has the String type because of path(String.class). You can specify type you would like of:

  • String

  • Long

  • Integer

  • Float

  • Double

  • Boolean

  • Short

  • Character

path method needs to be defined before any other, otherwise it will throw an error when Server starts.

When using path , be sure you have the the same path variable in the URI between brackets {<variable>} as path methods has been used.

Body parameter

Java
Kotlin
post("/user")
.body(NameData.class)
.handler((nameData) -> /* */);

The body method maps the HTTP Body into the type you specify.

Query parameter

curl http://localhost:8080/user?page=5
Java
Kotlin
get("/user")
.query("page")
.handler((page) -> /* */);
get("/user")
.query("page", Integer.class)
.handler((page) -> /* */);
get("/user")
.query("page", Integer.class, 0 /* default value */)
.handler((page) -> /* */);
get("/user")
.query("page")
.handler((page) -> /* */);

By default, the type of the query parameter is String. When the query parameter isn't present, the value is null. It's a good practice to specify always a default value for query parameters to avoid unexpected behavior.

Headers parameters

Java
Kotlin
get("/user")
.header("Location")
.handler((location) -> /* */);

Combining them all

Java
Kotlin
post("/user/{userId}")
.param(String.class)
.body(UserData.class)
.header("Location")
.handler((userId, userData, location) -> {
// Perform some action
return Completes.withSuccess(Response.of(Ok, serialized(userData)));
});

The order of the parameters matters. Try changing the order of body and header, you will see that the type of userData becomes a Header type and location know has the UserData type.

Check the frontservice at vlingo/vlingo-examples for a working example.

Content Negotiation

To support media/content negotiation request handlers use the ObjectResponse<?> return type. The ? is replaced with your actual representation data type, such as PersonData or EmployeeData, or whatever; the type that is represented to clients.

The from(...) method enables building an ObjectResponse with a response status, headers, and a concrete represented content type. This response is serialized according to the accept headers of the request via the MediaTypeMapper, or JSON if a given MediaTypeMapper is not supplied. If there is no match on the specific media type, the client receives the appropriate HTTP status code.

Once serialized by the supplied MediaTypeMapper , the final Response is generated and the appropriate Content-Type header is automatically inserted into the Response, along with any other headers provided by the handler method. The following is a basic example of how to create a handler that returns MyType as the ObjectResponse type.

// GET /resources/mytype
ObjectResponse<MyType> provideMyType() {
return ObjectResponse.from(Status.ok, new MyType("some value"));
}

The previous Mapper type has been deprecated, which affects places where the existing Mapper is being used as an input parameter to the RequestHandler methods. The older Mapper is undesirable because it ignores the accept header and may create confusion—the deprecation note speaks to that.

Known problems

  • We currently support up to 5 handler function argument. If you need more than 5, please open an issue here.

Would you like to contribute to vlingo-http? See vlingo/vlingo-http and check the issues. We are happy to help you with the simple on-boarding steps.