Skip to content

Commit

Permalink
Merge pull request #10379 from cescoffier/features/reactive-routes-mu…
Browse files Browse the repository at this point in the history
…tiny

Add support for mutiny types in reactive routes
  • Loading branch information
mkouba authored Jul 3, 2020
2 parents 42efa84 + 673c1b5 commit 2f20cf5
Show file tree
Hide file tree
Showing 15 changed files with 2,194 additions and 81 deletions.
215 changes: 215 additions & 0 deletions docs/src/main/asciidoc/reactive-routes.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,221 @@ public class SimpleRoutes {
----
<1> The `path` value is used as a prefix for any route method declared on the class where `Route#path()` is used. The `produces` value is used for content-based routing for all routes where `Route#produces()` is empty.

=== Returning Unis

In a reactive route, you can return a `Uni` directly:

[source,java]
----
@Route(path = "/hello")
Uni<String> hello(RoutingContext context) {
return Uni.createFrom().item("Hello world!");
}
@Route(path = "/person")
Uni<Person> getPerson(RoutingContext context) {
return Uni.createFrom().item(() -> new Person("neo", 12345));
}
----

Returning `Unis` is convenient when using a reactive client:

[source,java]
----
@Route(path = "/mail")
Uni<Void> sendEmail(RoutingContext context) {
return mailer.send(...);
}
----

The item produced by the returned `Uni` can be:

* a string - written into the HTTP response directly
* a buffer - written into the HTTP response directly
* an object - written into the HTTP response after having been encoded into JSON.
The `content-type` header is set to `application/json` if not already set.

If the returned `Uni` produces a failure (or is `null`), an HTTP 500 response is written.

Returning a `Uni<Void>` produces a 204 response (no content).

=== Returning results

You can also return a result directly:

[source, java]
----
@Route(path = "/hello")
String helloSync(RoutingContext context) {
return "Hello world";
}
----

Be aware, the processing must be **non-blocking** as reactive routes are invoked on the IO Thread.
Otherwise, use the `blocking` attribute of the `@Route` annotation.

The method can return:

* a string - written into the HTTP response directly
* a buffer - written into the HTTP response directly
* an object - written into the HTTP response after having been encoded into JSON.
The `content-type` header is set to `application/json` if not already set.

=== Returning Multis

A reactive route can return a `Multi`.
The items are written one by one, in the response.
The response `Transfer-Encoding` header is set to `chunked`.

[source, java]
----
@Route(path = "/hello")
Multi<String> hellos(RoutingContext context) {
return Multi.createFrom().items("hello", "world", "!"); // <1>
}
----
1. Produces `helloworld!`

The method can return:

* a `Multi<String>` - the items are written one by one (one per _chunk_) in the response.
* a `Multi<Buffer>` - the buffers are written one by one (one per _chunk_) without any processing.
* a `Multi<Object>` - the items are encoded to JSON written one by one in the response.


[source, java]
----
@Route(path = "/people")
Multi<Person> people(RoutingContext context) {
return Multi.createFrom().items(
new Person("superman", 1),
new Person("batman", 2),
new Person("spiderman", 3));
}
----

Ths previous snippet produces:

[source, json]
----
{"name":"superman", "id": 1} // chunk 1
{"name":"batman", "id": 2} // chunk 2
{"name":"spiderman", "id": 3} // chunk 3
----

=== Streaming JSON Array items

You can return a `Multi` to produce a JSON Array, where every item is an item from this array.
The response is written item by item to the client.
The `content-type` is set to `application/json` if not set already.

To use this feature, you need to wrap the returned `Multi` using `io.quarkus.vertx.web.ReactiveRoutes.asJsonArray`:

[source, java]
----
@Route(path = "/people")
Multi<Person> people(RoutingContext context) {
return ReactiveRoutes.asJsonArray(Multi.createFrom().items(
new Person("superman", 1),
new Person("batman", 2),
new Person("spiderman", 3)));
}
----

Ths previous snippet produces:

[source, json]
----
[
{"name":"superman", "id": 1} // chunk 1
,{"name":"batman", "id": 2} // chunk 2
,{"name":"spiderman", "id": 3} // chunk 3
]
----

Only `Multi<String>`, `Multi<Object>` and `Multi<Void>` can be written into the JSON Array.
Using a `Multi<Void>` produces an empty array.
You cannot use `Multi<Buffer>`.
If you need to use `Buffer`, transform the content into a JSON or String representation first.

=== Event Stream and Server-Sent Event support

You can return a `Multi` to produce an event source (stream of server sent events).
To enable this feature, you need to wrap the returned `Multi` using `io.quarkus.vertx.web.ReactiveRoutes.asEventStream`:

[source, java]
----
@Route(path = "/people")
Multi<Person> people(RoutingContext context) {
return ReactiveRoutes.asEventStream(Multi.createFrom().items(
new Person("superman", 1),
new Person("batman", 2),
new Person("spiderman", 3)));
}
----

This method would produce:

[source, text]
----
data: {"name":"superman", "id": 1}
id: 0
data: {"name":"batman", "id": 2}
id: 1
data: {"name":"spiderman", "id": 3}
id: 2
----

You can also implement the `io.quarkus.vertx.web.ReactiveRoutes.ServerSentEvent` interface to customize the `event` and `id` section of the server sent event:

[source, java]
----
class PersonEvent implements ReactiveRoutes.ServerSentEvent<Person> {
public String name;
public int id;
public PersonEvent(String name, int id) {
this.name = name;
this.id = id;
}
@Override
public Person data() {
return new Person(name, id); // Will be JSON encoded
}
@Override
public long id() {
return id;
}
@Override
public String event() {
return "person";
}
}
----

Using a `Multi<PersonEvent>` (wrapped using `io.quarkus.vertx.web.ReactiveRoutes.asEventStream`) would produce:

[source, text]
----
event: person
data: {"name":"superman", "id": 1}
id: 1
event: person
data: {"name":"batman", "id": 2}
id: 2
event: person
data: {"name":"spiderman", "id": 3}
id: 3
----

== Using the Vert.x Web Router

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package io.quarkus.vertx.web.deployment;

import org.jboss.jandex.DotName;
import org.jboss.jandex.MethodInfo;
import org.jboss.jandex.Type;

import io.smallrye.mutiny.Multi;
import io.smallrye.mutiny.Uni;
import io.vertx.core.buffer.Buffer;

/**
* Describe a request handler.
*/
public class HandlerDescriptor {

private static final DotName DOT_NAME_UNI = DotName.createSimple(Uni.class.getName());
private static final DotName DOT_NAME_MULTI = DotName.createSimple(Multi.class.getName());
private final MethodInfo method;

public HandlerDescriptor(MethodInfo method) {
this.method = method;
}

public Type getReturnType() {
return method.returnType();
}

public boolean isReturningVoid() {
return method.returnType().kind().equals(Type.Kind.VOID);
}

public boolean isReturningUni() {
return method.returnType().name().equals(DOT_NAME_UNI);
}

public boolean isReturningMulti() {
return method.returnType().name().equals(DOT_NAME_MULTI);
}

public Type getContentType() {
if (isReturningVoid()) {
return null;
}
if (isReturningUni()) {
return getReturnType().asParameterizedType().arguments().get(0);
}
if (isReturningMulti()) {
return getReturnType().asParameterizedType().arguments().get(0);
}
return getReturnType();
}

public boolean isContentTypeString() {
Type type = getContentType();
if (type == null) {
return false;
}
return type.name().equals(DotName.createSimple(String.class.getName()));
}

public boolean isContentTypeBuffer() {
Type type = getContentType();
if (type == null) {
return false;
}
return type.name().equals(DotName.createSimple(Buffer.class.getName()));
}

public boolean isContentTypeRxBuffer() {
Type type = getContentType();
if (type == null) {
return false;
}
return type.name()
.equals(DotName.createSimple(io.vertx.reactivex.core.buffer.Buffer.class.getName()));
}

public boolean isContentTypeMutinyBuffer() {
Type type = getContentType();
if (type == null) {
return false;
}
return type.name().equals(DotName.createSimple(io.vertx.mutiny.core.buffer.Buffer.class.getName()));
}

}
Loading

0 comments on commit 2f20cf5

Please sign in to comment.