# HTTP4K on Android ## 測試的Android版本 ```groovy compileSdkVersion 29 buildToolsVersion "29.0.2" minSdkVersion 21 targetSdkVersion 29 ``` ## 測試的[Code](https://gist.github.com/tonynowater87/99fe401e4199f3e42efc079184afa109), [Android Project](https://github.com/tonynowater87/Android-Library-Test/tree/http4k) ## 測試項目 * 當進入 HttpHandler 時是在 那一支 thread? > 是在DefaultDispatcher-worker-[1]。 > Http4K貌似是用Coroutine的Dispatchers.Default,是一種Thead Pool, Thread的數量會依照CPU的核心數決定。 * 是不是每一個 request 都是在新的 thread? > 不一定,因為是用Thread Pool。 * 有沒有方法把 response 用 RxJava 的方法返回? > Library貌似沒有直接支援,[需要自己引入RX搭配使用](#建立Client,送出Request,處理Response)。 * 是不是在 HttpHandler 裡面都要用同步的方法做 IO 的事情? > 是的,如果用非同步的話,Response會直接返回,不會等待。 ## Library的使用方式 ### 設定Manifest ```xml <uses-permission android:name="android.permission.INTERNET" /> ``` ### 設定Dependencies ```groovy compile (group: "org.http4k", name: "http4k-core", version: "3.205.0") compile (group: "org.http4k", name: "http4k-server-ktorcio", version: "3.205.0") compile (group: "org.http4k", name: "http4k-client-okhttp", version: "3.205.0") // RX Libraries implementation "io.reactivex.rxjava2:rxkotlin:2.4.0" implementation "io.reactivex.rxjava2:rxandroid:2.1.1" ``` > 官方範例是使用http4k-server-jetty,但是Run起來會Runtime Crash > 所以這裡改用另一個http4k-server-ktorcio #### 其它的server和client的module連結 [servers](https://www.http4k.org/guide/modules/servers/) [clients](https://www.http4k.org/guide/modules/clients/) ### 建立API Handler並啟動Server ```kotlin val apiHandler: HttpHandler = routes( "/hello" bind GET to { Response(OK).body("hello world!") } ) apiHandler .asServer(KtorCIO(port = 5566)) .start() ``` ### 建立Client,送出Request,處理Response ```kotlin private val client: HttpHandler by lazy { ClientFilters .BasicAuth(user = "userName", password = "userPwd") .then(DebuggingFilters.PrintRequestAndResponse()) .then(OkHttp()) } Single .fromCallable { val requestGet = Request(GET, "http://localhost:5566/hello/") client(requestGet) // 送出Request } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( { response -> // 處理 Response tvMain.text = response.bodyString() }, { tvMain.text = it.message } ) ``` ### 使用訂閱PublishSubject的方式接收Response ```kotlin private var comingResponse = PublishSubject.create<Response>() val apiFilter = Filter { next: HttpHandler -> { request: Request -> val response = next(request) response.also { this.comingResponse.onNext(it) } } } val server = CachingFilters.Response.NoCache() .then(apiFilter) .then(apiHandler) .asServer(KtorCIO(port = 5566)) .start() ``` ```kotlin comingResponse .observeOn(AndroidSchedulers.mainThread()) .subscribe( { response -> // 處理 Response tvMain.text = response.toString() }, { tvMain.text = it.toString() } ) ``` ### 遇到的錯誤 * Execution failed for task ':app:mergeDebugJavaResource'. A failure occurred while executing More than one file was found with OS independent path 'META-INF/kotlinx-io.kotlin_module', 'META-INF/atomicfu.kotlin_module' , 'META-INF/kotlinx-coroutines-io.kotlin_module' > 解法: ```groovy //app裡的build.gradle android { packagingOptions { pickFirst 'META-INF/kotlinx-io.kotlin_module' pickFirst 'META-INF/atomicfu.kotlin_module' pickFirst 'META-INF/kotlinx-coroutines-io.kotlin_module' } } ``` * Rejecting re-init on previously-failed class java.lang.Class<okhttp3.internal.platform.ConscryptPlatform$configureTrustManager$1>: java.lang.NoClassDefFoundError: Failed resolution of: Lorg/conscrypt/ConscryptHostnameVerifier; > 解法 ```groovy implementation 'org.conscrypt:conscrypt-android:2.2.1' ``` * Transform artifact javax.servlet-api.jar (javax.servlet:javax.servlet-api:4.0.1) with DexingNoClasspathTransform AGPBI: {"kind":"error","text":"Default interface methods are only supported starting with Android N (--min-api 24): void javax.servlet.Filter.destroy()","sources":[{}],"tool":"D8"} > 解法 ```groovy //app裡的build.gradle android { compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } ```