This repository has been archived by the owner on Nov 1, 2022. It is now read-only.
/
GeckoViewFetchClient.kt
135 lines (116 loc) · 4.6 KB
/
GeckoViewFetchClient.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package mozilla.components.browser.engine.gecko.fetch
import android.content.Context
import androidx.annotation.VisibleForTesting
import mozilla.components.concept.fetch.Client
import mozilla.components.concept.fetch.Headers
import mozilla.components.concept.fetch.MutableHeaders
import mozilla.components.concept.fetch.Request
import mozilla.components.concept.fetch.Response
import mozilla.components.concept.fetch.isDataUri
import mozilla.components.concept.fetch.isBlobUri
import mozilla.components.concept.fetch.Response.Companion.SUCCESS
import org.mozilla.geckoview.GeckoRuntime
import org.mozilla.geckoview.GeckoWebExecutor
import org.mozilla.geckoview.WebRequest
import org.mozilla.geckoview.WebRequest.CACHE_MODE_DEFAULT
import org.mozilla.geckoview.WebRequest.CACHE_MODE_RELOAD
import org.mozilla.geckoview.WebRequestError
import org.mozilla.geckoview.WebResponse
import java.io.IOException
import java.net.SocketTimeoutException
import java.nio.ByteBuffer
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
/**
* GeckoView ([GeckoWebExecutor]) based implementation of [Client].
*/
class GeckoViewFetchClient(
context: Context,
runtime: GeckoRuntime = GeckoRuntime.getDefault(context),
private val maxReadTimeOut: Pair<Long, TimeUnit> = Pair(MAX_READ_TIMEOUT_MINUTES, TimeUnit.MINUTES)
) : Client() {
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal var executor: GeckoWebExecutor = GeckoWebExecutor(runtime)
@Throws(IOException::class)
override fun fetch(request: Request): Response {
if (request.isDataUri()) {
return fetchDataUri(request)
}
val webRequest = request.toWebRequest(defaultHeaders)
val readTimeOut = request.readTimeout ?: maxReadTimeOut
val readTimeOutMillis = readTimeOut.let { (timeout, unit) ->
unit.toMillis(timeout)
}
return try {
var fetchFlags = 0
if (request.cookiePolicy == Request.CookiePolicy.OMIT) {
fetchFlags += GeckoWebExecutor.FETCH_FLAGS_ANONYMOUS
}
if (request.redirect == Request.Redirect.MANUAL) {
fetchFlags += GeckoWebExecutor.FETCH_FLAGS_NO_REDIRECTS
}
val webResponse = executor.fetch(webRequest, fetchFlags).poll(readTimeOutMillis)
webResponse?.toResponse(request.isBlobUri()) ?: throw IOException("Fetch failed with null response")
} catch (e: TimeoutException) {
throw SocketTimeoutException()
} catch (e: WebRequestError) {
throw IOException(e)
}
}
companion object {
const val MAX_READ_TIMEOUT_MINUTES = 5L
}
}
private fun Request.toWebRequest(defaultHeaders: Headers): WebRequest = WebRequest.Builder(url)
.method(method.name)
.addHeadersFrom(this, defaultHeaders)
.addBodyFrom(this)
.cacheMode(if (useCaches) CACHE_MODE_DEFAULT else CACHE_MODE_RELOAD)
.build()
private fun WebRequest.Builder.addHeadersFrom(request: Request, defaultHeaders: Headers): WebRequest.Builder {
defaultHeaders.filter { header ->
request.headers?.contains(header.name) != true
}.forEach { header ->
addHeader(header.name, header.value)
}
request.headers?.forEach { header ->
addHeader(header.name, header.value)
}
return this
}
private fun WebRequest.Builder.addBodyFrom(request: Request): WebRequest.Builder {
request.body?.let { body ->
body.useStream { inStream ->
val bytes = inStream.readBytes()
val buffer = ByteBuffer.allocateDirect(bytes.size)
buffer.put(bytes)
this.body(buffer)
}
}
return this
}
@VisibleForTesting
internal fun WebResponse.toResponse(isBlobUri: Boolean): Response {
val headers = translateHeaders(this)
// We use the same API for blobs and HTTP requests, but blobs won't receive a status code.
// If no exception is thrown we assume success.
val status = if (isBlobUri) SUCCESS else statusCode
return Response(
uri,
status,
headers,
body?.let {
Response.Body(it, headers["Content-Type"])
} ?: Response.Body.empty()
)
}
private fun translateHeaders(webResponse: WebResponse): Headers {
val headers = MutableHeaders()
webResponse.headers.forEach { (k, v) ->
v.split(",").forEach { headers.append(k, it.trim()) }
}
return headers
}