Git Product home page Git Product logo

cronet-transport-for-okhttp's Introduction

Cronet Transport for OkHttp and Retrofit

This package allows OkHttp and Retrofit users to use Cronet as their transport layer, benefiting from features like QUIC/HTTP3 support and connection migration.

First steps

Installation

The easiest way to import this library is to include it as a Gradle dependency in your app's build.gradle file. Simply add the following line and specify the desired version. The available VERSION can be found in the Google Maven Repo eg 0.1.0.

implementation 'com.google.net.cronet:cronet-okhttp:VERSION'

You'll also need to specify a dependency on OkHttp (which you likely already have) and add a dependency on core Cronet, which we cover in the next section.

Adding a Cronet dependency

There are several ways to obtain a CronetEngine instance, which is necessary to initialize this library.

From the Play Services

We recommend using a Google Play Services provider which loads Cronet implementation from the platform. This way the application doesn't need to pay the binary size cost of carrying Cronet and the platform ensures that the latest updates and security fixes are delivered. We also recommend falling back on using plain OkHttp if the platform-wide Cronet isn't available (e.g. because the device doesn't integrate with Google Play Services, or the platform has been tampered with).

In order to use the Play Services Cronet provider, add the following dependency to your project:

implementation "com.google.android.gms:play-services-cronet:18.0.1"

Before creating a Cronet engine, you also need to initialize the bindings between your application and Google Play Services by calling

CronetProviderInstaller.installProvider(context);

Note that the installProvider call is asynchronous. We omit handling of failures and synchronization here for brevity - check the sample application provided with the library for an example which is more appropriate for production use.

Finally, you can create a CronetEngine instance. Check out the documentation for custom CronetEngine.Builder configuration options.

CronetEngine cronetEngine = new CronetEngine.Builder(context).build();

Setting up the library

There are two ways to use this library — either as an OkHttp application interceptor, or as a Call factory.

If your application makes extensive use of application interceptors, using the library as an interceptor will be more practical as you can keep your current interceptor logic. Just add an extra interceptor to your client.

Note: Add the Cronet interceptor last, otherwise the subsequent interceptors will be skipped.

CronetEngine engine = new CronetEngine.Builder(context).build();

Call.Factory callFactory = new OkHttpClient.Builder()
   .addInterceptor(CronetInterceptor.newBuilder(engine).build())
   .build();

If you don't make heavy use of OkHttp's interceptors or if you're working with another library which requires Call.Factory instances (e.g. Retrofit), you'll be better off using the custom call factory implementation.

CronetEngine engine = new CronetEngine.Builder(context).build();

Call.Factory callFactory = CronetCallFactory.newBuilder(engine).build();

And that's it! You can now benefit from the Cronet goodies while using OkHttp APIs as usual:

String run(String url) throws IOException {
  Request request = new Request.Builder()
      .url(url)
      .build();
  try (Response response = callFactory.newCall(request).execute()) {
    return response.body().string();
  }
}

It's almost as simple in Retrofit:

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .callFactory(callFactory)
    .build();

Configuration

The transport libraries are configured in two ways. The builders for both the interceptor and the call factory provide configuration options which directly affect how the interop layer behaves. Most of the network configuration (certificate pinning, proxies etc.) should be done directly in the Cronet engine.

We're open to providing convenience utilities which will simplify configuring the Cronet engine — please reach out and tell us more about your use case if this sounds interesting!

Incompatibilities

While our design principle is to implement the full set of OkHttp APIs on top of Cronet, it's not always possible due to limitations and/or fundamental incompatibilities of the two layers. We are aware of the following list of limitations and features that are not provided by the bridge:

Common incompatibilities

  • The entirety of OkHttp core is bypassed. This includes caching, retries, authentication, and network interceptors. These features have to be enabled directly on the Cronet engine or built on top of this library.
  • It's not possible to set multiple values for a single header key in outgoing requests, Cronet uses the last value provided.
  • Accept-Encoding are automatically populated by Cronet based on the engine configuration. Custom values are ignored.
  • The Response object doesn't have the following fields set:
    • handshake
    • networkResponse
    • cacheResponse
    • sentRequestAtMillis / receivedResponseAtMillis
  • The Request field under Response is set as seen by the outmost layer and doesn't reflect internal Cronet transformations.
  • Response parsing logic is different at places. Generally, Cronet is more lenient and will silently drop headers/fall back to default values where OkHttp might throw an exception (for example, parsing status codes). This shouldn't be a concern for typical usage patterns.
  • Generally, while errors convey the same message across plain OkHttp and this library, the error message details differ.

Interceptor incompatibilities

  • Call cancellation signals are propagated with a delay.
  • If the Cronet interceptor isn't the last application interceptor, the subsequent interceptors are bypassed.
  • Most of the OkHttpClient network-related configuration which is handled by the core network logic is bypassed and has to be reconfigured directly on your CronetEngine builder.
  • Intermediate EventListener stages are not being reported.

Call factory incompatibilities

  • OkHttpClient configuration is unavailable and bypassed completely.

For contributors

Please see CONTRIBUTING.md.

License

This library is licensed under Apache License Version 2.0.

cronet-transport-for-okhttp's People

Contributors

danstahrg avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cronet-transport-for-okhttp's Issues

Redirected URL

Hello,

Is there a way to get a redirected URL from OkHttp's Response object?

Thanks

Requests are resolved through HTTP2

I'm trying to implement HTTP/3 with QUIC support in my Android app using OkHttp and the Cronet transport. However, all requests are being made under HTTP/2. I have used different devices with different versions. I've tried with Retrofit as well and same result.

The server I'm reaching out does support HTTP3 I've tested it on web and it works fine.

private val engine = CronetEngine.Builder(MainActivity.appContext).enableHttpCache(CronetEngine.Builder.HTTP_CACHE_DISK, 10 * 1024 * 1024)
    .build()


    private val callFactory = OkHttpClient.Builder()
        .addInterceptor(CronetInterceptor.newBuilder(engine).build())
    .build();

private fun getRequest(url: String): JSONObject? {
        val request: Request = Request.Builder()
            .url(url)
            .build()


        val response = callFactory.newCall(request).execute()
        return response.body?.string()?.let { JSONObject(it) }
    }

image

HttpLoggingInterceptor not logging after attaching CronetInterceptor

val clientBuilder = OkHttpClient.Builder().apply {
            readTimeout(8, TimeUnit.SECONDS)
            connectTimeout(8, TimeUnit.SECONDS)
            addInterceptor(SomeInterceptor())
            addInterceptor(AuthInterceptor())
             addNetworkInterceptor(loggingInterceptor)
            cache(null)
            protocols(listOf(Protocol.QUIC, Protocol.HTTP_1_1))
        }
        clientBuilder.addInterceptor(
            CronetInterceptor.newBuilder(
                CronetEngine.Builder(context).enableQuic(true).build()
            ).build()
        )
clientBuilder.build()

Cronet with Retrofit is crashing due to file-descriptor leaks

CronetCallFactory.newBuilder(engine).build()
val builder = Retrofit.Builder()
builder.callFactory(factory)
// use builder with OkHttp

Before enabling Cronet with Retrofit using above snippet, make sure to chek the file-descriptor count, via:

  1. [standard emulator] adb shell
  2. su
  3. ls -l /proc/$(pidof APP_PACKAGE_NAME)/fd | wc [we're interested in the first value]

Running the same commands again, after we enable Cronet with Retrofit almost doubled the number of opened file descriptors for my app, which on some devices is causing a crash, since it hits the limit per one app.

crash

I used the lib but I encountered a crash below

java.util.concurrent.ExecutionException: ey: Exception in CronetUrlRequest: net::ERR_SSL_BAD_RECORD_MAC_ALERT, ErrorCode=11, InternalErrorCode=-126, Retryable=false

The onFailure method is not called when http connection fails.

The onFailure method should be called when http connection fails.


val core  = CronetEngine.Builder(applicationContext)
            .enableHttp2(true)
            .enableQuic(true)
            .addQuicHint("cloudflare-quic.com",443,443)
            .enableBrotli(true)
            .build()
        val okHttpClient = OkHttpClient().newBuilder()
            .addInterceptor(CronetInterceptor.newBuilder(core).build())
            .build()
        val quic=Request.Builder()
            .url("https://cloudflare-quic.com/")
            .build()
        okHttpClient.newCall(quic).enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {
                runOnUiThread{
                    Toast.makeText(applicationContext,"Failure!", Toast.LENGTH_SHORT).show()
                }
            }

            override fun onResponse(call: Call, response: Response) {
                runOnUiThread{
                    Toast.makeText(applicationContext,"Successful!", Toast.LENGTH_SHORT).show()
                }
            }
        })

Contribution instructions

Are there instructions for external contributions? Opening in Intellij or Android Studio?

Currently just command line, no support for IDE.

setURLStreamHandlerFactory is incompatible with Firebase

W  It's not necessary to set Accept-Encoding on requests - cronet will do this automatically for you, and setting it yourself has no effect. See https://crbug.com/581399 for details.
             java.lang.Exception
             	at m.iv.b(:com.google.android.gms.dynamite_cronetdynamite@[email protected] (190400-0):18)
             	at m.iv.addHeader(:com.google.android.gms.dynamite_cronetdynamite@[email protected] (190400-0):1)
             	at m.jn.l(:com.google.android.gms.dynamite_cronetdynamite@[email protected] (190400-0):149)
             	at m.jn.j(:com.google.android.gms.dynamite_cronetdynamite@[email protected] (190400-0):23)
             	at m.jn.getResponseCode(:com.google.android.gms.dynamite_cronetdynamite@[email protected] (190400-0):1)
             	at com.google.android.datatransport.cct.CctTransportBackend.doSend(CctTransportBackend.java:317)
             	at com.google.android.datatransport.cct.CctTransportBackend.$r8$lambda$bLAzIpNF4NtapXlUpPVGhzxyNT8(Unknown Source:0)
             	at com.google.android.datatransport.cct.CctTransportBackend$$ExternalSyntheticLambda0.apply(Unknown Source:4)
             	at com.google.android.datatransport.runtime.retries.Retries.retry(Retries.java:54)
             	at com.google.android.datatransport.cct.CctTransportBackend.send(CctTransportBackend.java:372)
             	at com.google.android.datatransport.runtime.scheduling.jobscheduling.Uploader.logAndUpdateState(Uploader.java:146)
             	at com.google.android.datatransport.runtime.scheduling.jobscheduling.Uploader.lambda$upload$1$com-google-android-datatransport-runtime-scheduling-jobscheduling-Uploader(Uploader.java:105)
             	at com.google.android.datatransport.runtime.scheduling.jobscheduling.Uploader$$ExternalSyntheticLambda2.run(Unknown Source:8)
             	at com.google.android.datatransport.runtime.SafeLoggingExecutor$SafeLoggingRunnable.run(SafeLoggingExecutor.java:47)
             	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
             	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
             	at java.lang.Thread.run(Thread.java:1012)

I'm using 0.1.0, and not intending to set it.

Example request Making request to: https://firebaselogging-pa.googleapis.com/v1/firelog/legacy/batchlog

java.lang.IllegalStateException: There is no read or rewind or length check in progress CronetUploadDataStream

crashes are counted on firebase.

my UploadDataProvider code:

        @Override
        public void rewind(UploadDataSink uploadDataSink) {
          if (LogUtils.isLogEnable()) {
            LogUtils.d("cronet", "InMemoryRequestBodyConverter , rewind: "+requestBody.isOneShot());
          }
          if (requestBody.isOneShot()) {
            uploadDataSink.onRewindError(new UnsupportedOperationException("Rewind is not supported!"));
          }else{
            isMaterialized = false;
            materializedBody.clear();
            uploadDataSink.onRewindSucceeded();
          }
        }

Occasional exceptions occur when sending network requests

UnsupportedOperationException often occurs when making network requests.

Below is the error message:
E/cr_CronetUrlRequestContext: Exception in upload method
java.lang.UnsupportedOperationException
at com.google.net.cronet.okhttptransport.RequestBodyConverterImpl$InMemoryRequestBodyConverter$1.rewind(RequestBodyConverterImpl.java:331)
at org.chromium.net.impl.VersionSafeCallbacks$UploadDataProviderWrapper.rewind(VersionSafeCallbacks.java:169)
at org.chromium.net.impl.CronetUploadDataStream$2.run(CronetUploadDataStream.java:151)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:764)

ByteArrayOutputStream size cant be negative

https://github.com/chromium/chromium/blob/0a33cb54788c73e01fe8c2b77ab36cddb270dd8b/components/cronet/android/api/src/org/chromium/net/apihelpers/InMemoryTransformCronetCallback.java#L67-L75

https://github.com/chromium/chromium/blob/0a33cb54788c73e01fe8c2b77ab36cddb270dd8b/components/cronet/android/api/src/org/chromium/net/apihelpers/InMemoryTransformCronetCallback.java#L108-L118

https://docs.oracle.com/javase/8/docs/api/java/io/ByteArrayOutputStream.html

ByteArrayOutputStream
public ByteArrayOutputStream(int size)
Creates a new byte array output stream, with a buffer capacity of the specified size, in bytes.
Parameters:
size - the initial size.
Throws:
[IllegalArgumentException](https://docs.oracle.com/javase/8/docs/api/java/lang/IllegalArgumentException.html) - if size is negative.

ANRs when loading Cronet Provider

Sharing some of the ANRs encountered when calling CronetProviderInstaller and when calling build method of CronetEngine.Builder and CronetInterceptor

There is one ANR happening due to System.loadLibrary being called on main thread by Play Services.

Is it safe to call the CronetProviderInstaller.installProvider() on a background thread?

anr_load_library.txt
anr1.txt
anr2.txt
anr3.txt

About the size of apk

This library depends on guava and other repositories , increases the size of apk,what should i do?remove guava dependency? replace it with the classes in android sdk?

Commercial Support

We would like to implement some capabilities on okhttp based on quic and was considering if you would consider commercial support for this to help accelerate the implementation of those features.

Let me know the best way to contact you for this

Secure DNS support

Hello!

We were wondering how to achieve DoH support with cronet, since OkHttp DNS seems to be bypassed by cronet.

README seems to suggest that we should implement that in Cronet, but there does not seem to be any way to do so.

Add a parallel version using android.net.http.HttpEngine

If the library supported both org.chromium.net and android.net.http, then it woould be possible to use it without the additional chromium download on U.

I guess separate libs make sense.

But alternatively one, but with chromium as a compile time dependencies only, and choosing the first one available at runtime.

adding it as an OkHttp interceptor, breaks okhttp3.Authenticator

OkHttpClient.Builder builder = new OkHttpClient.Builder()
  .authenticator(authenticator)
  .addInterceptor(cronetInterceptor);

OkHttpClient client = builder.build();

If OkHttpClient is created as per above and request returns with 401 (Unauthorized), then the passed okhttp3.Authenticator is no longer invoked, since that's basically implemented as an interceptor (RetryAndFollowUpInterceptor) added after the cronet interceptor is set (i.e. when the request is executed), as it can be seen in the attached screenshot.

Due to this, the user session won't be refreshed.

image

java.io.IOException: Content-Length (2472) and stream length (9805) disagree

java.io.IOException: Content-Length (2472) and stream length (9805) disagree
at okhttp3.ResponseBody.bytes(ResponseBody.kt:330)

return ResponseBody.create(
contentType != null ? MediaType.parse(contentType) : null,
contentLength,
Okio.buffer(bodySource));

when server use compression methods like Gzip,Brotli and ect.The returned Content-Length is not equal to the actual stream length(Cronet has decompressed the compressed data)

case ON_READ_COMPLETED:
result.buffer.flip();
int bytesWritten = sink.write(result.buffer);
result.buffer.clear();
return bytesWritten;

Option for symbolicated traces

Is it possible to add symbols or the option include to for improved crash tracing/debugging?

Seems to be how libcronet crashes manifest for us in crashlytics:

image

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.