Call.Factory를 상속받은 OKHTTPClient를 통해서 송, 수신을 하고 있다.
interface RetrofitAPI {
@POST("/")
fun getStudent(
@Query("school_id") schoolID: Int,
@Query("grade") grade: Int,
@Query("classroom") classroom: Int
): Call<Student>
}
Retrofit.Builder()에서 생성된 Retrofit객체에서 Proxy를 이용해서 동적으로 interface를 구현한다.
Retrofit객체는 내부적으로 우리가 작성한 service에 대한 메서드들을 들고 있다. 그래서 우리가 getStudent()라는 Call을 실행하면 ServiceMethod를 상속받은 구현체 중 하나가 만들어지고 실행하는 것 같은데 더 자세한 내용은 이해하지 못했다. Adapter패턴이 사용되었다는 것 말고는 그 이상으로는 이해하기가 힘들다.
def retrofitVersion = "2.9.0"
implementation "com.squareup.retrofit2:retrofit:${retrofitVersion}"
implementation "com.squareup.retrofit2:converter-gson:${retrofitVersion}"
implementation "com.squareup.retrofit2:converter-scalars:${retrofitVersion}"
implementation 'com.google.code.gson:gson:2.8.6'
테스트를 하는 여러 방법이 있지만 로컬에서 간단하게 httpServer를 만들고 테스트를 했다.
우선 사용할 Data class를 만든다
data class Student(
@SerializedName("student_id")
val studentID : String,
@SerializedName("name")
val name : String,
@SerializedName("phoneNumber")
val phoneNumber : String
) {
override fun toString(): String {
return "Student(studentID='$studentID', name='$name', phoneNumber='$phoneNumber')"
}
}
다음에는 service를 만든다.
이때 @Query를 사용하면 uri에 포함되서 날라오게된다 서버측에서는
RequestURI /?grade=4&classroom=2
이렇게 받을 수 있다.
@Body를 사용하면 uri에 보이지않고 서버측에서는 requestBody 1
이렇게 받는다.
interface RetrofitAPI {
@POST("/")
fun getStudent(
@Query("grade") grade: Int,
@Query("classroom") classroom: Int
): Call<List<Student>>
@POST("/body")
fun getStudent2(
@Body studentID : String
) : Call<Student>
}
Retrofit객체 생성 및 service interface 만들기
val gson = GsonBuilder().setLenient().create()
val retrofit = Retrofit.Builder()
.baseUrl("http://10.0.2.2:10101/")
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
val requestApi = retrofit.create(RetrofitAPI::class.java)
이제 생성한 interface에서 메소드를 실행시키면 끝이다.
enqueue()를 사용해서 비동기로 실행시킬 수 있고 execute()를 사용해서 동기적으로도 실행시킬 수 있다.
val callGetStudent: Call<List<Student>> = requestApi.getStudent(grade, classroom)
callGetStudent.enqueue(object : Callback<List<Student>> {
override fun onResponse(
call: Call<List<Student>>,
response: Response<List<Student>>
) {
if (response.isSuccessful) {
Log.e("Success", "Headers ${response.headers().toMultimap()}")
Log.e("Success", response.body()!!.toString())
} else { //400 에러 처리
Log.e("ResponseFail", response.message())
}
}
override fun onFailure(call: Call<List<Student>>, t: Throwable) {
// 500 에러 처리
Log.e("onFailure", t.localizedMessage)
}
})
간단한 서버 코드
Teriser 프로젝트를 하면서 삽질했던 경험이 굉장히 많이 도움이 된다.
class Server {
val server = HttpServer.create(InetSocketAddress("127.0.0.1", 10101), 0)
val gson = GsonBuilder().create()
init {
server.createContext("/").setHandler { httpExchange ->
println("RequestURI ${httpExchange.requestURI}")
println("RequestMethod ${httpExchange.requestMethod}")
if (httpExchange.requestMethod == "POST") {
if (httpExchange.requestURI.path.contains("body")) {
println("uri path ${httpExchange.requestURI.path}")
val stream = httpExchange.requestBody
val streamArr = stream.readBytes()
println("request body ${String(streamArr)}")
var data = Student("idIS100", "nameISKotlin", "PhoneNumberIsNone")
val stringData = gson.toJson(data)
sendData(stringData, httpExchange)
} else {
var data = Student("idIS100", "nameISKotlin", "PhoneNumberIsNone")
val list = listOf<Student>(data)
val stringData = gson.toJson(list)
sendData(stringData, httpExchange)
}
}
}
}
private fun sendData(stringData: String, httpExchange: HttpExchange) {
val byteData = StandardCharsets.UTF_8.encode(stringData)
val arr = ByteArray(byteData.limit())
byteData.get(arr, 0, byteData.limit())
httpExchange.responseHeaders.apply {
add("Content-Type", "application/json")
add("Content-Length", arr.size.toString())
}
httpExchange.sendResponseHeaders(200, arr.size.toLong())
httpExchange.responseBody.run {
write(arr)
flush()
close()
}
}
fun start() = server.start()
}