프로그래밍/Android

[Okhttp3] Expected URL scheme 'http' or 'https' but no colon was found 해결방법

Lou Park 2021. 10. 26. 00:29
Fatal Exception: java.lang.IllegalArgumentException
Expected URL scheme 'http' or 'https' but no colon was found
okhttp3.HttpUrl$Builder.parse$okhttp (HttpUrl.java:1260)
okhttp3.HttpUrl$Companion.get (HttpUrl.java:1633)
okhttp3.Request$Builder.url (Request.java:184)
okhttp3.Cache$Entry.response (Cache.java:641)
okhttp3.Cache.get$okhttp (Cache.java:183)
okhttp3.internal.cache.CacheInterceptor.intercept (CacheInterceptor.java:47)
okhttp3.internal.http.RealInterceptorChain.proceed (RealInterceptorChain.java:109)
okhttp3.internal.http.BridgeInterceptor.intercept (BridgeInterceptor.java:83)
okhttp3.internal.http.RealInterceptorChain.proceed (RealInterceptorChain.java:109)
okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept (RetryAndFollowUpInterceptor.java:76)
okhttp3.internal.http.RealInterceptorChain.proceed (RealInterceptorChain.java:109)
okhttp3.internal.connection.RealCall.getResponseWithInterceptorChain$okhttp (RealCall.java:201)
okhttp3.internal.connection.RealCall$AsyncCall.run (RealCall.java:517)

오랫동안 앱에서 겪은 오류인데, URL같은건 상수로 관리하기 때문에 http://나 https:// 같은 프로토콜을 빠뜨릴리 없었다. 

아무리 찾아봐도 잘못된 URL로 요청을 하지 않는 것 같은데, 오류는 멎질않았다.

특히나 reproduce가 쉽지 않은 오류기때문에 정말...몇개의 버전에 걸쳐서 고친 것 같다. (계속 고통받아주신 유저님...감사요..(?)ㅋㅋㅋㅋ) 


원인은 무엇일까?

Okhttp Cache 내용이 손상되어(corrupt) 일어난 일이다. 원래 https://~하던 주소가 깨져서 s:// 라던가 등등의 형태로 불러와져서 저런 오류가 발생하게 된다. 자세한 논의는 아래 github에서 확인할 수 있다.

https://github.com/square/okhttp/issues/6453

 

해결방법

처음에 그럼 캐시를 사용하지 않으면 되겠지하고 cache(null)을 날려보았으나 여전히 문제가 지속적으로 리포트되었다.

그래서 그냥 캐시를 쓰되, 올바른 프로토콜을 갖지 않은 캐시는 앱 시작시에 모두 날려버리는 것으로 변경 해 보았다.

 

Okhttp Client를 생성할때, 충분한 크기의 캐시를 만들고, 직접만든 clearMalformedUrls()를 통해서 불순한 캐시들을 날려준다.

 val cache = Cache(
    directory = File(application.cacheDir, "http_cache"),
    maxSize = 50L * 1024L * 1024L // 50 MB
)

cache.clearMalformedUrls()

val builder = OkHttpClient.Builder()
    .cache(cache)
    .connectTimeout(10, TimeUnit.SECONDS)
    .readTimeout(10, TimeUnit.SECONDS)

 

clearMalformedUrls() 익스텐션 코드는 다음과 같다.

toHttpUrlOrNull()은 Okhttp3 유틸함수로, http://나 https://가 아니면 null을 반환한다.

fun Cache.clearMalformedUrls() {
    // corrupt 된 캐시 삭제하기
    val urlIterator = urls()
    while (urlIterator.hasNext()) {
        if (urlIterator.next().toHttpUrlOrNull() == null) {
            urlIterator.remove()
        }
    }
}

 

 

이렇게 하니까 해당 오류가 Crashlytics에서 싹-사라졌다.

 


이래도 안된다면?

위 내용으로도 충분한 해결책이라고 생각되지만

(이전에 cache(null)이 안먹힌 것도 있고) 실제 서비스중인 앱이라...추가적으로 더 조치를 해준게있는데,

바로 Okhttp 버전을 높인것이다. 

 

아직은 Alpha 버전이기때문에 stable한것은 아니지만, ChangeLog에 "Fix: Fail fast when the cache is corrupted."라고 적혀있었기 때문에 2중방어용으로 한 번 써보았다. 다른 사이드이펙트는 아직까지 보고되지 않았다.

implementation "com.squareup.okhttp3:okhttp:5.0.0-alpha.2"