kotlin + coroutines + Okhttp3 を使って同期/非同期対応のHTTPクライアントを作ってみた

# 動機

- ググっても簡単に使えるHTTPクライアントが見つからなかったので、Okhttp3とcoroutinesを使って作ってみました

# HTTPクライアント

```kotlin
import kotlinx.coroutines.*
import okhttp3.FormBody
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody
import org.json.JSONObject
import java.io.IOException

class HttpClient {
    companion object {
        val instance = OkHttpClient()

        fun get(url: String): JSONObject {
            val request = Request.Builder()
                    .url(url)
                    .build()
            val response = instance.newCall(request).execute()
            return JSONObject(response.body?.string().orEmpty())
        }

        fun getAsync(url: String, onFailed: (Exception) -> Unit = {}, onSuccess:  (JSONObject) -> Unit = {}) {
            GlobalScope.launch(Dispatchers.Main) {
                try {
                    coroutineScope {
                        async(Dispatchers.Default) { get(url) }.await().let {
                            onSuccess(it)
                        }
                    }
                } catch (e: Exception) {
                    onFailed(e)
                }
            }
        }

        fun post(url: String, params: Map<String, String>? = null): JSONObject {
            val formBuilder = FormBody.Builder()
            params?.forEach { (name: String, value: String) -> formBuilder.add(name, value) }
            val requestBody: RequestBody = formBuilder.build()
            val request = Request.Builder()
                    .url(url)
                    .post(requestBody)
                    .build()
            val response = instance.newCall(request).execute()
            if (!response.isSuccessful) throw IOException("Unexpected code $response");
            return JSONObject(response.body?.string().orEmpty())
        }

        fun postAsync(url: String, params: Map<String, String>? = null, onFailed: (Exception) -> Unit = {}, onSuccess:  (JSONObject) -> Unit = {}) {
            GlobalScope.launch(Dispatchers.Main) {
                try {
                    coroutineScope {
                        async(Dispatchers.Default) { post(url, params) }.await().let {
                            onSuccess(it)
                        }
                    }
                } catch (e: Exception) {
                    onFailed(e)
                }
            }
        }
    }
}
```

# 使い方

## 同期的にGET

```kotlin
val response = HttpClient.get("https://hoge/api/fuga")
Log.i(TAG, "同期的にGET：${response}")
```

## 非同期でGET

```kotlin
HttpClient.getAsync("https://hoge/api/fuga"){
	Log.i(TAG, "非同期でGET：${it}")
}
```

## 同期的にPOST

```kotlin
val params = mapOf(
	"name" to "ryota",
	"job" to "neet"
)
val response = HttpClient.post("https://hoge/api/fuga", params)
Log.i(TAG, "同期的にPOST：${response}")
```

## 非同期でPOST

```kotlin
val params = mapOf(
	"name" to "ryota",
	"job" to "neet"
)
HttpClient.postAsync("https://hoge/api/fuga", params) {
	Log.i(TAG, "非同期でPOST：${it}")
}
```

## 非同期でPOST + エラーハンドリング

```kotlin
val handleError: (Exception) -> Unit = {
	e -> Log.e(TAG, "エラー：${e}")
}
HttpClient.postAsync("https://hoge/api/fuga", params, onFailed = handleError) {
	Log.i(TAG, "非同期でPOST + エラーハンドリング：${it}")
}
```

使いやすいようにカスタマイズして使って下さい。