Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reactive routes - set content-type from Route#produces() #10489

Merged
merged 2 commits into from
Jul 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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