Android Undertow 2.0.42.Final 서버 작동시 오류
회사 프로젝트에서 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를 사용하기 위한 의존성이니 추가해주도록 하자.