feat(Http): support response caching
This commit is contained in:
parent
38e300ef38
commit
c840aac67e
7 changed files with 80 additions and 12 deletions
|
|
@ -1,18 +1,30 @@
|
|||
package com.aliucord.manager.di
|
||||
|
||||
import android.app.Application
|
||||
import com.aliucord.manager.BuildConfig
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.call.HttpClientCall
|
||||
import io.ktor.client.engine.okhttp.OkHttp
|
||||
import io.ktor.client.plugins.cache.HttpCache
|
||||
import io.ktor.client.plugins.cache.storage.FileStorage
|
||||
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
|
||||
import io.ktor.client.plugins.defaultRequest
|
||||
import io.ktor.client.request.HttpRequestBuilder
|
||||
import io.ktor.client.request.header
|
||||
import io.ktor.http.HttpHeaders
|
||||
import io.ktor.client.statement.HttpReceivePipeline
|
||||
import io.ktor.client.statement.HttpResponse
|
||||
import io.ktor.http.*
|
||||
import io.ktor.serialization.kotlinx.json.json
|
||||
import io.ktor.util.AttributeKey
|
||||
import io.ktor.util.date.GMTDate
|
||||
import io.ktor.utils.io.ByteReadChannel
|
||||
import io.ktor.utils.io.InternalAPI
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Dns
|
||||
import org.koin.core.scope.Scope
|
||||
import java.net.Inet4Address
|
||||
import java.net.InetAddress
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
@Suppress("UnusedReceiverParameter")
|
||||
fun Scope.provideJson() = Json {
|
||||
|
|
@ -21,10 +33,12 @@ fun Scope.provideJson() = Json {
|
|||
|
||||
fun Scope.provideHttpClient() = HttpClient(OkHttp) {
|
||||
val json: Json = get()
|
||||
val application: Application = get()
|
||||
|
||||
defaultRequest {
|
||||
header(HttpHeaders.UserAgent, "Aliucord Manager/${BuildConfig.VERSION_NAME}")
|
||||
}
|
||||
|
||||
engine {
|
||||
config {
|
||||
dns(object : Dns {
|
||||
|
|
@ -42,7 +56,43 @@ fun Scope.provideHttpClient() = HttpClient(OkHttp) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
install(ContentNegotiation) {
|
||||
json(json)
|
||||
}
|
||||
|
||||
install(HttpCache) {
|
||||
val dir = application.cacheDir.resolve("ktor")
|
||||
publicStorage(FileStorage(dir))
|
||||
}
|
||||
|
||||
// Custom plugin to allow overriding response cache headers, and force caching
|
||||
install("OverrideCacheControl") {
|
||||
receivePipeline.intercept(HttpReceivePipeline.Before) { response ->
|
||||
val customCacheControl = response.call.attributes.getOrNull(CustomCacheControl)
|
||||
?: return@intercept
|
||||
|
||||
proceedWith(object : HttpResponse() {
|
||||
@InternalAPI
|
||||
override val rawContent: ByteReadChannel = response.rawContent
|
||||
override val call: HttpClientCall = response.call
|
||||
override val coroutineContext: CoroutineContext = response.coroutineContext
|
||||
override val requestTime: GMTDate = response.requestTime
|
||||
override val responseTime: GMTDate = response.responseTime
|
||||
override val status: HttpStatusCode = response.status
|
||||
override val version: HttpProtocolVersion = response.version
|
||||
|
||||
override val headers: Headers = headers {
|
||||
appendAll(response.headers)
|
||||
set(HttpHeaders.CacheControl, customCacheControl.toString())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val CustomCacheControl: AttributeKey<CacheControl> = AttributeKey("CustomCacheControl")
|
||||
|
||||
fun HttpRequestBuilder.cacheControl(cacheControl: CacheControl) {
|
||||
attributes.put(CustomCacheControl, cacheControl)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ class KtorDownloadManager(private val http: HttpClient) : IDownloadManager {
|
|||
|
||||
try {
|
||||
val httpStmt = http.prepareGet(url) {
|
||||
header(HttpHeaders.CacheControl, "no-cache, no-store")
|
||||
|
||||
// Disable compression due to bug on emulators
|
||||
// This header cannot be set with Android's DownloadManager
|
||||
if (IS_PROBABLY_EMULATOR) {
|
||||
|
|
|
|||
|
|
@ -2,17 +2,23 @@ package com.aliucord.manager.network.services
|
|||
|
||||
import com.aliucord.manager.network.models.BuildInfo
|
||||
import com.aliucord.manager.network.utils.ApiResponse
|
||||
import io.ktor.client.request.header
|
||||
import io.ktor.client.request.url
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import io.ktor.http.HttpHeaders
|
||||
|
||||
class AliucordGithubService(
|
||||
val github: GithubService,
|
||||
val http: HttpService,
|
||||
) {
|
||||
suspend fun getDataJson(): ApiResponse<BuildInfo> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
http.request { url(DATA_JSON_URL) }
|
||||
/**
|
||||
* Fetches the build data of Aliucord (excluding Aliuhook).
|
||||
* @param force Whether to force Ktor to refetch/revalidate cache.
|
||||
*/
|
||||
suspend fun getBuildData(force: Boolean = false): ApiResponse<BuildInfo> = http.request {
|
||||
url(DATA_JSON_URL)
|
||||
|
||||
if (force) {
|
||||
header(HttpHeaders.CacheControl, "no-cache")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,22 @@
|
|||
package com.aliucord.manager.network.services
|
||||
|
||||
import com.aliucord.manager.di.cacheControl
|
||||
import com.aliucord.manager.network.utils.*
|
||||
import io.ktor.client.request.header
|
||||
import io.ktor.client.request.url
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.http.*
|
||||
|
||||
class AliucordMavenService(private val http: HttpService) {
|
||||
suspend fun getAliuhookVersion(): ApiResponse<SemVer> {
|
||||
val metadataResponse = http.request<String> { url(ALIUHOOK_METADATA_URL) }
|
||||
suspend fun getAliuhookVersion(force: Boolean = false): ApiResponse<SemVer> {
|
||||
val metadataResponse = http.request<String> {
|
||||
url(ALIUHOOK_METADATA_URL)
|
||||
|
||||
if (!force) {
|
||||
cacheControl(CacheControl.MaxAge(maxAgeSeconds = 60 * 30)) // 30 min
|
||||
} else {
|
||||
header(HttpHeaders.CacheControl, "no-cache")
|
||||
}
|
||||
}
|
||||
|
||||
return metadataResponse.transform {
|
||||
val versionString = "<release>(.+?)</release>".toRegex()
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ class DownloadAliuhookStep : DownloadStep(), IDexProvider, KoinComponent {
|
|||
|
||||
override suspend fun execute(container: StepRunner) {
|
||||
container.log("Obtaining latest aliuhook version")
|
||||
targetVersion = maven.getAliuhookVersion().getOrThrow()
|
||||
targetVersion = maven.getAliuhookVersion(force = true).getOrThrow()
|
||||
container.log("Fetched aliuhook version: $targetVersion")
|
||||
|
||||
super.execute(container)
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ class FetchInfoStep : Step(), KoinComponent {
|
|||
|
||||
override suspend fun execute(container: StepRunner) {
|
||||
container.log("Fetching ${AliucordGithubService.DATA_JSON_URL}")
|
||||
data = github.getDataJson().getOrThrow()
|
||||
data = github.getBuildData(force = true).getOrThrow()
|
||||
container.log("Fetched build data: $data")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ class HomeModel(
|
|||
}
|
||||
|
||||
suspend fun fetchRemoteData() {
|
||||
github.getDataJson().fold(
|
||||
github.getBuildData().fold(
|
||||
success = {
|
||||
val versionCode = it.discordVersionCode.toIntOrNull()
|
||||
if (versionCode == null) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue