Skip to content

Commit 3e5f6bb

Browse files
authored
Make it possible to configure CronetEngine settings (#767)
1 parent 760e854 commit 3e5f6bb

File tree

8 files changed

+927
-125
lines changed

8 files changed

+927
-125
lines changed

.github/workflows/cronet.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ jobs:
6161
- name: Run tests
6262
uses: reactivecircus/android-emulator-runner@v2
6363
with:
64-
api-level: 32
64+
api-level: 28
6565
target: playstore
6666
arch: x86_64
6767
profile: pixel
68-
script: cd ./pkgs/cronet_http/example && flutter pub get && flutter --version && flutter -v test integration_test/
68+
script: cd ./pkgs/cronet_http/example && flutter test --timeout=1200s integration_test/

pkgs/cronet_http/android/src/main/java/io/flutter/plugins/cronet_http/Messages.java

Lines changed: 314 additions & 9 deletions
Large diffs are not rendered by default.

pkgs/cronet_http/android/src/main/kotlin/io/flutter/plugins/cronet_http/CronetHttpPlugin.kt

Lines changed: 142 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -20,80 +20,94 @@ import java.util.concurrent.atomic.AtomicInteger
2020

2121
class CronetHttpPlugin : FlutterPlugin, Messages.HttpApi {
2222
private lateinit var flutterPluginBinding: FlutterPlugin.FlutterPluginBinding
23-
private lateinit var cronetEngine: CronetEngine
2423

24+
private val engineIdToEngine = HashMap<String, CronetEngine>()
2525
private val executor = Executors.newCachedThreadPool()
2626
private val mainThreadHandler = Handler(Looper.getMainLooper())
2727
private val channelId = AtomicInteger(0)
28+
private val engineId = AtomicInteger(0)
2829

2930
override fun onAttachedToEngine(
3031
@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding
3132
) {
3233
Messages.HttpApi.setup(flutterPluginBinding.binaryMessenger, this)
3334
this.flutterPluginBinding = flutterPluginBinding
34-
val context = flutterPluginBinding.getApplicationContext()
35-
36-
// TODO: Move the Cronet initialization elsewhere so that failures (e.g. because the device
37-
// doesn't have Google Play services) can be communicated to the Dart client.
38-
val builder = CronetEngine.Builder(context)
39-
cronetEngine = builder.build()
4035
}
4136

4237
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
4338
Messages.HttpApi.setup(binding.binaryMessenger, null)
4439
}
4540

46-
override fun start(startRequest: Messages.StartRequest): Messages.StartResponse {
47-
// Create a unique channel to communicate Cronet events to the Dart client code with.
48-
val channelName = "plugins.flutter.io/cronet_event/" + channelId.incrementAndGet()
49-
val eventChannel = EventChannel(flutterPluginBinding.binaryMessenger, channelName)
50-
lateinit var eventSink: EventChannel.EventSink
51-
var numRedirects = 0
41+
override fun createEngine(createRequest: Messages.CreateEngineRequest): Messages.CreateEngineResponse {
42+
try {
43+
val builder = CronetEngine.Builder(flutterPluginBinding.getApplicationContext())
5244

53-
val cronetRequest =
54-
cronetEngine.newUrlRequestBuilder(
55-
startRequest.url,
56-
object : UrlRequest.Callback() {
57-
override fun onRedirectReceived(
58-
request: UrlRequest,
59-
info: UrlResponseInfo,
60-
newLocationUrl: String
61-
) {
62-
if (!startRequest.getFollowRedirects()) {
63-
request.cancel()
64-
mainThreadHandler.post({
65-
eventSink.success(
66-
Messages.EventMessage.Builder()
67-
.setType(Messages.EventMessageType.responseStarted)
68-
.setResponseStarted(
69-
Messages.ResponseStarted.Builder()
70-
.setStatusCode(info.getHttpStatusCode().toLong())
71-
.setHeaders(info.getAllHeaders())
72-
.setIsRedirect(true)
73-
.build()
74-
)
75-
.build()
76-
.toMap()
77-
)
78-
})
79-
}
80-
++numRedirects
81-
if (numRedirects <= startRequest.getMaxRedirects()) {
82-
request.followRedirect()
83-
} else {
84-
request.cancel()
85-
mainThreadHandler.post({
86-
eventSink.success(
87-
Messages.EventMessage.Builder()
88-
.setType(Messages.EventMessageType.tooManyRedirects)
89-
.build()
90-
.toMap()
91-
)
92-
})
93-
}
94-
}
45+
if (createRequest.getStoragePath() != null) {
46+
builder.setStoragePath(createRequest.getStoragePath()!!)
47+
}
48+
49+
if (createRequest.getCacheMode() == Messages.CacheMode.disabled) {
50+
builder.enableHttpCache(createRequest.getCacheMode()!!.ordinal, 0)
51+
} else if (createRequest.getCacheMode() != null && createRequest.getCacheMaxSize() != null) {
52+
builder.enableHttpCache(createRequest.getCacheMode()!!.ordinal, createRequest.getCacheMaxSize()!!)
53+
}
54+
55+
if (createRequest.getEnableBrotli() != null) {
56+
builder.enableBrotli(createRequest.getEnableBrotli()!!)
57+
}
58+
59+
if (createRequest.getEnableHttp2() != null) {
60+
builder.enableHttp2(createRequest.getEnableHttp2()!!)
61+
}
62+
63+
if (createRequest.getEnablePublicKeyPinningBypassForLocalTrustAnchors() != null) {
64+
builder.enablePublicKeyPinningBypassForLocalTrustAnchors(createRequest.getEnablePublicKeyPinningBypassForLocalTrustAnchors()!!)
65+
}
9566

96-
override fun onResponseStarted(request: UrlRequest?, info: UrlResponseInfo) {
67+
if (createRequest.getEnableQuic() != null) {
68+
builder.enableQuic(createRequest.getEnableQuic()!!)
69+
}
70+
71+
if (createRequest.getUserAgent() != null) {
72+
builder.setUserAgent(createRequest.getUserAgent()!!)
73+
}
74+
75+
val engine = builder.build()
76+
val engineName = "cronet_engine_" + engineId.incrementAndGet()
77+
engineIdToEngine.put(engineName, engine)
78+
return Messages.CreateEngineResponse.Builder()
79+
.setEngineId(engineName)
80+
.build()
81+
} catch (e: IllegalArgumentException) {
82+
return Messages.CreateEngineResponse.Builder()
83+
.setErrorString(e.message)
84+
.setErrorType(Messages.ExceptionType.illegalArgumentException)
85+
.build()
86+
} catch (e: Exception) {
87+
return Messages.CreateEngineResponse.Builder()
88+
.setErrorString(e.message)
89+
.setErrorType(Messages.ExceptionType.otherException)
90+
.build()
91+
}
92+
}
93+
94+
override fun freeEngine(engineId: String) {
95+
engineIdToEngine.remove(engineId)
96+
}
97+
98+
private fun createRequest(startRequest: Messages.StartRequest, cronetEngine: CronetEngine, eventSink: EventChannel.EventSink): UrlRequest {
99+
var numRedirects = 0
100+
101+
val cronetRequest = cronetEngine.newUrlRequestBuilder(
102+
startRequest.url,
103+
object : UrlRequest.Callback() {
104+
override fun onRedirectReceived(
105+
request: UrlRequest,
106+
info: UrlResponseInfo,
107+
newLocationUrl: String
108+
) {
109+
if (!startRequest.getFollowRedirects()) {
110+
request.cancel()
97111
mainThreadHandler.post({
98112
eventSink.success(
99113
Messages.EventMessage.Builder()
@@ -102,51 +116,84 @@ class CronetHttpPlugin : FlutterPlugin, Messages.HttpApi {
102116
Messages.ResponseStarted.Builder()
103117
.setStatusCode(info.getHttpStatusCode().toLong())
104118
.setHeaders(info.getAllHeaders())
105-
.setIsRedirect(false)
119+
.setIsRedirect(true)
106120
.build()
107121
)
108122
.build()
109123
.toMap()
110124
)
111125
})
112-
request?.read(ByteBuffer.allocateDirect(1024 * 1024))
113126
}
114-
115-
override fun onReadCompleted(
116-
request: UrlRequest,
117-
info: UrlResponseInfo,
118-
byteBuffer: ByteBuffer
119-
) {
120-
byteBuffer.flip()
121-
val b = ByteArray(byteBuffer.remaining())
122-
byteBuffer.get(b)
127+
++numRedirects
128+
if (numRedirects <= startRequest.getMaxRedirects()) {
129+
request.followRedirect()
130+
} else {
131+
request.cancel()
123132
mainThreadHandler.post({
124133
eventSink.success(
125134
Messages.EventMessage.Builder()
126-
.setType(Messages.EventMessageType.readCompleted)
127-
.setReadCompleted(Messages.ReadCompleted.Builder().setData(b).build())
135+
.setType(Messages.EventMessageType.tooManyRedirects)
128136
.build()
129137
.toMap()
130138
)
131139
})
132-
byteBuffer.clear()
133-
request?.read(byteBuffer)
134140
}
141+
}
135142

136-
override fun onSucceeded(request: UrlRequest, info: UrlResponseInfo?) {
137-
mainThreadHandler.post({ eventSink.endOfStream() })
138-
}
143+
override fun onResponseStarted(request: UrlRequest?, info: UrlResponseInfo) {
144+
mainThreadHandler.post({
145+
eventSink.success(
146+
Messages.EventMessage.Builder()
147+
.setType(Messages.EventMessageType.responseStarted)
148+
.setResponseStarted(
149+
Messages.ResponseStarted.Builder()
150+
.setStatusCode(info.getHttpStatusCode().toLong())
151+
.setHeaders(info.getAllHeaders())
152+
.setIsRedirect(false)
153+
.build()
154+
)
155+
.build()
156+
.toMap()
157+
)
158+
})
159+
request?.read(ByteBuffer.allocateDirect(1024 * 1024))
160+
}
139161

140-
override fun onFailed(
141-
request: UrlRequest,
142-
info: UrlResponseInfo,
143-
error: CronetException
144-
) {
145-
mainThreadHandler.post({ eventSink.error("CronetException", error.toString(), null) })
146-
}
147-
},
148-
executor
149-
)
162+
override fun onReadCompleted(
163+
request: UrlRequest,
164+
info: UrlResponseInfo,
165+
byteBuffer: ByteBuffer
166+
) {
167+
byteBuffer.flip()
168+
val b = ByteArray(byteBuffer.remaining())
169+
byteBuffer.get(b)
170+
mainThreadHandler.post({
171+
eventSink.success(
172+
Messages.EventMessage.Builder()
173+
.setType(Messages.EventMessageType.readCompleted)
174+
.setReadCompleted(Messages.ReadCompleted.Builder().setData(b).build())
175+
.build()
176+
.toMap()
177+
)
178+
})
179+
byteBuffer.clear()
180+
request?.read(byteBuffer)
181+
}
182+
183+
override fun onSucceeded(request: UrlRequest, info: UrlResponseInfo?) {
184+
mainThreadHandler.post({ eventSink.endOfStream() })
185+
}
186+
187+
override fun onFailed(
188+
request: UrlRequest,
189+
info: UrlResponseInfo,
190+
error: CronetException
191+
) {
192+
mainThreadHandler.post({ eventSink.error("CronetException", error.toString(), null) })
193+
}
194+
},
195+
executor
196+
)
150197

151198
if (startRequest.getBody().size > 0) {
152199
cronetRequest.setUploadDataProvider(
@@ -158,16 +205,24 @@ class CronetHttpPlugin : FlutterPlugin, Messages.HttpApi {
158205
for ((key, value) in startRequest.getHeaders()) {
159206
cronetRequest.addHeader(key, value)
160207
}
208+
return cronetRequest.build()
209+
}
210+
211+
override fun start(startRequest: Messages.StartRequest): Messages.StartResponse {
212+
// Create a unique channel to communicate Cronet events to the Dart client code with.
213+
val channelName = "plugins.flutter.io/cronet_event/" + channelId.incrementAndGet()
214+
val eventChannel = EventChannel(flutterPluginBinding.binaryMessenger, channelName)
161215

162216
// Don't start the Cronet request until the Dart client code is listening for events.
163217
val streamHandler =
164218
object : EventChannel.StreamHandler {
165219
override fun onListen(arguments: Any?, events: EventChannel.EventSink) {
166-
eventSink = events
167220
try {
168-
cronetRequest.build().start()
221+
val cronetEngine = engineIdToEngine.getValue(startRequest.engineId)
222+
val cronetRequest = createRequest(startRequest, cronetEngine, events)
223+
cronetRequest.start()
169224
} catch (e: Exception) {
170-
mainThreadHandler.post({ eventSink.error("CronetException", e.toString(), null) })
225+
mainThreadHandler.post({ events.error("CronetException", e.toString(), null) })
171226
}
172227
}
173228

0 commit comments

Comments
 (0)