Skip to content

Commit

Permalink
Merge pull request #186 from hotwired/offline-caching-redirects
Browse files Browse the repository at this point in the history
When using offline caching, don't mask HTTP redirects
  • Loading branch information
jayohms authored Sep 8, 2021
2 parents f2105b1 + c17ccef commit 42b4883
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ internal class TurboHttpRepository(private val coroutineScope: CoroutineScope) {

data class Result(
val response: WebResourceResponse?,
val offline: Boolean
val offline: Boolean,
val redirectToLocation: String? = null
)

internal fun preCache(
Expand Down Expand Up @@ -74,15 +75,26 @@ internal class TurboHttpRepository(private val coroutineScope: CoroutineScope) {
return try {
val response = issueRequest(resourceRequest)

// Cache based on the response's request url, which may have been a redirect
val responseUrl = response?.request?.url.toString()
val isRedirect = url != responseUrl

// Let the app cache the response
val resourceResponse = resourceResponse(response)
val cachedResponse = resourceResponse?.let {
requestHandler.cacheResponse(url, it)
requestHandler.cacheResponse(responseUrl, it)
}

Result(cachedResponse ?: resourceResponse, false)
Result(
response = cachedResponse ?: resourceResponse,
offline = false,
redirectToLocation = if (isRedirect) responseUrl else null
)
} catch (e: IOException) {
Result(requestHandler.getCachedResponse(url, allowStaleResponse = true), true)
Result(
response = requestHandler.getCachedResponse(url, allowStaleResponse = true),
offline = true
)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package dev.hotwire.turbo.http

import android.webkit.WebResourceRequest
import android.webkit.WebResourceResponse
import dev.hotwire.turbo.session.TurboSession
import dev.hotwire.turbo.util.logEvent

internal class TurboWebViewRequestInterceptor(val session: TurboSession) {
private val offlineRequestHandler get() = session.offlineRequestHandler
private val httpRepository get() = session.httpRepository
private val currentVisit get() = session.currentVisit

fun interceptRequest(request: WebResourceRequest): WebResourceResponse? {
val requestHandler = offlineRequestHandler ?: return null

if (!shouldInterceptRequest(request)) {
return null
}

val url = request.url.toString()
val isCurrentVisitRequest = url == currentVisit?.location
val result = httpRepository.fetch(requestHandler, request)

return if (isCurrentVisitRequest) {
logCurrentVisitResult(url, result)
currentVisit?.completedOffline = result.offline

// If the request resulted in a redirect, don't return the response. This
// lets the WebView handle the request/response and Turbo can see the redirect,
// so a redirect "replace" visit can be proposed.
when (result.redirectToLocation) {
null -> result.response
else -> null
}
} else {
result.response
}
}

private fun shouldInterceptRequest(request: WebResourceRequest): Boolean {
return request.method.equals("GET", ignoreCase = true) &&
request.url.scheme?.startsWith("HTTP", ignoreCase = true) == true
}

private fun logCurrentVisitResult(url: String, result: TurboHttpRepository.Result) {
logEvent(
"location" to url,
"redirectToLocation" to result.redirectToLocation.toString(),
"statusCode" to (result.response?.statusCode ?: "<none>"),
"completedOffline" to result.offline
)
}

private fun logEvent(vararg params: Pair<String, Any>) {
val attributes = params.toMutableList().apply {
add(0, "session" to session.sessionName)
}
logEvent("interceptRequest", attributes)
}
}
25 changes: 3 additions & 22 deletions turbo/src/main/kotlin/dev/hotwire/turbo/session/TurboSession.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,7 @@ import androidx.webkit.WebViewFeature.*
import dev.hotwire.turbo.config.TurboPathConfiguration
import dev.hotwire.turbo.config.screenshotsEnabled
import dev.hotwire.turbo.delegates.TurboFileChooserDelegate
import dev.hotwire.turbo.http.TurboHttpClient
import dev.hotwire.turbo.http.TurboHttpRepository
import dev.hotwire.turbo.http.TurboOfflineRequestHandler
import dev.hotwire.turbo.http.TurboPreCacheRequest
import dev.hotwire.turbo.http.*
import dev.hotwire.turbo.nav.TurboNavDestination
import dev.hotwire.turbo.util.*
import dev.hotwire.turbo.views.TurboWebView
Expand Down Expand Up @@ -54,6 +51,7 @@ class TurboSession internal constructor(
internal var restorationIdentifiers = SparseArray<String>()
internal val context: Context = activity.applicationContext
internal val httpRepository = TurboHttpRepository(activity.lifecycleScope)
internal val requestInterceptor = TurboWebViewRequestInterceptor(this)
internal val fileChooserDelegate = TurboFileChooserDelegate(this)

// User accessible
Expand Down Expand Up @@ -648,24 +646,7 @@ class TurboSession internal constructor(
}

override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? {
val requestHandler = offlineRequestHandler ?: return null

if (!request.method.equals("GET", ignoreCase = true) ||
request.url.scheme?.startsWith("HTTP", ignoreCase = true) != true
) {
return null
}

val url = request.url.toString()
val result = httpRepository.fetch(requestHandler, request)

currentVisit?.let { visit ->
if (visit.location == url) {
visit.completedOffline = result.offline
}
}

return result.response
return requestInterceptor.interceptRequest(request)
}

override fun onReceivedError(view: WebView, request: WebResourceRequest, error: WebResourceErrorCompat) {
Expand Down

0 comments on commit 42b4883

Please sign in to comment.