Skip to content

Commit aa565e4

Browse files
mbarkleykdavisk6
authored andcommitted
Maintain user-given order for header values (OpenFeign#1009)
Fixes bug where HeaderTemplate stored values in a HashSet which caused the following issues: * Header values could be written in wrong order * Order was not stable between JVM instances Fixes OpenFeign#1007
1 parent 5c57213 commit aa565e4

3 files changed

Lines changed: 49 additions & 9 deletions

File tree

core/src/main/java/feign/template/HeaderTemplate.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import java.util.Collections;
2020
import java.util.Iterator;
2121
import java.util.LinkedHashSet;
22-
import java.util.Set;
2322
import java.util.stream.Collectors;
2423
import java.util.stream.StreamSupport;
2524

@@ -30,7 +29,7 @@
3029
public final class HeaderTemplate extends Template {
3130

3231
/* cache a copy of the variables for lookup later */
33-
private Set<String> values;
32+
private LinkedHashSet<String> values;
3433
private String name;
3534

3635
public static HeaderTemplate create(String name, Iterable<String> values) {
@@ -66,10 +65,10 @@ public static HeaderTemplate create(String name, Iterable<String> values) {
6665
* @return a new Header Template with the values added.
6766
*/
6867
public static HeaderTemplate append(HeaderTemplate headerTemplate, Iterable<String> values) {
69-
Set<String> headerValues = new LinkedHashSet<>(headerTemplate.getValues());
68+
LinkedHashSet<String> headerValues = new LinkedHashSet<>(headerTemplate.getValues());
7069
headerValues.addAll(StreamSupport.stream(values.spliterator(), false)
7170
.filter(Util::isNotBlank)
72-
.collect(Collectors.toSet()));
71+
.collect(Collectors.toCollection(LinkedHashSet::new)));
7372
return create(headerTemplate.getName(), headerValues);
7473
}
7574

@@ -82,7 +81,7 @@ private HeaderTemplate(String template, String name, Iterable<String> values, Ch
8281
super(template, ExpansionOptions.REQUIRED, EncodingOptions.NOT_REQUIRED, false, charset);
8382
this.values = StreamSupport.stream(values.spliterator(), false)
8483
.filter(Util::isNotBlank)
85-
.collect(Collectors.toSet());
84+
.collect(Collectors.toCollection(LinkedHashSet::new));
8685
this.name = name;
8786
}
8887

core/src/test/java/feign/template/HeaderTemplateTest.java

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,17 @@
1313
*/
1414
package feign.template;
1515

16+
import static org.hamcrest.CoreMatchers.equalTo;
17+
import static org.junit.Assert.assertEquals;
18+
import static org.junit.Assert.assertThat;
19+
20+
import java.util.ArrayList;
21+
import java.util.Arrays;
22+
import java.util.Collections;
23+
1624
import org.junit.Rule;
1725
import org.junit.Test;
1826
import org.junit.rules.ExpectedException;
19-
import java.util.Arrays;
20-
import java.util.Collections;
21-
import static org.junit.Assert.assertEquals;
2227

2328
public class HeaderTemplateTest {
2429

@@ -58,4 +63,40 @@ public void it_should_return_expanded() {
5863
headerTemplate.expand(Collections.singletonMap("name", "firsts")));
5964
}
6065

66+
@Test
67+
public void create_should_preserve_order() {
68+
/*
69+
* Since Java 7, HashSet order is stable within a since JVM process, so one of these assertions
70+
* should fail if a HashSet is used.
71+
*/
72+
HeaderTemplate headerTemplateWithFirstOrdering =
73+
HeaderTemplate.create("hello", Arrays.asList("test 1", "test 2"));
74+
assertThat(new ArrayList<>(headerTemplateWithFirstOrdering.getValues()),
75+
equalTo(Arrays.asList("test 1", "test 2")));
76+
77+
HeaderTemplate headerTemplateWithSecondOrdering =
78+
HeaderTemplate.create("hello", Arrays.asList("test 2", "test 1"));
79+
assertThat(new ArrayList<>(headerTemplateWithSecondOrdering.getValues()),
80+
equalTo(Arrays.asList("test 2", "test 1")));
81+
}
82+
83+
@Test
84+
public void append_should_preserve_order() {
85+
/*
86+
* Since Java 7, HashSet order is stable within a since JVM process, so one of these assertions
87+
* should fail if a HashSet is used.
88+
*/
89+
HeaderTemplate headerTemplateWithFirstOrdering =
90+
HeaderTemplate.append(HeaderTemplate.create("hello", Collections.emptyList()),
91+
Arrays.asList("test 1", "test 2"));
92+
assertThat(new ArrayList<>(headerTemplateWithFirstOrdering.getValues()),
93+
equalTo(Arrays.asList("test 1", "test 2")));
94+
95+
HeaderTemplate headerTemplateWithSecondOrdering =
96+
HeaderTemplate.append(HeaderTemplate.create("hello", Collections.emptyList()),
97+
Arrays.asList("test 2", "test 1"));
98+
assertThat(new ArrayList<>(headerTemplateWithSecondOrdering.getValues()),
99+
equalTo(Arrays.asList("test 2", "test 1")));
100+
}
101+
61102
}

jaxrs/src/test/java/feign/jaxrs/JAXRSContractTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,7 @@ public void producesWithHeaderParamContainAllHeaders() throws Exception {
401401
assertThat(parseAndValidateMetadata(MixedAnnotations.class, "getWithHeaders",
402402
String.class, String.class, String.class)
403403
.template())
404-
.hasHeaders(entry("Accept", Arrays.asList("{Accept}", "application/json")))
404+
.hasHeaders(entry("Accept", Arrays.asList("application/json", "{Accept}")))
405405
.hasQueries(
406406
entry("multiple", Arrays.asList("stuff", "{multiple}")),
407407
entry("another", Collections.singletonList("{another}")));

0 commit comments

Comments
 (0)