Skip to content

Commit

Permalink
Merge pull request #10489 from mkouba/issue-10457
Browse files Browse the repository at this point in the history
Reactive routes - set content-type from Route#produces()
  • Loading branch information
FroMage authored Jul 6, 2020
2 parents 30baf78 + 5b0d419 commit 89df78f
Show file tree
Hide file tree
Showing 22 changed files with 120 additions and 163 deletions.
13 changes: 11 additions & 2 deletions docs/src/main/asciidoc/reactive-routes.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -105,15 +105,24 @@ The `@Route` annotation is repeatable and so you can declare several routes for

[source,java]
----
@Route(path = "/first")
@Route(path = "/first") <1>
@Route(path = "/second")
public void route(RoutingContext rc) {
// ...
}
----
<1> Each route can use different paths, methods...

Each route can use different paths, methods...
If no content-type header is set then we will try to use the most acceptable content type as defined by `io.vertx.ext.web.RoutingContext.getAcceptableContentType()`.

[source,java]
----
@Route(path = "/person", produces = "text/html") <1>
String person() {
// ...
}
----
<1> If the `accept` header matches `text/html` we set the content type automatically.

=== Handling conflicting routes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@
import io.quarkus.deployment.builditem.ServiceStartBuildItem;
import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem;
import io.quarkus.deployment.util.JandexUtil;
import io.quarkus.gizmo.ClassOutput;
import io.quarkus.qute.Engine;
Expand All @@ -81,7 +80,6 @@
import io.quarkus.qute.LoopSectionHelper;
import io.quarkus.qute.ParserHelper;
import io.quarkus.qute.ParserHook;
import io.quarkus.qute.PublisherFactory;
import io.quarkus.qute.ResultNode;
import io.quarkus.qute.SectionHelper;
import io.quarkus.qute.SectionHelperFactory;
Expand All @@ -99,7 +97,6 @@
import io.quarkus.qute.deployment.TypeInfos.Info;
import io.quarkus.qute.generator.ExtensionMethodGenerator;
import io.quarkus.qute.generator.ValueResolverGenerator;
import io.quarkus.qute.mutiny.MutinyPublisherFactory;
import io.quarkus.qute.runtime.EngineProducer;
import io.quarkus.qute.runtime.QuteConfig;
import io.quarkus.qute.runtime.QuteRecorder;
Expand Down Expand Up @@ -854,11 +851,6 @@ TemplateVariantsBuildItem collectTemplateVariants(List<TemplatePathBuildItem> te
return new TemplateVariantsBuildItem(baseToVariants);
}

@BuildStep
ServiceProviderBuildItem registerPublisherFactory() {
return new ServiceProviderBuildItem(PublisherFactory.class.getName(), MutinyPublisherFactory.class.getName());
}

@BuildStep
void excludeTypeChecks(BuildProducer<TypeCheckExcludeBuildItem> excludes) {
// Exclude all checks that involve built-in value resolvers
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package io.quarkus.qute.deployment;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.math.BigDecimal;
import java.math.RoundingMode;
Expand All @@ -13,14 +12,13 @@
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.reactivestreams.Publisher;

import io.quarkus.qute.Template;
import io.quarkus.qute.TemplateData;
import io.quarkus.test.QuarkusUnitTest;
import io.smallrye.mutiny.Multi;

public class PublisherTest {
public class MultiTest {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
Expand All @@ -33,11 +31,10 @@ public class PublisherTest {
Template foo;

@Test
public void testTemplateData() {
Publisher<String> publisher = foo.data("roundingMode", RoundingMode.HALF_UP)
.data("foo", new TemplateDataTest.Foo(new BigDecimal("123.4563"))).publisher();
assertTrue(publisher instanceof Multi);
assertEquals("123.4563 is not 123.46", Multi.createFrom().publisher(publisher)
public void testCreateMulti() {
Multi<String> multi = foo.data("roundingMode", RoundingMode.HALF_UP)
.data("foo", new TemplateDataTest.Foo(new BigDecimal("123.4563"))).createMulti();
assertEquals("123.4563 is not 123.46", multi
.collectItems().in(StringBuffer::new, StringBuffer::append)
.onItem().apply(StringBuffer::toString)
.await().indefinitely());
Expand Down
4 changes: 0 additions & 4 deletions extensions/qute/runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,6 @@
<groupId>io.quarkus.qute</groupId>
<artifactId>qute-core</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus.qute</groupId>
<artifactId>qute-mutiny</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import javax.inject.Singleton;

import org.jboss.logging.Logger;
import org.reactivestreams.Publisher;

import io.quarkus.qute.Engine;
import io.quarkus.qute.Expression;
Expand All @@ -29,6 +28,7 @@
import io.quarkus.qute.Variant;
import io.quarkus.qute.api.ResourcePath;
import io.quarkus.qute.runtime.QuteRecorder.QuteContext;
import io.smallrye.mutiny.Multi;

@Singleton
public class TemplateProducer {
Expand Down Expand Up @@ -154,8 +154,8 @@ public CompletionStage<String> renderAsync() {
}

@Override
public Publisher<String> publisher() {
return templateInstance().publisher();
public Multi<String> createMulti() {
return templateInstance().createMulti();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import io.quarkus.vertx.web.runtime.MultiJsonArraySupport;
import io.quarkus.vertx.web.runtime.MultiSseSupport;
import io.quarkus.vertx.web.runtime.MultiSupport;
import io.quarkus.vertx.web.runtime.RouteHandlers;
import io.smallrye.mutiny.Multi;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.groups.UniSubscribe;
Expand Down Expand Up @@ -136,6 +137,9 @@ class Methods {
CreationalContext.class);
static final MethodDescriptor OBJECT_CONSTRUCTOR = MethodDescriptor.ofConstructor(Object.class);

static final MethodDescriptor ROUTE_HANDLERS_SET_CONTENT_TYPE = MethodDescriptor
.ofMethod(RouteHandlers.class, "setContentType", void.class, RoutingContext.class);

private Methods() {
// Avoid direct instantiation
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -530,10 +530,13 @@ void implementInvoke(HandlerDescriptor descriptor, BeanInfo bean, MethodInfo met
// Invoke the business method handler
ResultHandle res = invoke.invokeVirtualMethod(methodDescriptor, beanInstanceHandle, paramHandles);

// If no content-type header is set then try to use the most acceptable content type
invoke.invokeStaticMethod(Methods.ROUTE_HANDLERS_SET_CONTENT_TYPE, routingContext);

// Get the response: HttpServerResponse response = rc.response()
ResultHandle response = invoke.invokeInterfaceMethod(Methods.RESPONSE, routingContext);
MethodDescriptor end = Methods.getEndMethodForContentType(descriptor);
if (descriptor.isReturningUni()) {
ResultHandle response = invoke.invokeInterfaceMethod(Methods.RESPONSE, routingContext);
// The method returns a Uni.
// We subscribe to this Uni and write the provided item in the HTTP response
// If the method returned null, we fail
Expand Down Expand Up @@ -568,7 +571,7 @@ void implementInvoke(HandlerDescriptor descriptor, BeanInfo bean, MethodInfo met

} else if (descriptor.getContentType() != null) {
// The method returns "something" in a synchronous manner, write it into the response

ResultHandle response = invoke.invokeInterfaceMethod(Methods.RESPONSE, routingContext);
// If the method returned null, we fail
// If the method returns string or buffer, the response.end method is used to write the response
// If the method returns an object, the result is mapped to JSON and written into the response
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import io.vertx.core.Handler;
import io.vertx.core.http.HttpMethod;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;

/**
* This annotation can be used to configure a reactive route in a declarative way.
Expand Down Expand Up @@ -74,8 +75,11 @@

/**
* Used for content-based routing.
* <p>
* If no {@code Content-Type} header is set then try to use the most acceptable content type.
*
* @see io.vertx.ext.web.Route#produces(String)
* @see RoutingContext#getAcceptableContentType()
* @return the produced content types
*/
String[] produces() default {};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,29 @@
import io.quarkus.arc.Arc;
import io.quarkus.arc.impl.LazyValue;
import io.quarkus.security.identity.SecurityIdentity;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.ext.web.RoutingContext;

final class RouteHandlers {
public final class RouteHandlers {

private RouteHandlers() {
}

static final String CONTENT_TYPE = "content-type";

private static final LazyValue<Event<SecurityIdentity>> SECURITY_IDENTITY_EVENT = new LazyValue<>(
RouteHandlers::createEvent);

public static void setContentType(RoutingContext context) {
HttpServerResponse response = context.response();
if (response.headers().get(CONTENT_TYPE) == null) {
String acceptableContentType = context.getAcceptableContentType();
if (acceptableContentType != null) {
response.putHeader(CONTENT_TYPE, acceptableContentType);
}
}
}

static void fireSecurityIdentity(SecurityIdentity identity) {
SECURITY_IDENTITY_EVENT.get().fire(identity);
}
Expand Down
4 changes: 2 additions & 2 deletions independent-projects/qute/core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
<artifactId>jboss-logging</artifactId>
</dependency>
<dependency>
<groupId>org.reactivestreams</groupId>
<artifactId>reactive-streams</artifactId>
<groupId>io.smallrye.reactive</groupId>
<artifactId>mutiny</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,13 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.jboss.logging.Logger;

/**
Expand All @@ -38,7 +34,6 @@ class EngineImpl implements Engine {
private final Map<String, Template> templates;
private final List<TemplateLocator> locators;
private final List<ResultMapper> resultMappers;
private final PublisherFactory publisherFactory;
private final AtomicLong idGenerator = new AtomicLong(0);
private final List<ParserHook> parserHooks;
final boolean removeStandaloneLines;
Expand All @@ -50,18 +45,6 @@ class EngineImpl implements Engine {
this.evaluator = new EvaluatorImpl(this.valueResolvers);
this.templates = new ConcurrentHashMap<>();
this.locators = sort(builder.locators);
ServiceLoader<PublisherFactory> loader = ServiceLoader.load(PublisherFactory.class);
Iterator<PublisherFactory> iterator = loader.iterator();
if (iterator.hasNext()) {
this.publisherFactory = iterator.next();
} else {
this.publisherFactory = null;
}
if (iterator.hasNext()) {
throw new IllegalStateException(
"Multiple reactive factories found: " + StreamSupport.stream(loader.spliterator(), false)
.map(Object::getClass).map(Class::getName).collect(Collectors.joining(",")));
}
this.resultMappers = sort(builder.resultMappers);
this.sectionHelperFunc = builder.sectionHelperFunc;
this.parserHooks = ImmutableList.copyOf(builder.parserHooks);
Expand Down Expand Up @@ -129,10 +112,6 @@ public void removeTemplates(Predicate<String> test) {
templates.keySet().removeIf(test);
}

PublisherFactory getPublisherFactory() {
return publisherFactory;
}

String generateId() {
return "" + idGenerator.incrementAndGet();
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.quarkus.qute;

import io.smallrye.mutiny.Multi;
import java.util.List;
import java.util.Optional;
import java.util.Set;
Expand All @@ -9,7 +10,6 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import org.reactivestreams.Publisher;

class TemplateImpl implements Template {

Expand Down Expand Up @@ -68,12 +68,15 @@ public String render() {
}

@Override
public Publisher<String> publisher() {
PublisherFactory factory = engine.getPublisherFactory();
if (factory == null) {
throw new UnsupportedOperationException();
}
return factory.createPublisher(this);
public Multi<String> createMulti() {
return Multi.createFrom().emitter(emitter -> consume(emitter::emit)
.whenComplete((r, f) -> {
if (f == null) {
emitter.complete();
} else {
emitter.fail(f);
}
}));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.quarkus.qute;

import io.smallrye.mutiny.Multi;
import java.util.concurrent.CompletionStage;
import java.util.function.Consumer;
import org.reactivestreams.Publisher;
Expand Down Expand Up @@ -79,8 +80,19 @@ public interface TemplateInstance {
*
* @return a publisher that can be used to consume chunks of the rendered template
* @throws UnsupportedOperationException If no {@link PublisherFactory} service provider is found
* @deprecated Use {@link #createMulti()} instead
*/
Publisher<String> publisher();
@Deprecated
default Publisher<String> publisher() {
return createMulti();
}

/**
* Each subscription triggers rendering.
*
* @return a Multi that can be used to consume chunks of the rendered template
*/
Multi<String> createMulti();

/**
* Triggers rendering.
Expand Down
Loading

0 comments on commit 89df78f

Please sign in to comment.