Android/Exception handling

Android Undertow 2.0.42.Final 서버 작동시 오류

y0ngha 2022. 1. 27. 09:36

회사 프로젝트에서 Undertow를 사용해서 서버를 열어주고 있었다.

이번 프로덕트는 Android OS 환경에서 작동되는 어플리케이션이다.

 

Undertow의 Minor 버전이 올라가면서(2.0.x → 2.1.x) 해결이 된 것 같은데, 나는 2.0.x 버전을 사용중이여서 해당 오류를 해결하기 위해 사용한 방법을 기술하고자 한다.

(2.1.x 로 버전을 올리면 안드로이드 앱이 작동을 안하더라.. 이유는 모르겠다. 그냥 어플리케이션이 종료가 된다.)

 

먼저 AOS환경의 Api Level은 27버전으로 8.1(Oreo MR1)을 사용하고 있다.

Undertow를 이용해 개발한 웹 프론트엔드를 띄우고, API 서버를 열어주는 역할을 진행해야하는데 서버는 다 열었으나 POST 요청만 하게 되면 Exception이 발생하면서 500 Internal Server Error 가 발생하고 있었다.

override fun handleRequest(exchange: HttpServerExchange) {
    .
    .
    .
    
    val requestBody = exchange.inputStream.readBytes() // -- Exception!
    
    .
    .
    .
}

오류의 발생지는 undertow-core.2.0.42.final.jar::io.undertow.server.DirectBufferDeallocator.class 파일에서 발생하고 있었으며, 오류 내용으로는 "'0.9'를 parseInt(); 처리 할 수 없다." 라는 내용이었다.

 

나는 0.9라는걸 쓰지도 않았고, 0.9라는거를 Body에 실어 보내지도 않았는데 이게 웬 오류인가? 싶었다.

그래서 stackTrace를 좇아가다보니 마침내 위 class 파일에서 발생하고 있음을 확인하였다.

 

undertow-core.2.0.42.final.jar::io.undertow.server.DirectBufferDeallocator.class 23번째 Line

static {
    String versionString = System.getProperty("java.specification.version");
    if(versionString.startsWith("1.")) {
        versionString = versionString.substring(2);
    }
    int version = Integer.parseInt(versionString);

    Method tmpCleaner = null;
    Method tmpCleanerClean = null;
    boolean supported;
    Unsafe tmpUnsafe = null;
    .
    .
    .
    .
}

System.getProperty("java.specification.version") 을 진행하게 되면 시스템에 깔려있는 VM 버전을 갖고오는것인데 1.x 버전대면 substring을 해서 갖고오는 것을 확인했다.

java.specification.version을 안드로이드에서 갖고오게되면 무조건 '0.9' 를 반환하게 되어 있는데, Java에서 Integer.parseInt("0.9") 를 진행하게 되면 오류가 발생하는 것이었다.

 

이 오류를 해결하고자 undertow GitHub에 들어가 Repository를 Fork하고 코드를 다운받았다.

(GitHub: https://github.com/undertow-io/undertow 2.0.x 버전을 받고자 한다면 Branch를 수정해줘야 한다.)

 

코드를 다운로드 받고, 위 클래스 파일을 찾아가 아래와 같이 변경해줬다.

static {
    Method tmpCleaner = null;
    Method tmpCleanerClean = null;
    Unsafe tmpUnsafe = null;
    String versionString = System.getProperty("java.specification.version");
    boolean supported;
    if (!versionString.equals("0.9")) {
        if (versionString.startsWith("1.")) {
        versionString = versionString.substring(2);
        }

        int version = Integer.parseInt(versionString);
        if (version < 9) {
        try {
            tmpCleaner = getAccesibleMethod("java.nio.DirectByteBuffer", "cleaner");
            tmpCleanerClean = getAccesibleMethod("sun.misc.Cleaner", "clean");
            supported = true;
        } catch (Throwable var8) {
            UndertowLogger.ROOT_LOGGER.directBufferDeallocatorInitializationFailed(var8);
            supported = false;
        }
        } else {
        try {
            tmpUnsafe = getUnsafe();
            tmpCleanerClean = getDeclaredMethod(tmpUnsafe, "invokeCleaner", ByteBuffer.class);
            supported = true;
        } catch (Throwable var7) {
            UndertowLogger.ROOT_LOGGER.directBufferDeallocatorInitializationFailed(var7);
            supported = false;
        }
        }
    } else {
        supported = false;
    }

    SUPPORTED = supported;
    cleaner = tmpCleaner;
    cleanerClean = tmpCleanerClean;
    UNSAFE = tmpUnsafe;
}

Property의 값이 '0.9'라면 supported를 false로 바꾸도록 예외처리를 진행해줬다.

이 supported는 free 함수를 작동할 때 사용된다.

 

위처럼 바꿨으면, 이제 기존에 사용하던 undertow를 사용하지 않고 직접 수정한 undertow를 적용해줘야 한다.

그러기 위해서 jar 파일을 만들어줘야 하니 아래와 같이 터미널에 입력하자.

cd core
# 테스트를 스킵하고 빌드 진행
mvn package -Dmaven.test.skip=true

package 작업이 정상적으로 끝났으면 core/target/undertow-core-2.0.42.Final.jar 이라는 파일이 생겼을 것이다.

위 파일을 android에서 사용해주면 되는데, 나는 gradle.kts를 사용중이라 아래와 같이 적용했다.

dependencies {
    .
    .
    .
    implementation("org.jboss.xnio", "xnio-api", "3.3.8.Final")
    implementation("org.jboss.xnio", "xnio-nio", "3.3.8.Final")
    api(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
    .
    .
    .
}

libs 폴더에 jar을 넣어주고 위처럼 작성하면 처리 된다.

xnio-api, nio는 undertow를 사용하기 위한 의존성이니 추가해주도록 하자.