spring 5.2.6 버전 기준
webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/search")
.queryParam("keyword", query.getKeyword())
...
.build()
)
위와 같이 요청을 만들면 keyword 가 인코딩 되어 나가는걸 기대 했다.
한글이나 일부 특수문자들도 잘 되길래 크게 의심없이 넘어갔었다.
하지만 이런 방식으로 하면 1+1 이라는 키워드중 ‘+’ 가 인코딩 되지 않는 이슈가 있다.
webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/search")
.queryParam("keyword", URLEncoder.encode(query.getKeyword())
...
.build()
)
그래서 파라미터를 직접 인코딩을 해보았다. 하지만 이렇게 하면 인코딩이 두번된다.
(기대값 1%2B1, 실제 api 호출시 나가는 값 1%252B1)
queryParam 을 사용하는 경우에 인코딩 되는 부분을 따라가보니
HierarchicalUriComponents.encodeUriComponent 에서 인코딩을 해주는데,
isAllowed 에 허용되지 않는 것들을 인코딩 해준다.
static String encodeUriComponent(String source, Charset charset, Type type) {
if (!StringUtils.hasLength(source)) {
return source;
}
Assert.notNull(charset, "Charset must not be null");
Assert.notNull(type, "Type must not be null");
byte[] bytes = source.getBytes(charset);
boolean original = true;
for (byte b : bytes) {
if (!type.isAllowed(b)) {
original = false;
break;
}
}
if (original) {
return source;
}
ByteArrayOutputStream baos = new ByteArrayOutputStream(bytes.length);
for (byte b : bytes) {
if (type.isAllowed(b)) {
baos.write(b);
}
else {
baos.write('%');
char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, 16));
char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, 16));
baos.write(hex1);
baos.write(hex2);
}
}
return StreamUtils.copyToString(baos, charset);
}
QUERY_PARAM {
@Override
public boolean isAllowed(int c) {
if ('=' == c || '&' == c) {
return false;
}
else {
return isPchar(c) || '/' == c || '?' == c;
}
}
},
protected boolean isPchar(int c) {
return (isUnreserved(c) || isSubDelimiter(c) || ':' == c || '@' == c);
}
protected boolean isSubDelimiter(int c) {
return ('!' == c || '$' == c || '&' == c || '\'' == c || '(' == c || ')' == c || '*' == c || '+' == c ||
',' == c || ';' == c || '=' == c);
}
HierarchicalUriComponents.TYPE.QUERY_PARAM 을 보면
isSubDelimeter 에 '+' 가 있어서 이걸 통과해버린다.
https://github.com/spring-projects/spring-framework/issues/21259
이와 관련된 깃헙 이슈를 찾을수 있었는데, spring 4.3.5 까지는 + 문자를 인코딩 해줬지만 5.0.5 부터는 안해주는 것으로 바뀌었다.
그 이유는 + 문자가 sub-delims 에 속해서 라고 하는데 이건 rfc 문서를 더 자세히 살펴봐야할듯 하다 (https://www.ietf.org/rfc/rfc3986.txt)
The commit references #19394 which also references the RFC3986 stating that '+' is a valid URL query parameter value and should not be encoded.
(sub-delims 는 valid 한 URL query parameter 이기 때문에 encode 되지 않는다고 한다)
인코딩이 되게 하려면 다음과 같이 해야한다.
uriBuilder
.path("/search")
.queryParam("keyword", "{keyword}")
...
.build(request.getKeyword())
이렇게 하면 가능한 이유는 QUERY_PARAM 이 아닌 URI 로 간주되어 위와 다른 경로를 타게된다.
URI {
@Override
public boolean isAllowed(int c) {
return isUnreserved(c);
}
};
이 부분을 타게 되고, sub-delims 라도 인코딩이 되게 된다.
'java' 카테고리의 다른 글
java subList memory leak (0) | 2023.02.20 |
---|---|
java mapping library MapStruct (0) | 2022.12.18 |