๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿ”‹ | ๊ธฐํƒ€ ๊ธฐ์ˆ /๐Ÿ–ฅ๏ธ | Ktor

[Kotlin, Ktor] ktor๋กœ http api ๋งŒ๋“ค๊ธฐ(2)

by immgga 2023. 6. 14.

 

์ง€๋‚œ ํฌ์ŠคํŒ…

https://rkdrkd-history.tistory.com/52

 

[Kotlin, Ktor] ktor ํ”„๋กœ์ ํŠธ ์‹œ์ž‘ํ•˜๊ธฐ(1)

์นœ๊ตฌ๋“ค์ด๋ž‘ ํ•จ๊ป˜ ํ•˜๋Š” ํ”„๋กœ์ ํŠธ์—์„œ ์„œ๋ฒ„ ๋ถ€๋ถ„์„ ktor๋กœ ๋ฐ”๊ธฐ๋กœ ๊ฒฐ์ • ๋‚ฌ๋‹ค. ์ฒ˜์Œ ๋“ค์–ด๋ณธ ktor์ด์ง€๋งŒ, ๊ฒ€์ƒ‰์„ ์ข€ ํ•ด๋ณด๋‹ˆ๊นŒ kotlin์œผ๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋Š” ์„œ๋ฒ„๋ผ๋”๋ผ ์ด๋ฒˆ ๊ธฐํšŒ์— ๊ณต๋ถ€ํ•ด ๋ณด๋ฉด์„œ ktor์— ๋Œ€ํ•ด

rkdrkd-history.tistory.com

 

์ง€๋‚œ ํฌ์ŠคํŒ…์—์„œ ktor project๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณด์•˜๋‹ค.

์ด๋ฒˆ ํฌ์ŠคํŒ…์—์„œ๋Š” ktor๋กœ HTTP api๋ฅผ ๋งŒ๋“ค์–ด ๋ณด๊ฒ ๋‹ค.


ktor ์„œ๋ฒ„ ์‹คํ–‰ํ•˜๊ธฐ

์ผ๋‹จ ktor์„œ๋ฒ„๋ฅผ ์‹คํ–‰์‹œ์ผœ ๋ณด์ž.

ktor project๋ฅผ ๋งŒ๋“ค๋ฉด ๊ธฐ๋ณธ์œผ๋กœ ๋‚˜์˜ค๋Š” ์ƒ˜ํ”Œ ์ฝ”๋“œ๊ฐ€ ์žˆ๋‹ค(์ฒ˜์Œ์— ๋งŒ๋“ค ๋•Œ ์„ค์ •์œผ๋กœ ์ƒ˜ํ”Œ ์ฝ”๋“œ ์ƒ์„ฑ์„ ์•ˆ ํ•˜๊ฒŒ ํ•  ์ˆ˜๋„ ์žˆ๋‹ค).

 

Routing.kt

package com.example.plugins

import io.ktor.server.routing.*
import io.ktor.server.response.*
import io.ktor.server.application.*

fun Application.configureRouting() {
    routing {
        get("/") {
            call.respondText("Hello World!")
        }
    }
}

์ด ์ฝ”๋“œ๋ฅผ ๊ฐ€์ง€๊ณ  ์„œ๋ฒ„๋ฅผ ์‹คํ–‰ํ•ด ๋ณด๊ฒ ๋‹ค.

 

Application.kt๋กœ ์ด๋™ํ•œ ํ›„main ํ•จ์ˆ˜์—์„œ ์‹คํ–‰ํ•ด ๋ณด๋„๋ก ํ•˜์ž.

 

์‹คํ–‰ ๊ฒฐ๊ณผ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

logcat
web

์ด๋ ‡๊ฒŒ ๋‚˜์˜ค๋ฉด ์„ฑ๊ณต์ ์œผ๋กœ ์„œ๋ฒ„ ์‹คํ–‰์„ ๋งˆ์นœ ๊ฒƒ์ด๋‹ค.

 

๊ฐ„๋‹จ api ๋งŒ๋“ค๊ธฐ

๊ณต์‹ ๋ฌธ์„œ์— ๋‚˜์™€ ์žˆ๋Š” ์ฝ”๋“œ๋ฅผ ์ด์šฉํ•ด ๊ฐ„๋‹จํ•œ api๋ฅผ ์ƒ์„ฑํ•ด ๋ณด๋„๋ก ํ•˜๊ฒ ๋‹ค.

 

Customers.kt

package com.example.models

import kotlinx.serialization.Serializable

@Serializable
data class Customers(
    val id: String,
    val firstName: String,
    val lastName: String,
    val email: String
)

val customerStorage = mutableListOf<Customers>()

ktor์—์„œ data ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์–ด ์ค€๋‹ค.

@Serializable ์–ด๋…ธํ…Œ์ด์…˜์ด ktor ์„œ๋ฒ„์— ์‘๋‹ต json์„ ์ƒ์„ฑํ•ด ์ค€๋‹ค.

 

1. GET

get {
    if (customerStorage.isNotEmpty()) call.respond(customerStorage)
    else call.respondText("No Customer Found.", status = HttpStatusCode.OK)
}
get("{id}") {
    val id = call.parameters["id"] ?: return@get call.respondText(
        text = "Missing Id",
        status = HttpStatusCode.BadRequest
    )
    val customer = customerStorage.find { it.id == id } ?: return@get call.respondText(
        text = "No Customer with id $id",
        status = HttpStatusCode.NotFound
    )
    call.respond(customer)
}

์ฒซ ๋ฒˆ์งธ get์€ customer๋“ค์˜ ์ •๋ณด๋ฅผ ๋‚˜์—ดํ•˜๋Š” api์ด๊ณ 

๋‘ ๋ฒˆ์งธ get์€ customerStorage์˜ id๋ฅผ ๊ธฐ์ค€์œผ๋กœ ํ•œ ๋ช…์˜ customer๋งŒ ์กฐํšŒํ•˜๋Š” ๋ถ€๋ถ„์ด๋‹ค.

  • id๋ฅผ ๋จผ์ € ๋ถˆ๋Ÿฌ์˜จ๋‹ค.
    id๊ฐ€ null์ด๋ฉด missing id text๋ฅผ ์ถœ๋ ฅํ•˜๋„๋ก ํ–ˆ๋‹ค.
  • customer ์ •๋ณด๋ฅผ ๋ถˆ๋Ÿฌ์˜จ๋‹ค.
    find๋ฅผ ์ด์šฉํ•ด ๊ฐ™์€ id์˜ ์ •๋ณด๋งŒ ๋ฝ‘์•„์˜จ๋‹ค.
    ์—†์œผ๋ฉด not found ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œํ‚จ๋‹ค.

 

2. POST

post {
    val customer = call.receive<Customers>()
    customerStorage.add(customer)
    call.respondText(
        text = "Customer stored correctly",
        status = HttpStatusCode.Created
    )
}

post๋ฅผ ์ด์šฉํ•ด customer ์ •๋ณด๋ฅผ ์ €์žฅํ•œ๋‹ค.

 

3. DELETE

delete("{id}") {
    val id = call.parameters["id"] ?: return@delete call.respond(HttpStatusCode.BadRequest)
    if (customerStorage.removeIf { it.id == id })
        call.respondText(
            text = "Customer remove correctly",
            status = HttpStatusCode.Accepted
        )
    else call.respondText(text = "Not found.", status = HttpStatusCode.NotFound)
}

delete๋กœ ํŠน์ • id์˜ customer๋ฅผ ์‚ญ์ œํ•œ๋‹ค.

 

4. Response๋ฅผ JSON์œผ๋กœ ์„ค์ •

install(ContentNegotiation) {
    json(Json {
        prettyPrint = true
        isLenient = true
    })
}

์ด ์ฝ”๋“œ๋Š” ktor ์„œ๋ฒ„์˜ response๊ฐ’์„ json์œผ๋กœ ์„ค์ •ํ•ด ์ฃผ๋Š” ๋ถ€๋ถ„์ด๋‹ค.

์ด ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋ฉด ์ถ”ํ›„์— android studio์—์„œ retrofit์œผ๋กœ ํ†ต์‹ ์„ ํ•  ๋•Œ 415 ์—๋Ÿฌ(Unsupported Media Type)๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์œผ๋‹ˆ ์ž‘์„ฑํ•˜๋„๋ก ํ•˜์ž(์‹ค์ œ๋กœ ๊ฒช์—ˆ์—ˆ๋‹ค...ใ… ).

 

ktor์—์„œ post ๋ถ€๋ถ„์˜ receive <T> ๋ถ€๋ถ„์€ T๊ฐ์ฒด๋ฅผ request body๋กœ ๋ฐ›๋Š” ๊ฒƒ์„ ๋œปํ•˜๊ณ 

respond๋Š” ํŠน์ • ํ˜•์˜ ๊ฐ์ฒด๋ฅผ api response๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์„ ๋œปํ•œ๋‹ค.

  • call.receive <Customers>: Customers ๊ฐ์ฒด๋ฅผ request body๋กœ ๋ฐ›๊ธฐ
  • call.respond(Customers(... )): Customers ๊ฐ์ฒด๋ฅผ api response๋กœ ์ง€์ •

 

์ „์ฒด ์ฝ”๋“œ

CustomerRoutes.kt

package com.example.routes

import com.example.models.Customers
import com.example.models.ResponseData
import com.example.models.customerStorage
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.application.*
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import kotlinx.serialization.json.Json

fun Route.customerRouting() {
    route("/customer") {
        install(ContentNegotiation) {
            json(Json {
                prettyPrint = true
                isLenient = true
            })
        }
        get {
            if (customerStorage.isNotEmpty()) call.respond(customerStorage)
            else call.respondText("No Customer Found.", status = HttpStatusCode.OK)
        }
        get("{id}") {
            val id = call.parameters["id"] ?: return@get call.respondText(
                text = "Missing Id",
                status = HttpStatusCode.BadRequest
            )
            val customer = customerStorage.find { it.id == id } ?: return@get call.respondText(
                text = "No Customer with id $id",
                status = HttpStatusCode.NotFound
            )
            call.respond(customer)
        }
        post {
            val customer = call.receive<Customers>()
            customerStorage.add(customer)

            call.respond(
                status = HttpStatusCode.Created,
                message = ResponseData("Customer stored correctly")
            )
        }
        delete("{id}") {
            val id = call.parameters["id"] ?: return@delete call.respond(HttpStatusCode.BadRequest)
            if (customerStorage.removeIf { it.id == id })
                call.respondText(
                    text = "Customer remove correctly",
                    status = HttpStatusCode.Accepted
                )
            else call.respondText(text = "Not found.", status = HttpStatusCode.NotFound)
        }
    }
}

์ผ๋‹จ ๊ฐ„๋‹จํ•˜๊ฒŒ get, post, delete ๋กœ์ง์„ ๋งŒ๋“ค์–ด ์ฃผ์—ˆ๋‹ค.

 

ํŒŒ์ผ ๊ตฌ์กฐ(ktor)

 

Android์—์„œ Retrofit์œผ๋กœ ์„œ๋ฒ„ ํ…Œ์ŠคํŠธํ•˜๋Š” ์ฝ”๋“œ๋Š” ์ƒ๋žตํ•˜๊ฒ ๋‹ค.


์ •๋ฆฌ

ktor๋กœ http api๋ฅผ ๋งŒ๋“ค์–ด๋ดค๋Š”๋ฐ ๋งŒ๋“ค๊ณ  ๋‚˜์„œ android์™€ ์—ฐ๋™ํ•ด ํ…Œ์ŠคํŠธํ•  ๋•Œ response ํƒ€์ž…์„ ๋งž์ถ”์ง€ ์•Š์•„์„œ 415 ์—๋Ÿฌ๊ฐ€ ๊ณ„์† ๋ฐœ์ƒํ–ˆ์—ˆ๋Š”๋ฐ ๊ฐ„๋‹จํ•œ ๋ฌธ์ œ์˜€๋Š”๋ฐ ํ•˜๋ฃจ๋ฅผ ๋‚ ๋ ธ์—ˆ๋‹ค...ใ… 

๋‹ค์Œ ํฌ์ŠคํŒ…์—์„œ๋Š” ktor์—์„œ database๋ฅผ ์ด์šฉํ•ด ๋ณด๋„๋ก ํ•˜๊ฒ ๋‹ค.

 

728x90

๋Œ“๊ธ€