이상한거로 3시간 잡아먹었다
결론과 코드는 맨밑이니 걍 맨밑에가서 메서드복사해다가 써도됨
기본적으로 https://datatracker.ietf.org/doc/html/rfc3986#section-2.2 에 규정된대로,/나 +등 예약된 문자들은 따로 인코딩하지않아도 url에 사용할수있고,그래서 UriComponentsBuilder는 저런값들을 인코딩하지 않는다
근데 문제는 공공데이터포탈의 키는 이걸 인코딩해야한다
문제는 더있는데,만약 그래서 인코딩된 키를 사용하려고 하면,인코딩이 됐으니 %가 들어있어서 다시 인코딩이 된다
즉
GTFN0/s1 이게 인코딩하기전,즉 디코딩키라면
GTFN0%2Fs1 정상인코딩은 이거고
GTFN0%252Fs1이중인코딩은 이거다
즉 %가 두번 인코딩되면 %25가 추가되는거다( / -> %2F -> %252F )
그래서 이럴땐 아예 인코딩을 꺼버리고 사용하면된다,
즉 인코딩을 끄고 미리 인코딩된 키를 사용하면 된다
문제는 쿼리파라미터로 한글을 사용해야할경우다
이러면 한글이 들어가는 모든 쿼리파라미터를 따로 인코딩해야한다(실수하기 딱좋음)
즉 한글+"/"가 들어가면
인코딩된 키로 인코딩을 한다->이중인코딩으로 키 에러
디코딩된 키로 인코딩을 한다->키가 인코딩되지않아 키 에러
인코딩된 키로 인코딩을 하지않는다->한글이 인코딩되지않아 에러
디코딩된 키로 인코딩을 하지않는다-> 키가 인코딩되지않아 키 에러
이렇게 사분면 전부 에러가 나는 대참사가 나게된다
즉 결론은 우리는 결국 수동으로 인코딩을 하고 인코딩을 꺼야한다
문제는 클라이언트에 인코딩을 맞기면 딱 실수하기좋고,이건 api를 처리하는쪽에서 처리해야할문제라는거다
애초에 응집도나 책임으로 봐도 웹관련 책임이 밖으로 새어나가는거부터 맘에안들기도 하니까 말이다
또한 이건 저 공공데이터포탈의 이상동작에 가까우니,전역api통신을 담당하는 클래스가 있다면 해당 객체에 넣는거도 책임이 유출된다,즉 최외곽 어댑터,공공데이터포탈 통신어댑터에 이 로직이 들어가는게 맞다
그래서 어떻게 처리했냐면
전역api통신 인터페이스에
fun buildUrl(
baseUrl: String,
queryParams: MultiValueMap<String, String>?,
encoded: Boolean = false,
): URI
이렇게 encode를 미리 했는지 체크하는 매개변수를 추가하고,기본값은 인코딩을 자동으로하는 false로 뒀다(미리 인코딩을 하지않았다는뜻)
그리고 구현체에
override fun buildUrl(
baseUrl: String,
queryParams: MultiValueMap<String, String>?,
encoded: Boolean,
): URI =
UriComponentsBuilder
.fromHttpUrl(baseUrl)
.queryParams(queryParams)
.build(encoded)
.toUri()
이렇게 build에 encoded로 인코딩을 할지말지를 체크했고(false면 UriComponentsBuilder가 인코딩을 하고,
true면 UriComponentsBuilder가 인코딩을 하지않음)
이걸 가져다쓰는곳(즉 공공데이터포탈 통신어댑터)에서
//import org.apache.catalina.util.URLEncoder 이거사용
// 오픈api용 인코딩,/나 +같은걸 한글과 같이 인코딩하기위해 필요
private fun specialEncode(queryParams: MultiValueMap<String, String>?): MultiValueMap<String, String> {
val encodeQueryParams = LinkedMultiValueMap<String, String>()
val urlEncoder = URLEncoder()
if (queryParams != null) {
for ((key, valueList) in queryParams) {
val resValueList = mutableListOf<String>()
for (value in valueList) {
resValueList.add(urlEncoder.encode(value, Charset.forName("UTF-8")))
}
encodeQueryParams.addAll(key, resValueList)
}
}
return encodeQueryParams
}
저렇게 아파치의 URLEncoder을 사용해서 인코딩을 수동으로 처리하고(저건 /나 +를 다 인코딩해줌,코드짜서 만들려다가 저거 찾아서 저거로 처리했음)
override fun fetch(queryKey: String): Map<String, String> {
val restTemplate = apiHelper.createRestTemplate()
val queryParams = createQueryParams(queryKey, ApiConfig.getOpenApiKey())
val specialEncode = specialEncode(queryParams)
val url = apiHelper.buildUrl(getBaseUrl(), specialEncode, true)
return apiCall(restTemplate, url, queryKey)
}
해당 코드를 사용하기전에 수동으로 인코딩하고,true를 넣어서 미리 인코딩했으니 인코딩하지말아달라고 보내는식으로 처리했다
진짜 개발하다보면 별에별일이 다생긴다 ㅋㅋ
'스프링' 카테고리의 다른 글
스프링에서 특정 패키지 테스트에서 제외하기 (0) | 2024.09.26 |
---|---|
spring ai의 openAiChatModel로 Perplexity같은 다른 회사와 통신하기 (0) | 2024.09.24 |
spring AI gradle 의존성 추가 방법 (0) | 2024.08.22 |
[코틀린]Sort객체를 QueryDsl의 OrderSpecifier로 바꿔서 정렬하기(pageable도?) (0) | 2024.04.11 |
스프링 테스트시 application.yaml을 작성했는데 읽지못할때 (0) | 2024.03.22 |