Skip to content

Commit e32cdf4

Browse files
unknownAdrian Cole
authored andcommitted
Added possibility to leave slash encoded in path parameters
1 parent 44459da commit e32cdf4

6 files changed

Lines changed: 74 additions & 2 deletions

File tree

core/src/main/java/feign/Contract.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,9 @@ protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodA
166166
data.template().append(
167167
requestLine.substring(requestLine.indexOf(' ') + 1, requestLine.lastIndexOf(' ')));
168168
}
169+
170+
data.template().decodeSlash(RequestLine.class.cast(methodAnnotation).decodeSlash());
171+
169172
} else if (annotationType == Body.class) {
170173
String body = Body.class.cast(methodAnnotation).value();
171174
checkState(emptyToNull(body) != null, "Body annotation was empty on method %s.",

core/src/main/java/feign/RequestLine.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,5 @@
4747
public @interface RequestLine {
4848

4949
String value();
50+
boolean decodeSlash() default true;
5051
}

core/src/main/java/feign/RequestTemplate.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ public final class RequestTemplate implements Serializable {
5656
private transient Charset charset;
5757
private byte[] body;
5858
private String bodyTemplate;
59+
private boolean decodeSlash = true;
5960

6061
public RequestTemplate() {
6162

@@ -71,6 +72,7 @@ public RequestTemplate(RequestTemplate toCopy) {
7172
this.charset = toCopy.charset;
7273
this.body = toCopy.body;
7374
this.bodyTemplate = toCopy.bodyTemplate;
75+
this.decodeSlash = toCopy.decodeSlash;
7476
}
7577

7678
private static String urlDecode(String arg) {
@@ -200,7 +202,10 @@ public RequestTemplate resolve(Map<String, ?> unencoded) {
200202
for (Entry<String, ?> entry : unencoded.entrySet()) {
201203
encoded.put(entry.getKey(), urlEncode(String.valueOf(entry.getValue())));
202204
}
203-
String resolvedUrl = expand(url.toString(), encoded).replace("%2F", "/").replace("+", "%20");
205+
String resolvedUrl = expand(url.toString(), encoded).replace("+", "%20");
206+
if (decodeSlash) {
207+
resolvedUrl = resolvedUrl.replace("%2F", "/");
208+
}
204209
url = new StringBuilder(resolvedUrl);
205210

206211
Map<String, Collection<String>>
@@ -246,12 +251,21 @@ public RequestTemplate method(String method) {
246251
this.method = checkNotNull(method, "method");
247252
return this;
248253
}
249-
254+
250255
/* @see Request#method() */
251256
public String method() {
252257
return method;
253258
}
254259

260+
public RequestTemplate decodeSlash(boolean decodeSlash) {
261+
this.decodeSlash = decodeSlash;
262+
return this;
263+
}
264+
265+
public boolean decodeSlash() {
266+
return decodeSlash;
267+
}
268+
255269
/* @see #url() */
256270
public RequestTemplate append(CharSequence value) {
257271
url.append(value);

core/src/test/java/feign/DefaultContractTest.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,22 @@ public void customExpander() throws Exception {
293293
.containsExactly(entry(0, DateToMillis.class));
294294
}
295295

296+
@Test
297+
public void slashAreEncodedWhenNeeded() throws Exception {
298+
MethodMetadata
299+
md =
300+
contract.parseAndValidatateMetadata(
301+
SlashNeedToBeEncoded.class.getDeclaredMethod("getQueues", String.class));
302+
303+
assertThat(md.template().decodeSlash()).isFalse();
304+
305+
md = contract.parseAndValidatateMetadata(
306+
SlashNeedToBeEncoded.class.getDeclaredMethod("getZone", String.class));
307+
308+
assertThat(md.template().decodeSlash()).isTrue();
309+
310+
}
311+
296312
interface Methods {
297313

298314
@RequestLine("POST /")
@@ -405,4 +421,12 @@ public String expand(Object value) {
405421
return String.valueOf(((Date) value).getTime());
406422
}
407423
}
424+
425+
interface SlashNeedToBeEncoded {
426+
@RequestLine(value = "GET /api/queues/{vhost}", decodeSlash = false)
427+
String getQueues(@Param("vhost") String vhost);
428+
429+
@RequestLine("GET /api/{zoneId}")
430+
String getZone(@Param("ZoneId") String vhost);
431+
}
408432
}

core/src/test/java/feign/FeignBuilderTest.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,21 @@ public InvocationHandler create(Target target, Map<Method, MethodHandler> dispat
185185
assertThat(server.takeRequest())
186186
.hasBody("request data");
187187
}
188+
189+
@Test
190+
public void testSlashIsEncodedInPathParams() throws Exception {
191+
server.enqueue(new MockResponse().setBody("response data"));
192+
193+
String url = "http://localhost:" + server.getPort();
194+
195+
TestInterface
196+
api =
197+
Feign.builder().target(TestInterface.class, url);
198+
api.getQueues("/");
199+
200+
assertThat(server.takeRequest())
201+
.hasPath("/api/queues/%2F");
202+
}
188203

189204
interface TestInterface {
190205
@RequestLine("GET")
@@ -201,5 +216,8 @@ interface TestInterface {
201216

202217
@RequestLine("POST /")
203218
String decodedPost();
219+
220+
@RequestLine(value = "GET /api/queues/{vhost}", decodeSlash = false)
221+
String getQueues(@Param("vhost") String vhost);
204222
}
205223
}

core/src/test/java/feign/RequestTemplateTest.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,4 +241,16 @@ public void spaceEncodingInUrlParam() {
241241
assertThat(template.request().url())
242242
.isEqualTo("/api/ABC%20123?key=XYZ+123");
243243
}
244+
245+
@Test
246+
public void encodeSlashTest() throws Exception {
247+
RequestTemplate template = new RequestTemplate().method("GET")
248+
.append("/api/{vhost}")
249+
.decodeSlash(false);
250+
251+
template.resolve(mapOf("vhost", "/"));
252+
253+
assertThat(template)
254+
.hasUrl("/api/%2F");
255+
}
244256
}

0 commit comments

Comments
 (0)