OkHttp
OkHttp는 Square사에서 만든 오픈 소스 HTTP 클라이언트 라이브러리로, 안드로이드 및 Java 어플리케이션에서 사용하며 REST API, HTTP 통신을 간편하게 구현할 수 있다.
OkHttp는 소켓 연결, 요청 및 응답 처리, 캐싱, 인터셉터, 스트리밍 등의 기능을 포함한 고급 네트워크 기능을 제공한다.
OkHttp는 소켓 연결을 풀링하고, 헤더를 압축하며, 비동기 및 동기적인 요청을 효과적으로 처리하는 등의 성능 향상을 위해 여러 최적화를 수행한다.
OkHttp는 HTTP/1.x와 HTTP/2 프로토콜을 모두 지원하며, 안전한 연결을 제공하기 위해 TLS/SSL을 지원한다. 그리고 여러 인터셉터를 사용하여 요청 및 응답을 가로채고 수정할 수 있도록 풍부한 확장성 역시 제공한다.
간결하고 사용하기 쉬운 API를 제공하여 개발자가 쉽게 네트워크 통신을 구현할 수 있도록 도와준다.
간결한 API 사용
OkHttp는 간결한 API를 제공하여 간단한 한 줄의 코드로도 HTTP요청을 수행하고 응답을 처리할 수 있다.
// OkHttp 라이브러리 의존성 추가
implementation("com.squareup.okhttp3:okhttp:4.9.0")
// ...
// OkHttp 클라이언트 생성
val client = OkHttpClient()
// HTTP 요청 객체 생성
val request = Request.Builder()
.url("https://api.example.com/data") // 요청할 URL 설정 (기본 메서드 GET)
.build()
// HTTP 요청 실행
client.newCall(request).enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
// 서버 응답 처리
val responseData = response.body?.string()
println("서버 응답: $responseData")
}
override fun onFailure(call: Call, e: IOException) {
// 요청 실패 처리
println("요청 실패: ${e.message}")
}
})
// HTTP POST 일 경우
val request = Request.Builder()
.url("https://api.example.com/data")
.post(requestBody) // requestBody는 POST 요청 시에 함께 보낼 데이터를 담은 객체
.build()
OkHttp 클라이언트를 생성하고, Request.Builder를 사용하여 간단한 HTTP GET 요청을 만든다.
그리고 client.newCall(request)를 통해 비동기적으로 요청을 실행하고, 응답 또는 오류에 대한 처리를 Callback 인터페이스를 통해 정의한다.
이러한 직관적인 코드의 API 사용으로 네트워크 기능을 쉽게 사용하고 유지보수할 수 있게 해준다.
HttpURLConnection, AsyncTask (Deprecated) 를 사용한 비동기 네트워크 호출 예시
import android.os.AsyncTask
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader
import java.net.HttpURLConnection
import java.net.URL
class HttpTask : AsyncTask<String, Void, String>() {
override fun doInBackground(vararg params: String?): String {
val urlString = params[0]
var result = ""
try {
val url = URL(urlString)
val urlConnection: HttpURLConnection = url.openConnection() as HttpURLConnection
try {
// HTTP GET 요청 설정
urlConnection.requestMethod = "GET"
// 응답 코드 확인
if (urlConnection.responseCode == HttpURLConnection.HTTP_OK) {
// 응답 데이터 읽기
val reader = BufferedReader(InputStreamReader(urlConnection.inputStream))
var line: String?
while (reader.readLine().also { line = it } != null) {
result += line
}
} else {
result = "HTTP Request Failed with Status code: ${urlConnection.responseCode}"
}
} finally {
// 연결 종료
urlConnection.disconnect()
}
} catch (e: IOException) {
result = "Error during HTTP request: ${e.message}"
}
return result
}
override fun onPostExecute(result: String?) {
// HTTP 요청 결과 처리
println("HTTP 요청 결과: $result")
}
}
// 예시 사용법
val url = "https://api.example.com/data"
HttpTask().execute(url)
이러한 방식은 Android에서 비동기 네트워크 통신을 하는 오래된 방법으로, OkHttp와 비교해도 직관적이지 않다.
또한, AsyncTask는 재사용 불가, 메모리 누수, UI 스레드에서 호출 등 여러 문제점들로 인해 Deprecated 되었다.
OkHttp는 HttpURLConnection 과 별개로 자체적으로 개발된 HTTP 클라이언트 라이브러리이다.
동기, 비동기 지원
OkHttp는 동기 및 비동기 작업을 모두 지원하며, 콜백 기반의 비동기 처리를 통해 네트워크 요청을 처리할 수 있다.
동기적 방식 (Synchronous)
// OkHttp 라이브러리 의존성 추가
implementation("com.squareup.okhttp3:okhttp:4.9.0")
// OkHttp 클라이언트 생성
val client = OkHttpClient()
// HTTP 요청 객체 생성
val request = Request.Builder()
.url("https://api.example.com/data")
.build()
// 동기적으로 요청 실행
try {
// excute 메서드를 사용하여 동기적으로 HTTP 요청 수행. 요청이 완료될 때까지 코드가 블록되며
// 응답이나 예외가 발생할 때까지 다음 코드로 진행되지 않음
val response: Response = client.newCall(request).execute()
// 서버 응답 처리
val responseData = response.body?.string()
println("서버 응답: $responseData")
} catch (e: IOException) {
// 요청 실패 처리
println("요청 실패: ${e.message}")
}
비동기적 방식 (Asynchronous)
// OkHttp 라이브러리 의존성 추가
implementation("com.squareup.okhttp3:okhttp:4.9.0")
// OkHttp 클라이언트 생성
val client = OkHttpClient()
// HTTP 요청 객체 생성
val request = Request.Builder()
.url("https://api.example.com/data")
.build()
// 'enqueue' 메서드를 사용해 비동기적으로 HTTP 요청 수행
// 요청이 백그라운드에서 실행되며, 응답 또는 예외가 발생하면 콜백 메서드가 호출
// 이 때 코드는 블록되지 않고 계속해서 실행
client.newCall(request).enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
// 서버 응답 처리
val responseData = response.body?.string()
println("서버 응답: $responseData")
}
override fun onFailure(call: Call, e: IOException) {
// 요청 실패 처리
println("요청 실패: ${e.message}")
}
})
동기적 방식은 간단하고 직관적이지만, 긴 네트워크 작업을 수행할 때는 UI가 응답하지 않을 수 있다.
반면, 비동기적 방식은 UI의 블로킹 없이 작업을 수행할 수 있지만, 콜백을 다루는 코드가 추가로 필요하다.
인터셉터 지원
인터셉터(Interceptor)는 OkHttp에서 HTTP 요청 및 응답을 가로채고 조작할 수 있는 메커니즘 중 하나이다.
인터셉터를 사용하면 요청과 응답을 수정하거나, 로깅, 인증, 캐싱 등의 추가 작업을 수행할 수 있다.
로깅 인터셉터
val loggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY // 로깅 레벨 설정
}
val client = OkHttpClient.Builder()
.addInterceptor(loggingInterceptor) // 로깅 인터셉터 추가
.build()
HttpLoggingInterceptor를 사용하여 로깅 인터셉터를 생성하고, 이를 OkHttpClient에 추가한다.
이 로깅 인터셉터는 요청과 응답의 상세 내용을 로그로 출력하도록 설정되어 있다.
헤더 인터셉터
val headerInterceptor = Interceptor { chain ->
// 현재의 원본 요청을 얻어옴
val originalRequest = chain.request()
// 헤더를 추가하거나 수정
val modifiedRequest = originalRequest.newBuilder()
.addHeader("Authorization", "Bearer YourAccessToken")
.build()
// 수정된 요청을 사용하여 체인을 계속 진행
val response = chain.proceed(modifiedRequest)
// 응답 반환
response
}
val client = OkHttpClient.Builder()
.addInterceptor(headerInterceptor) // 헤더 인터셉터 추가
.build()
Interceptor 인터페이스를 구현하여 헤더를 조작하는 작업을 수행한다.
이러한 방식으로 인증 토큰이나 앱이 필요로 하는 다른 헤더를 추가하거나 수정할 수 있다.
캐싱 지원
캐싱은 네트워크 요청의 반복을 줄이고 응답을 더 빠르게 반환하기 위한 중요한 메커니즘 중 하나이다.
OkHttp는 캐싱을 지원하여 네트워크 트래픽을 최적화하고 앱의 성능을 향상시킬 수 있다.
val cacheSize = (5 * 1024 * 1024).toLong() // 5 MB
val cache = Cache(context.cacheDir, cacheSize)
val client = OkHttpClient.Builder()
.cache(cache) // 캐시 설정
.build()
Cache 클래스를 사용하여 캐시 객체를 생성하고 이를 OkHttpClient에 추가한다.
캐시 객체는 앱의 캐시 디렉토리에 저장되며, OkHttpClient는 캐시를 이용해 동일한 요청에 대한 응답을 캐시에서 가져오고, 네트워크를 통한 실제 요청을 줄일 수 있다.
만약 응답 헤더에 ‘Cache-Control’ 헤더를 포함하고 있다면, OkHttp는 해당 헤더를 따라 캐시 동작을 조절한다. 또한, 캐시된 응답을 사용하는 동안 서버와의 조건부 요청(If-None-Match, If-Modified-Since 등)을 처리할 수 있다.
캐싱을 적절히 활용하면 네트워크 트래픽을 줄이고 응답 속도를 향상시킬 수 있다.
다양한 인증 방식 지원
기본 인증 (Basic Authentication)
기본 인증은 사용자 이름과 비밀번호를 Base64로 인코딩하여 서버에 제공하는 간단한 형태의 인증이다.
val credentials = Credentials.basic("username", "password")
val client = OkHttpClient.Builder()
.addInterceptor { chain ->
val request = chain.request().newBuilder()
.header("Authorization", credentials)
.build()
chain.proceed(request)
}
.build()
Credentials.basic 메서드를 사용하여 사용자 이름과 비밀번호를 인코딩한 문자열을 생성하고, 이를 인터셉터를 통해 요청 헤더에 추가한다.
Bearer 토큰 인증
Bearer 토큰은 서버로부터 발급받은 액세스 토큰으로, 보통 OAuth 2.0 인증에서 사용된다.
val token = "access_token"
val client = OkHttpClient.Builder()
.addInterceptor { chain ->
val request = chain.request().newBuilder()
.header("Authorization", "Bearer $token")
.build()
chain.proceed(request)
}
.build()
Bearer라는 단어는 토큰의 타입을 나타내는 것으로, 해당 토큰이 OAuth 2.0 표준에 따라 발급된 액세스 토큰임을 명시적으로 나타낸다. 토큰 앞에 Bearer를 보고 서버는 클라인트가 제공한 토큰이 OAuth 2.0 규격에 따라 발급된 액세스 토큰임을 알 수 있다.
OAuth 2.0은 웹 및 어플리케이션에서 클라이언트가 리소스 서버에 접근하기 위한 권한 부여를 위한 프로토콜이며, Bearer 토큰은 이 권한 부여를 받은 클라이언트가 보유하게 되는 특별한 형태의 액세스 토큰이다.
Authorization 헤더
Authorization 헤더는 HTTP 요청에 인증 정보를 포함시키기 위한 표준적인 방법 중 하나이다. 이 헤더는 클라이언트가 서버에게 리소스에 접근하기 위한 권한을 가지고 있는 확인하기 위해 사용된다. 다양한 인증 방식에서 이 Authorization 헤더를 사용하며, 헤더 값에는 클라이언트의 인증 정보가 들어간다.
대
부분의 경우 Authorization 헤더의 값은 특정 규약을 따른다.
- Basic Authentication: "Basic [base64-encoded-username-and-password]"
- Bearer Token Authentication: "Bearer [access-token]"
이러한 표준화로 서버와 클라이언트 간에 일관된 규약이 유지되며, 보안적인 측면에서도 일정 수준의 안정성을 확보할 수 있다. 서버는 Authorization 헤더를 통해 클라이언트가 올바른 인증 정보를 제공하는지 확인하고, 그에 따라 리소스에 대한 접근 권한을 부여하거나 거부할 수 있다.
Authorization 헤더는 HTTP/1.1의 RFC 7235 표준 규약에 따라 일반적으로 인증 정보를 담는 데 사용되는 표준 헤더의 키 이름이다. 특별한 경우 사용자 정의 헤더를 사용할 수도 있다. 예를 들어, X-Auth-Token등 사용자가 정의한 이름으로 키를 설정하여 인증 정보를 전달할 수 있다. 이는 서버와 클라이언트 간의 약속에 따라 결정되며, 이러한 사용자 정의 헤더를 사용하는 경우 서버 및 클라이언트 모두에서 이를 지원하도록 설정해야 한다. 또한 사용자 정의 헤더를 사용하는 경우 서버 및 클라이언트 간의 일관성을 유지하기 위해 문서화 및 협의가 필요하다.
이외에도 OkHttp는 HTTP/2 및 HTTP/3, WebSocket, 마이크로소프트의 NTLM과 같은 인증 프로토콜, 서버와의 연결을 유지하고 재사용하는 풀링 등의 다양한 기능을 지원한다.