-
Notifications
You must be signed in to change notification settings - Fork 16
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
fixes #468: Dialogue clients send Accept headers #482
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
type: improvement | ||
improvement: | ||
description: Dialogue clients send Accept headers | ||
links: | ||
- https://github.com/palantir/dialogue/pull/482 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
/* | ||
* (c) Copyright 2020 Palantir Technologies Inc. All rights reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.palantir.conjure.java.dialogue.serde; | ||
|
||
import com.palantir.dialogue.TypeMarker; | ||
import com.palantir.logsafe.Preconditions; | ||
import com.palantir.logsafe.SafeArg; | ||
import java.io.InputStream; | ||
|
||
/** | ||
* Package-private internal api. | ||
* This partial Encoding implementation exists to allow binary responses to share the same safety | ||
* and validation provided by structured encodings. This is only consumed internally to create | ||
* a binary-specific <pre>EncodingDeserializerRegistry</pre>. | ||
*/ | ||
enum BinaryEncoding implements Encoding { | ||
INSTANCE; | ||
|
||
static final String CONTENT_TYPE = "application/octet-stream"; | ||
static final TypeMarker<InputStream> MARKER = new TypeMarker<InputStream>() {}; | ||
|
||
@Override | ||
public <T> Serializer<T> serializer(TypeMarker<T> _type) { | ||
throw new UnsupportedOperationException("BinaryEncoding does not support serializers"); | ||
} | ||
|
||
@Override | ||
@SuppressWarnings("unchecked") | ||
public <T> Deserializer<T> deserializer(TypeMarker<T> type) { | ||
Preconditions.checkArgument( | ||
InputStream.class.equals(type.getType()), | ||
"BinaryEncoding only supports InputStream", | ||
SafeArg.of("requested", type)); | ||
return input -> (T) input; | ||
} | ||
|
||
@Override | ||
public String getContentType() { | ||
return CONTENT_TYPE; | ||
} | ||
|
||
@Override | ||
public boolean supportsContentType(String contentType) { | ||
return Encodings.matchesContentType(CONTENT_TYPE, contentType); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,6 +16,7 @@ | |
|
||
package com.palantir.conjure.java.dialogue.serde; | ||
|
||
import com.google.common.net.HttpHeaders; | ||
import com.google.common.util.concurrent.ExecutionError; | ||
import com.google.common.util.concurrent.FutureCallback; | ||
import com.google.common.util.concurrent.Futures; | ||
|
@@ -29,10 +30,13 @@ | |
import com.palantir.dialogue.Deserializer; | ||
import com.palantir.dialogue.Endpoint; | ||
import com.palantir.dialogue.Request; | ||
import com.palantir.logsafe.Preconditions; | ||
import com.palantir.logsafe.SafeArg; | ||
import com.palantir.logsafe.UnsafeArg; | ||
import com.palantir.logsafe.exceptions.SafeRuntimeException; | ||
import java.io.Closeable; | ||
import java.io.IOException; | ||
import java.util.Optional; | ||
import java.util.concurrent.ExecutionException; | ||
import org.checkerframework.checker.nullness.qual.Nullable; | ||
import org.slf4j.Logger; | ||
|
@@ -47,8 +51,10 @@ enum DefaultClients implements Clients { | |
@Override | ||
public <T> ListenableFuture<T> call( | ||
Channel channel, Endpoint endpoint, Request request, Deserializer<T> deserializer) { | ||
Optional<String> accepts = deserializer.accepts(); | ||
Request outgoingRequest = accepts.isPresent() ? accepting(request, accepts.get()) : request; | ||
return Futures.transform( | ||
channel.execute(endpoint, request), deserializer::deserialize, MoreExecutors.directExecutor()); | ||
channel.execute(endpoint, outgoingRequest), deserializer::deserialize, MoreExecutors.directExecutor()); | ||
} | ||
|
||
@Override | ||
|
@@ -120,4 +126,20 @@ public void onFailure(Throwable throwable) { | |
log.info("Canceled call failed", throwable); | ||
} | ||
} | ||
|
||
private static Request accepting(Request original, String acceptValue) { | ||
Preconditions.checkNotNull(acceptValue, "Accept value is required"); | ||
Preconditions.checkState(!acceptValue.isEmpty(), "Accept value must not be empty"); | ||
if (original.headerParams().containsKey(HttpHeaders.ACCEPT)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should we append the acceptValue? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think so, we'd need to deduplicate repeated accept values, and the existing values will be preferred since we're using a list, which isn't what we want here. I considered throwing instead of logging but it's difficult to trace where the value was added (unlike the null/empty inputs) so we would want to return a future. |
||
log.warn( | ||
"Request {} already contains an Accept header value {}", | ||
UnsafeArg.of("request", original), | ||
SafeArg.of("existingAcceptValue", original.headerParams().get(HttpHeaders.ACCEPT))); | ||
return original; | ||
} | ||
return Request.builder() | ||
.from(original) | ||
.putHeaderParams(HttpHeaders.ACCEPT, acceptValue) | ||
.build(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,6 +32,14 @@ public interface BodySerDe { | |
*/ | ||
Deserializer<Void> emptyBodyDeserializer(); | ||
|
||
/** | ||
* Returns a {@link Deserializer} that reads an {@link InputStream} from the {@link Response} body. | ||
* <p> | ||
* This method is named <pre>inputStreamDeserializer</pre> not <pre>binaryDeserializer</pre> | ||
* to support future streaming binary bindings without conflicting method signatures. | ||
*/ | ||
Deserializer<InputStream> inputStreamDeserializer(); | ||
|
||
/** Serializes a {@link BinaryRequestBody} to <pre>application/octet-stream</pre>. */ | ||
RequestBody serialize(BinaryRequestBody value); | ||
|
||
|
@@ -40,6 +48,11 @@ public interface BodySerDe { | |
* <p> | ||
* This method is named <pre>deserializeInputStream</pre> not <pre>deserializeBinary</pre> | ||
* to support future streaming binary bindings without conflicting method signatures. | ||
* | ||
* @deprecated Prefer {@link #inputStreamDeserializer()} | ||
*/ | ||
InputStream deserializeInputStream(Response response); | ||
@Deprecated | ||
default InputStream deserializeInputStream(Response response) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not just get rid of this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. our circular dependency on generated code would break |
||
return inputStreamDeserializer().deserialize(response); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of joining the separate accept headers into a single string should we expose a
List<String>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried this initially, but found that it maps to adding multiple
Accept
headers each with a single value, but it's most common to useAccept
header with multiple values. The optional approach allows us to pre-calculate the value.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems surprising to encode multiple accept values as a header value since in a multimap set up I would expect each value in the map to be a single header value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I completely agree that multiple accept header key/value pairs would be clean, but I don't think I've seen it used that way in practice. I think we'll reduce odd edge case failures by following the principle of least astonishment.