Skip to content

Commit

Permalink
make replacements headers work the same like in the body
Browse files Browse the repository at this point in the history
  • Loading branch information
ahus1 committed Feb 13, 2016
1 parent 7464d5e commit 7e25bbb
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 17 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,7 @@
### Version 8.15 ### Version 8.15
* Supports PUT without a body parameter * Supports PUT without a body parameter
* Supports substitutions in `@Headers` like in `@Body`. (#326)
* **Note:** You might need to URL-encode literal values of `{` or `%` in your existing code.


### Version 8.14 ### Version 8.14
* Add support for RxJava Observable and Single return types via the `HystrixFeign` builder. * Add support for RxJava Observable and Single return types via the `HystrixFeign` builder.
Expand Down
27 changes: 23 additions & 4 deletions core/src/main/java/feign/Contract.java
Expand Up @@ -233,7 +233,7 @@ protected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[
if (annotationType == Param.class) { if (annotationType == Param.class) {
String name = ((Param) annotation).value(); String name = ((Param) annotation).value();
checkState(emptyToNull(name) != null, "Param annotation was empty on param %s.", checkState(emptyToNull(name) != null, "Param annotation was empty on param %s.",
paramIndex); paramIndex);
nameParam(data, name, paramIndex); nameParam(data, name, paramIndex);
if (annotationType == Param.class) { if (annotationType == Param.class) {
Class<? extends Param.Expander> expander = ((Param) annotation).expander(); Class<? extends Param.Expander> expander = ((Param) annotation).expander();
Expand All @@ -244,16 +244,17 @@ protected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[
isHttpAnnotation = true; isHttpAnnotation = true;
String varName = '{' + name + '}'; String varName = '{' + name + '}';
if (data.template().url().indexOf(varName) == -1 && if (data.template().url().indexOf(varName) == -1 &&
!searchMapValues(data.template().queries(), varName) && !searchMapValuesContainsExact(data.template().queries(), varName) &&
!searchMapValues(data.template().headers(), varName)) { !searchMapValuesContainsSubstring(data.template().headers(), varName)) {
data.formParams().add(name); data.formParams().add(name);
} }
} }
} }
return isHttpAnnotation; return isHttpAnnotation;
} }


private static <K, V> boolean searchMapValues(Map<K, Collection<V>> map, V search) { private static <K, V> boolean searchMapValuesContainsExact(Map<K, Collection<V>> map,
V search) {
Collection<Collection<V>> values = map.values(); Collection<Collection<V>> values = map.values();
if (values == null) { if (values == null) {
return false; return false;
Expand All @@ -268,6 +269,24 @@ private static <K, V> boolean searchMapValues(Map<K, Collection<V>> map, V searc
return false; return false;
} }


private static <K, V> boolean searchMapValuesContainsSubstring(Map<K, Collection<String>> map,
String search) {
Collection<Collection<String>> values = map.values();
if (values == null) {
return false;
}

for (Collection<String> entry : values) {
for (String value : entry) {
if (value.indexOf(search) != -1) {
return true;
}
}
}

return false;
}

private static Map<String, Collection<String>> toMap(String[] input) { private static Map<String, Collection<String>> toMap(String[] input) {
Map<String, Collection<String>> Map<String, Collection<String>>
result = result =
Expand Down
12 changes: 8 additions & 4 deletions core/src/main/java/feign/Headers.java
Expand Up @@ -8,7 +8,7 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME; import static java.lang.annotation.RetentionPolicy.RUNTIME;


/** /**
* Expands headers supplied in the {@code value}. Variables are permitted as values. <br> * Expands headers supplied in the {@code value}. Variables to the the right of the colon are expanded. <br>
* <pre> * <pre>
* &#64;Headers("Content-Type: application/xml") * &#64;Headers("Content-Type: application/xml")
* interface SoapApi { * interface SoapApi {
Expand All @@ -24,9 +24,13 @@
* }) void post(&#64;Param("token") String token); * }) void post(&#64;Param("token") String token);
* ... * ...
* </pre> * </pre>
* <br> <strong>Note:</strong> Headers do not overwrite each other. All headers with the same name * <br> <strong>Notes:</strong>
* will be included in the request. <br><br><b>Relationship to JAXRS</b><br> <br> The following two * <ul>
* forms are identical. <br> Feign: * <li>If you'd like curly braces literally in the header, urlencode them first.</li>
* <li>Headers do not overwrite each other. All headers with the same name will be included
* in the request.</li>
* </ul>
* <br><b>Relationship to JAXRS</b><br> <br> The following two forms are identical. <br><br> Feign:
* <pre> * <pre>
* &#64;RequestLine("POST /") * &#64;RequestLine("POST /")
* &#64;Headers({ * &#64;Headers({
Expand Down
11 changes: 2 additions & 9 deletions core/src/main/java/feign/RequestTemplate.java
Expand Up @@ -215,15 +215,8 @@ public RequestTemplate resolve(Map<String, ?> unencoded) {
for (String field : headers.keySet()) { for (String field : headers.keySet()) {
Collection<String> resolvedValues = new ArrayList<String>(); Collection<String> resolvedValues = new ArrayList<String>();
for (String value : valuesOrEmpty(headers, field)) { for (String value : valuesOrEmpty(headers, field)) {
String resolved; String resolved = urlDecode(expand(value, encoded));
if (value.indexOf('{') == 0) { resolvedValues.add(resolved);
resolved = expand(value, unencoded);
} else {
resolved = value;
}
if (resolved != null) {
resolvedValues.add(resolved);
}
} }
resolvedHeaders.put(field, resolvedValues); resolvedHeaders.put(field, resolvedValues);
} }
Expand Down
48 changes: 48 additions & 0 deletions core/src/test/java/feign/DefaultContractTest.java
Expand Up @@ -237,6 +237,19 @@ public void headerParamsParseIntoIndexToName() throws Exception {


assertThat(md.indexToName()) assertThat(md.indexToName())
.containsExactly(entry(0, asList("authToken"))); .containsExactly(entry(0, asList("authToken")));
assertThat(md.formParams()).isEmpty();
}

@Test
public void headerParamsParseIntoIndexToNameNotAtStart() throws Exception {
MethodMetadata md = parseAndValidateMetadata(HeaderParamsNotAtStart.class, "logout", String.class);

assertThat(md.template())
.hasHeaders(entry("Authorization", asList("Bearer {authToken}", "Foo")));

assertThat(md.indexToName())
.containsExactly(entry(0, asList("authToken")));
assertThat(md.formParams()).isEmpty();
} }


@Test @Test
Expand Down Expand Up @@ -358,6 +371,13 @@ interface HeaderParams {
void logout(@Param("authToken") String token); void logout(@Param("authToken") String token);
} }


interface HeaderParamsNotAtStart {

@RequestLine("POST /")
@Headers({"Authorization: Bearer {authToken}", "Authorization: Foo"})
void logout(@Param("authToken") String token);
}

interface CustomExpander { interface CustomExpander {


@RequestLine("POST /?date={date}") @RequestLine("POST /?date={date}")
Expand Down Expand Up @@ -528,6 +548,34 @@ public void parameterizedHeaderExpandApi() throws Exception {
.isEmpty(); .isEmpty();
} }


@Test
public void parameterizedHeaderNotStartingWithCurlyBraceExpandApi() throws Exception {
List<MethodMetadata>
md =
contract.parseAndValidatateMetadata(
ParameterizedHeaderNotStartingWithCurlyBraceExpandApi.class);

assertThat(md).hasSize(1);

assertThat(md.get(0).configKey())
.isEqualTo("ParameterizedHeaderNotStartingWithCurlyBraceExpandApi#getZone(String,String)");
assertThat(md.get(0).returnType())
.isEqualTo(String.class);
assertThat(md.get(0).template())
.hasHeaders(entry("Authorization", asList("Bearer {authHdr}")),
entry("Accept", asList("application/json")));
// Ensure that the authHdr expansion was properly detected and did not create a formParam
assertThat(md.get(0).formParams())
.isEmpty();
}

@Headers("Authorization: Bearer {authHdr}")
interface ParameterizedHeaderNotStartingWithCurlyBraceExpandApi {
@RequestLine("GET /api/{zoneId}")
@Headers("Accept: application/json")
String getZone(@Param("zoneId") String vhost, @Param("authHdr") String authHdr);
}

@Headers("Authorization: {authHdr}") @Headers("Authorization: {authHdr}")
interface ParameterizedHeaderBase { interface ParameterizedHeaderBase {
} }
Expand Down
33 changes: 33 additions & 0 deletions core/src/test/java/feign/RequestTemplateTest.java
Expand Up @@ -142,6 +142,39 @@ public void resolveTemplateWithHeaderSubstitutions() {
.hasHeaders(entry("Auth-Token", asList("1234"))); .hasHeaders(entry("Auth-Token", asList("1234")));
} }


@Test
public void resolveTemplateWithHeaderSubstitutionsNotAtStart() {
RequestTemplate template = new RequestTemplate().method("GET")
.header("Authorization", "Bearer {token}");

template.resolve(mapOf("token", "1234"));

assertThat(template)
.hasHeaders(entry("Authorization", asList("Bearer 1234")));
}

@Test
public void resolveTemplateWithHeaderWithURLEncodedElements() {
RequestTemplate template = new RequestTemplate().method("GET")
.header("Encoded", "%7Bvar%7D");

template.resolve(mapOf("var", "1234"));

assertThat(template)
.hasHeaders(entry("Encoded", asList("{var}")));
}

@Test
public void resolveTemplateWithHeaderEmptyResult() {
RequestTemplate template = new RequestTemplate().method("GET")
.header("Encoded", "{var}");

template.resolve(mapOf("var", ""));

assertThat(template)
.hasHeaders(entry("Encoded", asList("")));
}

@Test @Test
public void resolveTemplateWithMixedRequestLineParams() throws Exception { public void resolveTemplateWithMixedRequestLineParams() throws Exception {
RequestTemplate template = new RequestTemplate().method("GET")// RequestTemplate template = new RequestTemplate().method("GET")//
Expand Down

0 comments on commit 7e25bbb

Please sign in to comment.