본문으로 바로가기
반응형

Photo by unsplash

앞서서 만든 프로젝트를 이용하여 HTTP API 서버를 생성해 봅니다. 혹시라도 Ktor 프로젝트 생성을 보지 않았다면, 앞선 포스팅부터 보고 오시길 바랍니다. [1]

Ktor은 server와 client 모두를 지원합니다. 따라서 ktor를 이용하여 HTTP API 서버를 생성하고, Android에서 ktor client를 이용하여 이를 받아와 표시하는 sample project를 만듭니다. 먼저 최종적인 결과는 아래와 같습니다

Android에서 구현한 최종 결과화면

서버에서는 아래와 같은 정보를 제공하며, 이 정보를 client에서 읽어 화면에 표기합니다.

  • 이미지 Url
  • 이미지 Title
  • 이미지 Location

resource 추가

수신받을 이미지 두장을 서버에 추가합니다. "nature1.jpg"와 "nature2.jpg" 두장으로 resources/static/nature/ 경로에 추가하도록 합니다.

resrouce 위치

그리고 Client에 전달할 세 가지 정보를 담을 data class를 아래 경로에 추가합니다. (data/Picture.kt)

Pciture class

import kotlinx.serialization.Serializable

@Serializable
data class Picture(
    val title: String,
    val location: String,
    val imageUrl: String
)

kotlinx의 serialization을 사용하므로 @Serializable annotation만으로도 json객체로 변환이 가능합니다.

Route (GET) 추가

Restful api를 호출할 경로를 아래와 같이 설정하겠습니다.

  • HTTP method : GET
  • API 경로: "/picture"
  • parameter: "id"
    • "id"는 0, 1 두 개의 값을 가지며 각각 nature1.jpg, nature2.jpg를 나타냄. 그 이외의 값은 error 처리

최종적으로 client에서는 아래의 형태로 호출하도록 만듭니다.

http://localhost/picture?id=0

routes라는 package를 추가하고, NatureRoute.kt 파일을 생성하겠습니다.

NatrueRoute.kt의 위치

내부에 아래와 같이 코드를 작성합니다

private const val BASE_URL = "http://192.168.0.6"

fun Route.naturePicture() {
    get("/picture") {
        //param 정보 읽음.
        val id = call.parameters["id"] ?: "-1"

        // 전달할 정보(Picture객체)를 생성
        val pictureData = getPictureData(Integer.parseInt(id))
        if (pictureData == null) {
            // 해당 객체가 없을경우 400 에러 응답
            call.respond(HttpStatusCode.BadRequest)
        } else {
            // 해당 객체 응답.
            call.respond(HttpStatusCode.OK, pictureData)
        }
    }   
}

private fun getPictureData(id: Int): Picture? {
    return when (id) {
        0 -> Picture("falls", "No address!!", "$BASE_URL/nature/nature1.jpg")
        1 -> Picture("White hill", "Santorini", "$BASE_URL/nature/nature2.jpg")
        else -> null
    }
}

Route 함수에 extension function으로 naturePicture()라는 함수를 추가합니다. connection 정보를 담고 있는 call 객체에서 call.parameters를 이용하여 param 정보를 받습니다. 받은 정보를 이용하여 id에 맞는 Picture 객체를 생성하여 call.respond로 결과를 반환합니다.

Get 함수의 구현이 끝났습니다. 마지막으로 해당 경로를 routing path에 추가하기 위하여 아래와 같이 Routing.kt 파일에 추가합니다.

기존에 만들어 놓은 Routing.kt 경로

fun Application.configureRouting() {
    routing {
        get("/") {
            call.respondText("Do you wanna build a snowman?")
        }
        // "/picture" 경로에 대한 get 처리를 추가
        naturePicture()
        // Static plugin. Try to access `/static/index.html`
//        static("/static") {
        static {
            resources("static")
        }
    }
}

naturePicture()Route의 extention function으로 생성했기 때문에 routing {..} 블록 안에서 바로 호출이 가능합니다.

위 코드 중에 getPictureData() 함수를 보면 Picture 객체 생성 시 img url을 아래와 같이 생성합니다.

private fun getPictureData(id: Int): Picture? {
...
        0 -> Picture("falls", "No address!!", "$BASE_URL/nature/nature1.jpg")
...
}

따라서 이미지 url은 "http://192.168.0.6/nature/nature1.jpg"의 경로로 접근하게 됩니다. 하지만 프로젝트의 기본값을 그대로 유지할 경우 실제 이미지에 접근하기 위해서는 "http://192.168.0.6/static/nature/nature1.jpg"으로 접근해야 합니다.

여기서는 불필요한 경로를 단축시키기 위해 "/static" 경로를 삭제하기 위하여 위 코드에서 처럼 static("/static")에서 param으로 들어가는 remote path를 삭제하여 최종적으로 static resource에 접근 시 "/static" 없이 가능하도록 바꿉니다.

web browser로 접속하여 해당 경로에 대한 접근이 정상적으로 일어나는지 확인이 가능합니다.

또한 imageUrl 역시 접근 가능한지 web browser를 통해 확인이 가능합니다.

JDK의 버전 변경

web browser로 image url 접근시 로딩이 실패하는경우가 있습니다. 서버 로그를 보면 exception이 주르륵 떠 있는걸 볼수 있습니다. 원인은 JDK8이나 11을 사용하기 때문으로 JDK는 최신 버전으로 사용하도록 변경 합니다

File -> Project structure -> project -> SDK 에서 jdk를 변경하거나 최신 버전을 받을 수 있습니다.

ip 주소 확인 및 Port 설정

이전 글에서 application.conf를 통해서 서버의 port를 조정 가능하다고 언급했습니다. 기본적으로는 8080으로 세팅되어 있으나, 제 경우 80으로 변경했습니다. 만약 8080 포트 그대로 쓴다면 BASE_URL이 "http://192.168.0.6:8080"이 되어야 합니다. 또한 여담이지만 test를 위해 자신의 ip 주소 확인은 아래와 같이 할 수 있습니다.

#Mac
ifconfig | grep inet

#Window
ipconfig

Logger 확인

CallLogging 객체를 plugin으로 추가하고 설정 하였으므로 test를 위해 web browser로 접속 시 아래와 같은 로그를 확인할 수 있습니다.

Route (POST)의 구현

ktor 서버는 GET 이외에 모든 형태의(POST, PUT, DELETE..) HTTP API를 제공합니다. 여기서는 post의 경우 한 가지만 추가적으로 구현해 보겠습니다. [2]

post 요청 시 id를 넘겨받을 json 객체를 위한 data class를 추가합니다.

data/PictureRequest.kt

위 경로에 PictureRequest data class를 추가합니다.

@Serializable
data class PictureRequest(val pictureId:Int)

아까 만들어 두었던 Route.naturePicture() post {..} 블록을 추가합니다.

fun Route.naturePicture() {
    get("/picture") {
        ...
    }
    post(path = "/picture") {
        // body 얻어옴
        val pictureRequest = call.receive<PictureRequest>()

        val pictureData = getPictureData(pictureRequest.pictureId)
        if (pictureData == null) {
            call.respond(HttpStatusCode.BadRequest)
        } else {
            call.respond(HttpStatusCode.OK, pictureData)
        }
    }
}

private fun getPictureData(id: Int): Picture? {
    ...
}

동일한 path ("/picture")에 post방식으로 요청이 들어오는 경우 body의 json 값을 call.receive() 함수를 이용하여 PictureRequest 객체로 받습니다. 응답을 넘겨주는 부분은 get방식과 동일합니다.

post방식의 경우 web browser에서 확인이 어려움으로 다음 포스팅에서 android client를 통해 동작을 확인하도록 하겠습니다.

 

References

[1] 2022.01.24 - [개발이야기/Spring & Ktor Framework] - [Ktor] Ktor 소개 및 서버용 프로젝트 생성 #1

[2] https://ktor.io/docs/creating-http-apis.html

반응형