일반적으로.. 모바일 폰은 서버로부터 데이터를 받아 동작하는 클라이언트의 역할을 하는 것이 대부분이다. 또한, HTTP REST API 기능을 앱으로 구현한다고 한다면, 클라이언트의 기능을 구현한다고 생각하는 것이 일반적이다. 이동형 장치의 특성상, 수시로 네트워크가 변경되는 모바일 폰에 서버를 구축한다는 것 자체가 적합한 일은 아니다.
이유가 어찌 되었든,
기술적으로 모바일에서 REST API 서버를 구축할 수 있는 라이브러리가 있다.
## NanoHTTPd
2016년 8월에 마지막 릴리즈를 한 매우 오래된 open-source 이다. 하지만, 심플한 편이라 지금도 잘 동작한다.
아래 링크의 guide가 정리가 잘 되어 도움이 되었다.
A Guide to NanoHTTPD
https://www.baeldung.com/nanohttpd
dependency 설정
앱 수준 gradle 파일에 아래와 같이 nanohttpd를 위한 dependency를 설정한다.
nanohttpd-nanolets 는 multi route를 사용할 경우 필요하다.
dependencies {
...
// for REST API server
implementation("org.nanohttpd:nanohttpd:2.3.1")
implementation("org.nanohttpd:nanohttpd-nanolets:2.3.1")
...
}
kotlin 적용 코드
아래는 NanoHTTPd 를 적용하여 구현한 테스트 코드 중 일부이다.
// singlton
class RESTManager(private val port: Int) : RouterNanoHTTPD(port) {
companion object {
@Volatile private var server: RESTManager? = null
fun getServer(port: Int): RESTManager {
return server ?: synchronized(this) {
server ?: RESTManager(port).also {
server = it
}
}
}
}
fun addRoute(url: String, routeHandler: Class<*>, looperHandler: Handler) {
super.addRoute(url, routeHandler, looperHandler)
}
fun startServer() {
Log.i(LOG_TAG, "start rest server with $ipAddr:$port")
start()
}
}
// simple http server test
class HTTPManager(private val handler: Handler) : NanoHTTPD(7777) {
init {
start(SOCKET_READ_TIMEOUT, false)
}
override fun serve(session: IHTTPSession): Response {
Log.d(LOG_TAG_DEBUG, "URI:: ${session.uri}")
Log.d(LOG_TAG_DEBUG, "method:: ${session.method}")
val hashMap = HashMap<String, String>()
session.parseBody(hashMap)
val receivedBody = if(hashMap.isNotEmpty()) hashMap["postData"] else session.queryParameterString
if (receivedBody != null) {
val data = Json.decodeFromString<RestTestRequest>(receivedBody)
val msg: Message = Message().apply {
this.obj = mapOf(session.uri to data)
}
handler.sendMessage(msg)
}
return newFixedLengthResponse("THIS IS TEST SERVER")
}
}
// for multi route
class TestRestHandler : RouterNanoHTTPD.GeneralHandler() {
private lateinit var looperHandler: Handler?
override fun get(
uriResource: RouterNanoHTTPD.UriResource,
urlParams: Map<String, String>,
session: NanoHTTPD.IHTTPSession
): NanoHTTPD.Response {
val sessingParam = session.parameters!!
Log.d(LOG_TAG_DEBUG, sessingParam.toString())
sessingParam.forEach {
Log.d(LOG_TAG_DEBUG, "${it.key} : ${it.value}")
}
return NanoHTTPD.newFixedLengthResponse("Requested: \n$sessingParam\n")
}
override fun post(
uriResource: RouterNanoHTTPD.UriResource?,
urlParams: MutableMap<String, String>?,
session: NanoHTTPD.IHTTPSession?
): NanoHTTPD.Response {
Log.d(LOG_TAG_DEBUG, "${this::class.simpleName} POST !!")
if(!::looperHandler.isInitialized)
looperHandler = uriResource?.initParameter(Handler::class.java)
val hashMap = HashMap<String, String>()
session?.parseBody(hashMap)
val receivedBody = if(hashMap.isNotEmpty()) hashMap["postData"] else session?.queryParameterString
if (receivedBody != null) {
// RestTestRequest는 @Serializable data class
val data = Json.decodeFromString<RestTestRequest>(receivedBody)
val json = Json.parseToJsonElement(receivedBody)
json.jsonObject.toMap().forEach {
Log.d(LOG_TAG_DEBUG, "receivedBody:: ${it.key} : ${it.value}")
}
// POST로 전달받은 Json Data로부터 필요한 동작들 수행.
looperHandler?.sendEmptyMessage(data.cmdCode)
}
return NanoHTTPD.newFixedLengthResponse("Received body:\n$receivedBody\n")
}
}
위 클래스들은 아래와 같이 사용할 수 있다.
- HTTP Server
val http = HTTPManager(object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
var uri = ""
lateinit var data: RestTestRequest
(msg.obj as Map<*, *>).firstNotNullOf {
uri = it.key as String
data = it.value as RestTestRequest
}
Log.d(LOG_TAG_DEBUG, "DATA:: $data")
super.handleMessage(msg)
}
})
- REST API Server (multi route)
private fun startREST(port: Int) {
restManager = RESTManager.getServer(port)
// IndexHandler는 nanoHttpd에서 제공하는 기본 handler로 "Hello World!"를 응답한다.
restManager.addRoute("/", IndexHandler::class.java)
// runTestCommand 는 looper handler
restManager.addRoute("/test", RestHandler::class.java, runTestCommand)
restManager.startServer()
}
반응형
'프로그래밍 > android' 카테고리의 다른 글
[android studio] multiple build operations failed 오류 해결 (0) | 2024.05.09 |
---|---|
[kotlin] 현재 위치의 함수명, 라인번호 가져오기 (0) | 2024.03.27 |
[kotlin] RecyclerView 에서 맨 아래로 스크롤 (0) | 2022.06.18 |
[kotlin] 뒤로가기 두 번 눌러 종료하는 코드 (0) | 2022.01.08 |
wireless debugging on Android 11 (0) | 2021.12.18 |