Skip to content

Commit

Permalink
[undertow] Asynchronous request processing (#361)
Browse files Browse the repository at this point in the history
The Undertow server target supports asynchronous request processing.
  • Loading branch information
carterkozak authored and bulldozer-bot[bot] committed May 2, 2019
1 parent ba4aa41 commit 6c4239b
Show file tree
Hide file tree
Showing 24 changed files with 1,386 additions and 75 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
package com.palantir.product;

import com.google.common.util.concurrent.ListenableFuture;
import com.palantir.conjure.java.undertow.lib.BinaryResponseBody;
import com.palantir.conjure.java.undertow.lib.Endpoint;
import com.palantir.conjure.java.undertow.lib.ReturnValueWriter;
import com.palantir.conjure.java.undertow.lib.Serializer;
import com.palantir.conjure.java.undertow.lib.TypeMarker;
import com.palantir.conjure.java.undertow.lib.UndertowRuntime;
import com.palantir.conjure.java.undertow.lib.UndertowService;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import io.undertow.util.HttpString;
import io.undertow.util.Methods;
import io.undertow.util.StatusCodes;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalInt;
import javax.annotation.Generated;

@Generated("com.palantir.conjure.java.services.UndertowServiceHandlerGenerator")
public final class AsyncRequestProcessingTestServiceEndpoints implements UndertowService {
private final UndertowAsyncRequestProcessingTestService delegate;

private AsyncRequestProcessingTestServiceEndpoints(
UndertowAsyncRequestProcessingTestService delegate) {
this.delegate = delegate;
}

public static UndertowService of(UndertowAsyncRequestProcessingTestService delegate) {
return new AsyncRequestProcessingTestServiceEndpoints(delegate);
}

@Override
public List<Endpoint> endpoints(UndertowRuntime runtime) {
return Collections.unmodifiableList(
Arrays.asList(
new DelayEndpoint(runtime, delegate),
new ThrowsInHandlerEndpoint(runtime, delegate),
new FailedFutureEndpoint(runtime, delegate),
new BinaryEndpoint(runtime, delegate)));
}

private static final class DelayEndpoint
implements HttpHandler, Endpoint, ReturnValueWriter<String> {
private final UndertowRuntime runtime;

private final UndertowAsyncRequestProcessingTestService delegate;

private final Serializer<String> serializer;

DelayEndpoint(UndertowRuntime runtime, UndertowAsyncRequestProcessingTestService delegate) {
this.runtime = runtime;
this.delegate = delegate;
this.serializer = runtime.bodySerDe().serializer(new TypeMarker<String>() {});
}

@Override
public void handleRequest(HttpServerExchange exchange) throws IOException {
Map<String, Deque<String>> queryParams = exchange.getQueryParameters();
OptionalInt delayMillis =
runtime.plainSerDe().deserializeOptionalInteger(queryParams.get("delayMillis"));
ListenableFuture<String> result = delegate.delay(delayMillis);
runtime.async().register(result, this, exchange);
}

@Override
public void write(String result, HttpServerExchange exchange) throws IOException {
serializer.serialize(result, exchange);
}

@Override
public HttpString method() {
return Methods.GET;
}

@Override
public String template() {
return "/async/delay";
}

@Override
public String serviceName() {
return "AsyncRequestProcessingTestService";
}

@Override
public String name() {
return "delay";
}

@Override
public HttpHandler handler() {
return this;
}
}

private static final class ThrowsInHandlerEndpoint
implements HttpHandler, Endpoint, ReturnValueWriter<Void> {
private final UndertowRuntime runtime;

private final UndertowAsyncRequestProcessingTestService delegate;

ThrowsInHandlerEndpoint(
UndertowRuntime runtime, UndertowAsyncRequestProcessingTestService delegate) {
this.runtime = runtime;
this.delegate = delegate;
}

@Override
public void handleRequest(HttpServerExchange exchange) throws IOException {
ListenableFuture<Void> result = delegate.throwsInHandler();
runtime.async().register(result, this, exchange);
}

@Override
public void write(Void result, HttpServerExchange exchange) throws IOException {
exchange.setStatusCode(StatusCodes.NO_CONTENT);
}

@Override
public HttpString method() {
return Methods.GET;
}

@Override
public String template() {
return "/async/throws";
}

@Override
public String serviceName() {
return "AsyncRequestProcessingTestService";
}

@Override
public String name() {
return "throwsInHandler";
}

@Override
public HttpHandler handler() {
return this;
}
}

private static final class FailedFutureEndpoint
implements HttpHandler, Endpoint, ReturnValueWriter<Void> {
private final UndertowRuntime runtime;

private final UndertowAsyncRequestProcessingTestService delegate;

FailedFutureEndpoint(
UndertowRuntime runtime, UndertowAsyncRequestProcessingTestService delegate) {
this.runtime = runtime;
this.delegate = delegate;
}

@Override
public void handleRequest(HttpServerExchange exchange) throws IOException {
Map<String, Deque<String>> queryParams = exchange.getQueryParameters();
OptionalInt delayMillis =
runtime.plainSerDe().deserializeOptionalInteger(queryParams.get("delayMillis"));
ListenableFuture<Void> result = delegate.failedFuture(delayMillis);
runtime.async().register(result, this, exchange);
}

@Override
public void write(Void result, HttpServerExchange exchange) throws IOException {
exchange.setStatusCode(StatusCodes.NO_CONTENT);
}

@Override
public HttpString method() {
return Methods.GET;
}

@Override
public String template() {
return "/async/failed-future";
}

@Override
public String serviceName() {
return "AsyncRequestProcessingTestService";
}

@Override
public String name() {
return "failedFuture";
}

@Override
public HttpHandler handler() {
return this;
}
}

private static final class BinaryEndpoint
implements HttpHandler, Endpoint, ReturnValueWriter<Optional<BinaryResponseBody>> {
private final UndertowRuntime runtime;

private final UndertowAsyncRequestProcessingTestService delegate;

BinaryEndpoint(
UndertowRuntime runtime, UndertowAsyncRequestProcessingTestService delegate) {
this.runtime = runtime;
this.delegate = delegate;
}

@Override
public void handleRequest(HttpServerExchange exchange) throws IOException {
Map<String, Deque<String>> queryParams = exchange.getQueryParameters();
Optional<String> stringValue =
runtime.plainSerDe().deserializeOptionalString(queryParams.get("stringValue"));
ListenableFuture<Optional<BinaryResponseBody>> result = delegate.binary(stringValue);
runtime.async().register(result, this, exchange);
}

@Override
public void write(Optional<BinaryResponseBody> result, HttpServerExchange exchange)
throws IOException {
if (result.isPresent()) {
runtime.bodySerDe().serialize(result.get(), exchange);
} else {
exchange.setStatusCode(StatusCodes.NO_CONTENT);
}
}

@Override
public HttpString method() {
return Methods.GET;
}

@Override
public String template() {
return "/async/binary";
}

@Override
public String serviceName() {
return "AsyncRequestProcessingTestService";
}

@Override
public String name() {
return "binary";
}

@Override
public HttpHandler handler() {
return this;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.palantir.product;

import com.google.common.util.concurrent.ListenableFuture;
import com.palantir.conjure.java.undertow.lib.BinaryResponseBody;
import java.util.Optional;
import java.util.OptionalInt;
import javax.annotation.Generated;

@Generated("com.palantir.conjure.java.services.UndertowServiceInterfaceGenerator")
public interface UndertowAsyncRequestProcessingTestService {
ListenableFuture<String> delay(OptionalInt delayMillis);

ListenableFuture<Void> throwsInHandler();

ListenableFuture<Void> failedFuture(OptionalInt delayMillis);

ListenableFuture<Optional<BinaryResponseBody>> binary(Optional<String> stringValue);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.palantir.conjure.java;

import com.google.common.annotations.Beta;
import com.palantir.conjure.java.services.JerseyServiceGenerator;
import com.palantir.conjure.java.services.Retrofit2ServiceGenerator;
import javax.validation.constraints.NotNull;
Expand Down Expand Up @@ -59,4 +60,19 @@ public enum FeatureFlags {
* Use the conjure immutable "Bytes" class over ByteBuffer.
*/
UseImmutableBytes,

/**
* Instructs the {@link com.palantir.conjure.java.services.UndertowServiceGenerator} to generate service
* endpoints returning {@link com.google.common.util.concurrent.ListenableFuture} to allow asynchronous
* request processing.
*/
UndertowListenableFutures,

/**
* Allows synchronous and {@link com.google.common.util.concurrent.ListenableFuture} based asynchronous request
* handling to be mixed in a single module using {@link com.palantir.conjure.spec.EndpointDefinition#getMarkers()}.
* This feature is experimental and subject to change.
*/
@Beta
ExperimentalUndertowAsyncMarkers,
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.palantir.conjure.java.types.TypeMapper;
import com.palantir.conjure.java.undertow.lib.Deserializer;
import com.palantir.conjure.java.undertow.lib.Endpoint;
import com.palantir.conjure.java.undertow.lib.ReturnValueWriter;
import com.palantir.conjure.java.undertow.lib.Serializer;
import com.palantir.conjure.java.undertow.lib.TypeMarker;
import com.palantir.conjure.java.undertow.lib.UndertowRuntime;
Expand Down Expand Up @@ -244,8 +245,26 @@ private TypeSpec generateEndpointHandler(

endpointBuilder
.addMethod(ctorBuilder.build())
.addMethod(handleMethodBuilder.build())
.addMethod(MethodSpec.methodBuilder("method")
.addMethod(handleMethodBuilder.build());

if (UndertowTypeFunctions.isAsync(endpointDefinition, experimentalFeatures)) {
ParameterizedTypeName type = UndertowTypeFunctions.getAsyncReturnType(
endpointDefinition, returnTypeMapper, experimentalFeatures);
TypeName resultType = Iterables.getOnlyElement(type.typeArguments);
endpointBuilder.addSuperinterface(ParameterizedTypeName.get(
ClassName.get(ReturnValueWriter.class), resultType));

endpointBuilder.addMethod(MethodSpec.methodBuilder("write")
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override.class)
.addParameter(resultType, RESULT_VAR_NAME)
.addParameter(HttpServerExchange.class, EXCHANGE_VAR_NAME)
.addException(IOException.class)
.addCode(generateReturnValueCodeBlock(endpointDefinition, typeDefinitions))
.build());
}

endpointBuilder.addMethod(MethodSpec.methodBuilder("method")
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override.class)
.returns(HttpString.class)
Expand Down Expand Up @@ -327,16 +346,38 @@ private CodeBlock endpointInvocation(EndpointDefinition endpointDefinition, List
.map(arg -> sanitizeVarName(arg, endpointDefinition))
.collect(Collectors.toList()));

if (endpointDefinition.getReturns().isPresent()) {
Type returnType = endpointDefinition.getReturns().get();
boolean async = UndertowTypeFunctions.isAsync(endpointDefinition, experimentalFeatures);
if (async || endpointDefinition.getReturns().isPresent()) {
code.addStatement("$1T $2N = $3N.$4L($5L)",
returnTypeMapper.getClassName(returnType),
async
? UndertowTypeFunctions.getAsyncReturnType(
endpointDefinition, returnTypeMapper, experimentalFeatures)
: returnTypeMapper.getClassName(endpointDefinition.getReturns().get()),
RESULT_VAR_NAME,
DELEGATE_VAR_NAME,
JavaNameSanitizer.sanitize(endpointDefinition.getEndpointName().get()),
String.join(", ", methodArgs)
);
} else {
code.addStatement("$1N.$2L($3L)",
DELEGATE_VAR_NAME,
endpointDefinition.getEndpointName(),
String.join(", ", methodArgs));
}
if (UndertowTypeFunctions.isAsync(endpointDefinition, experimentalFeatures)) {
code.add(CodeBlocks.statement("$1N.async().register($2N, this, $3N)",
RUNTIME_VAR_NAME, RESULT_VAR_NAME, EXCHANGE_VAR_NAME));
} else {
code.add(generateReturnValueCodeBlock(endpointDefinition, typeDefinitions));
}
return code.build();
}

private CodeBlock generateReturnValueCodeBlock(
EndpointDefinition endpointDefinition, List<TypeDefinition> typeDefinitions) {
CodeBlock.Builder code = CodeBlock.builder();
if (endpointDefinition.getReturns().isPresent()) {
Type returnType = endpointDefinition.getReturns().get();
// optional<> handling
// TODO(ckozak): Support aliased binary types
if (UndertowTypeFunctions.toConjureTypeWithoutAliases(returnType, typeDefinitions)
Expand Down Expand Up @@ -366,10 +407,6 @@ private CodeBlock endpointInvocation(EndpointDefinition endpointDefinition, List
}
}
} else {
code.addStatement("$1N.$2L($3L)",
DELEGATE_VAR_NAME,
endpointDefinition.getEndpointName(),
String.join(", ", methodArgs));
// Set 204 response code for void methods
// Use the constant from undertow for improved source readability, javac will compile it out.
code.addStatement("$1N.setStatusCode($2T.NO_CONTENT)", EXCHANGE_VAR_NAME, StatusCodes.class);
Expand Down
Loading

0 comments on commit 6c4239b

Please sign in to comment.