palantir / palantir/conjure-java
Extension point for endpoint specific handler behaviour in conjure-undertow
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 39
- Forks
- 49
- Avg merge
- 8h 22m
- Merged PRs (30d)
- 32
Description
What happened?
I had forked generated undertow handlers to add metrics dependent on request body.
Currently you can modify your service by wrapping your service definition or wrapping the endpoint. However, there's no way to create a plugin that can use both of them without forking generated undertow handlers.
The concrete case was creating a server response time metric that has tag values that are request body specific. This is a simpler version of the code modifications I had to make to generated service endpoints. Naturally this gets pretty cumbersome with many endpoints.
public final class SomeServiceEndpoints implements UndertowService {
private final DatasetSizeThresholds sizeThresholds;
private final MetricsSink metrics;
private final SomeService delegate;
private SomeServiceEndpoints(
DatasetSizeThresholds sizeThresholds, MetricsSink metrics, SomeService delegate) {
this.sizeThresholds = sizeThresholds;
this.metrics = metrics;
this.delegate = delegate;
}
public static UndertowService of(
DatasetSizeThresholds sizeThresholds,
MetricsSink metrics,
SomeService delegate) {
return new SomeServiceEndpoints(sizeThresholds, metrics, delegate);
}
@Override
public List<Endpoint> endpoints(UndertowRuntime runtime) {
return Collections.unmodifiableList(
Arrays.asList(
new GetPreviewEndpoint(runtime, delegate, sizeThresholds, metrics)));
}
private static final class GetPreviewEndpoint
implements HttpHandler, Endpoint, ReturnValueWriter<BinaryResponseBody> {
private final UndertowRuntime runtime;
private final SomeService delegate;
private final Deserializer<PreviewDataRequest> deserializer;
private final DatasetSizeThresholds sizeThresholds;
private final MetricsSink metrics;
GetPreviewEndpoint(
UndertowRuntime runtime,
SomeService delegate,
DatasetSizeThresholds sizeThresholds,
MetricsSink metrics) {
this.runtime = runtime;
this.delegate = delegate;
this.deserializer =
runtime.bodySerDe().deserializer(new TypeMarker<PreviewDataRequest>() {});
this.sizeThresholds = sizeThresholds;
this.metrics = metrics;
}
@Override
public void handleRequest(HttpServerExchange exchange) throws IOException {
AuthHeader authHeader = runtime.auth().header(exchange);
PreviewDataRequest previewRequest = deserializer.deserialize(exchange);
exchange.addExchangeCompleteListener(buildExchangeCompletionListener(
metrics,
() -> sizeThresholds.getDatasetSize(
authHeader,
previewRequest)
.sizeInBytes()));
ListenableFuture<BinaryResponseBody> result =
delegate.getPreview(authHeader, previewRequest);
runtime.async().register(result, this, exchange);
}
@Override
public void write(BinaryResponseBody result, HttpServerExchange exchange)
throws IOException {
runtime.bodySerDe().serialize(result, exchange);
}
@Override
public HttpString method() {
return Methods.POST;
}
@Override
public String template() {
return "/someservice/arrow";
}
@Override
public String serviceName() {
return "SomeService";
}
@Override
public String name() {
return "getPreview";
}
@Override
public HttpHandler handler() {
return this;
}
}
private static void recordMetrics(
MetricsSink metrics, HttpServerExchange completeExchange, long inputDatasetSize) {
metrics.status(completeExchange.getStatusCode(), inputDatasetSize);
metrics.durationNanoseconds(System.nanoTime() - completeExchange.getRequestStartTime(), inputDatasetSize);
}
private static ExchangeCompletionListener buildExchangeCompletionListener(
MetricsSink metrics, Supplier<Optional<Long>> sizeProvider) {
return (exchange, nextListener) -> {
try {
sizeProvider.get().ifPresent(datasetSize -> recordMetrics(metrics, exchange, datasetSize));
} finally {
nextListener.proceed();
}
};
}
}
What did you want to happen?
It should be possible to write a an extension to generated handlers that can access both request objects and the undertow exchange.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading the generated Undertow handlers and the extension points described in the issue's example. Define and implement an extension mechanism that can access both deserialized request objects and the Undertow exchange, then verify that endpoint-specific behavior no longer requires forking generated handlers.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- api, backend
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 30/100