Google Summer of Code 2024, Project Report
-
Project Title: Creating an HTTP Client using Dart Bindings from OkHttp
-
Organization: Dart
-
Contributor: Anikate De
-
Mentors: Brian Quinlan, Camille Simon
-
Project Size: Large, 350 hours
This project aimed at creating a novel Flutter plugin that integrates the OkHttp library with Dart, to provide an HTTP Client for Flutter applications. This integration would be facilitated using Dart's JNIgen package, which generates JNI bindings for Java & Kotlin code. Since OkHttp can interact with native Android APIs, this plugin was expected to provide support for the following user-requested features:
-
Support for
KeyStore
PrivateKey
s (#50669) -
Support for the System Proxy (#50434)
-
Support for User-Installed Certificates (#50435)
The aforementioned Client was expected to be conformant with the other HTTP Clients available in the Dart HTTP mono-repo, to ensure a seamless transition for the users.
Finally, the project also aimed at creating a new WebSocketChannel using OkHttp's WebSocket APIs.
During my initial proposal, the project was broadly divided into four milestones:
-
Create a bare-bones
package:ok_http
: Using an FFI package template to create a new package, and setting up the necessary dependencies. -
Implement the BaseClient Interface and execute requests synchronously: Not using asynchronous APIs from OkHttp yet, this milestone ensures that the Client was able to execute basic requests.
-
Execute Asynchronous Requests and Stream ResponseBody: Using OkHttp's
enqueue()
API, this milestone aimed at executing asynchronous requests and streaming the response body. Due to the nature ofInputStream
s in Java, several challenges were expected to be faced. -
WebSocket Implementation: The final milestone proposed the creation of a new
OkHttpWebSocket
Class using OkHttp'sWebSocket
andWebSocketListener
APIs.
From the above milestones, the following project flow was expected:
-
Set up the package and dependencies.
-
Identify and generate the JNI Bindings of the required OkHttp APIs using
package:jnigen
. -
Implement the
BaseClient
interface and execute synchronous requests. -
Implement the
enqueue()
API and execute asynchronous requests. -
Stream the response body.
-
Pass all conformance tests in
package:http_client_conformance_tests
. -
Implement the
WebSocket
interface frompackage:web_socket
. -
Pass all conformance tests in
package:web_socket_conformance_tests
. -
Publish the package to pub.dev!
Spoiler Alert!
All the above steps were completed successfully and on schedule! The package was published to pub.dev on the 5th of August, 2024.
Starting May 29th, 2024, I made the following Pull Requests to the Dart HTTP mono-repo:
Additionally, I created another PR in the Dart Native repository:
# | Title | Status |
---|---|---|
1 | pkgs/jni: Support Throwing Java Exceptions in Interface Impl. #1211 | Merged ✅ |
After around 75 days of consistent contributions and development (excluding the sample project development period), package:ok_http
was released on August 5th, 2024. The package was published to pub.dev, and is now available for use by the Flutter Community. 🥳🎉
Click here to see ok_http
All the milestones were adequately completed, and the package passed all the HTTP Client and WebSocket conformance tests in the Dart HTTP mono-repo. The package was also documented thoroughly, and extra features, such as support for Flutter DevTools Network Tab, and configurable timeouts, were also added.
Interestingly, using the package leads to a valuable reduction in the APK size of Flutter applications, adding to its benefits.
package:ok_http
is now a part of the Dart HTTP mono-repo, and is expected to be maintained by both myself and the Dart Team.
Although JNIgen is a powerful tool, it is an experimental package. An array of issues piled up while generating bindings for OkHttp, especially while making package:ok_http
conformant.
The first issue, whose severity went unnoticed, was due to threading model of JNI.
Due to the architecture of the integration tests, the OkHttpClient was forced to wait for a response from the server. Although insignificant visually, this led to a deadlock, eventually leading to a java.net.SocketTimeoutException
.
As predicted in the comment, this issue was resolved after adding support for Streamed Responses, as the InputStream
was read asynchronously, and did not block the main thread.
OkHttp has a hard limit of 20 redirects, which is not configurable. This was a major issue, as the conformance tests expected the client to follow the redirects, that are customized by the user.
This required the implementation of a custom Interceptor
that would keep track of the number of redirects, and throw an exception if the limit was reached.
This simple solution, however, turned into a prolonged issue, as the Interceptor
is a "special" interface in its own right. Interceptor.Chain.proceed()
led to deadlocks and made it impossible for Dart JNI Bindings to implement them.
A workaround was found, dedicated Kotlin code was written to handle the redirects, and the Interceptor
was implemented in Kotlin, instead of Dart.
The issue is still open, and is expected to be resolved in the future.
Streaming the response bodies was a major challenge, as the InputStream
in Java is blocking, and does not support asynchronous reading. This required the use of a separate thread to read the InputStream
, and pass the data to the Dart side.
Again, the threading model of JNI came into play, forcing the use of dedicated Kotlin code to handle the streaming in a multi-threaded manner.
The conformance tests in the Dart HTTP mono-repo are quite strict, and expect the client to behave in a certain way. This led to a few issues, especially with supporting folded headers. A new flag was added to skip these tests, as the client was not expected to support them. You can read more about this issue here.
Furthermore, testRequestCookies
was deemed as "racy" by Brian which was fixed in this PR.
Just like the redirects, the WebSocket conformance tests were also quite strict and expected the client to only contain a specific set of headers in responses. This required creating another implementation of Interceptor
, called the WebSocketInterceptor
, which would only add the required headers to the WebSocket handshake.
package:ok_http
turned out to be a great success. Smaller APK sizes, WebSocket support, Flutter DevTools Networking Tab support, and a seamless integration with the Dart HTTP mono-repo, are some of the highlights of the project.
It has been a great journey, and I am looking forward to maintaining package:ok_http
in the future, along with the Dart team. Some of the future work includes:
-
Adding Response Cache Configuration: OkHttp provides a powerful
Cache
API, which can be used to cache responses. This would be a great addition to the package. -
Support for other Protocols: We wish to support a wider range of protocols supported by OkHttp, such as HTTP/2, and QUIC.
-
Flutter Integration Testing: I learned how to write integration tests for Flutter applications, and how to test the HTTP Client using the
package:http_client_conformance_tests
. -
JNI Threading Model: A HUGE thanks to Hossein Yousefi (although not the assigned mentor in this project, he went out of his way to review my PRs and schedule demo meetings, and answer all of my doubts) for helping me understand the threading model of JNI, and how to work around it.
-
Using JNIgen: JNIgen is an awesome tool, and I learned how to generate JNI bindings for Java and Kotlin code using it. I can see myself using it in future projects as well.
-
Reading HTTP and WebSocket RFCs: I read the HTTP and WebSocket RFCs to understand how the protocols work, and how the clients should behave. This was a great learning experience.
I would like to thank the Dart Team, especially Brian and Camille, for guiding me throughout the project and helping me resolve the issues I faced. I would also like to thank Hossein for his invaluable help and support.
I would also like to thank the Flutter Community for their support and feedback, and the Google Summer of Code team for providing me with this opportunity.
This summer has been nothing short of amazing, and I am looking forward to contributing to the Dart Community, and more open-source projects in the future.