apollo-kotlin: (V3 Migration) okhttp.OkHttpClient: <-- HTTP FAILED: java.lang.IllegalStateException: closed

Hi, I am migrating to V3, but keep getting the following error when uploading file(s).

okhttp.OkHttpClient: <-- HTTP FAILED: java.lang.IllegalStateException: closed

In version 2, everything works fine based on my implementation below:

apolloClient.mutate(
     CreateProjectMutation(
          ...
          video = FileUpload(mimetype = type, filePath = parameters.video),
          thumbnail = FileUpload(mimetype = thumbType, filePath = parameters.thumbnail)
       )
 )

This is my implementation in V3:

val upload = DefaultUpload.Builder()
      .content(File(parameters.video)) // using DefaultUpload.Builder content ext
      .contentType(type)
      .build()

val thumbnail = DefaultUpload.Builder()
      .content(File(parameters.thumbnail))
      .contentType(thumbType)
      .build()

apolloClient.mutation(
     CreateProjectMutation(
          ...
          video = upload,
          thumbnail = thumbnail
       )
 )

For reference, here is my mutation

mutation CreateProject(
    ...
    $thumbnail: Upload!,
    $video: Upload!
) ...

Here’s the network log

I/okhttp.OkHttpClient: Content-Type: multipart/form-data; boundary=44a3c556-0e96-44e1-824a-f3bfa67322a3
I/okhttp.OkHttpClient: X-APOLLO-OPERATION-ID: 3ad17cbe8d5f05812ba6d19dabd9f896304b6cb1808d105b0697ee188a074dc9
I/okhttp.OkHttpClient: X-APOLLO-OPERATION-NAME: CreateProject
I/okhttp.OkHttpClient: Authorization: JWT eyJ0eXAiOiJKV1QiLCJhb...
I/okhttp.OkHttpClient: apollographql-client-name: Android
I/okhttp.OkHttpClient: apollographql-client-version: 1.0.0
...
I/okhttp.OkHttpClient: �e��T��o�~����VJ/6�y{�_a�Op����/���5���.����˂�Η���˷)�i�Vj�"�Ô��3q�2a�J
I/okhttp.OkHttpClient: ZZZZZZZ^
I/okhttp.OkHttpClient: --> END POST (-1-byte body)
I/okhttp.OkHttpClient: <-- HTTP FAILED: java.lang.IllegalStateException: closed

What I did

I checked to make sure nothing was calling response.body.toString() 2x. Double checked the image/video files that were passed to DefaultUpload.Builder. Everything seems to be in place, but I am now going nuts trying to find the issue. Any idea of what I am doing wrong or missing? Any help would be greatly appreciated. Thanks

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 26 (13 by maintainers)

Most upvoted comments

@BoD Used your solution and works as planned. Additionally, used Kotlin delegation to make wrapper even shorter

class UploadWrapper(private val upload: Upload) : Upload by upload {

    override fun writeTo(sink: BufferedSink) {
        val stacktrace = Throwable().stackTrace
        val isFromNetworkInspector =
            stacktrace.any { it.className.startsWith("com.android") && it.methodName == "trackRequest" }
        if (isFromNetworkInspector) {
            //Do not consume the Upload when from Android Studio's Network Inspector
        } else {
            upload.writeTo(sink)
        }
    }
}

Thanks!

@martinbonnin tried version 3.3.2-SNAPSHOT but issue is still there. Below is the exception thrown. It’s easy to reproduce. Just try uploading file while App Inspection in Android Studio is active. Fun fact is that I don’t really need Network monitor from App Inspector but it comes with Work manager inspector an DB inspector that I cannot use now which makes development much harder. In my own interceptors I am buffering body as Apollo library expect but I cannot change interceptor used by IDE 😦 Hope that you can help with that somehow.

 Could not track an OkHttp3 request
    java.lang.IllegalStateException: Apollo: DefaultUpload body can only be read once. If you want to read it several times for logging or other purposes, either buffer it in memory or use your own `Upload` implementation.
        at com.apollographql.apollo3.api.DefaultUpload.writeTo(DefaultUpload.kt:27)
        at com.apollographql.apollo3.api.http.UploadsHttpBody.writeBoundaries(DefaultHttpRequestComposer.kt:319)
        at com.apollographql.apollo3.api.http.UploadsHttpBody.writeTo(DefaultHttpRequestComposer.kt:278)
        at com.apollographql.apollo3.network.http.DefaultHttpEngine$execute$2$httpRequest$1$2.writeTo(OkHttpEngine.kt:60)
        at com.android.tools.profiler.agent.okhttp.OkHttp3Interceptor.trackRequest(OkHttp3Interceptor.java:91)
        at com.android.tools.profiler.agent.okhttp.OkHttp3Interceptor.intercept(OkHttp3Interceptor.java:45)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
        at com.android.tools.appinspection.network.okhttp.OkHttp3Interceptor.intercept(OkHttp3Interceptor.kt:46)

Thanks for the update!

I can confirm I’m reproducing the issue.

The problem comes from the OkHttp Interceptor that Android Studio injects for the inspector, which reads (and consumes) the Upload’s body once before forwarding down the chain, where it’s expectedly read again.

I can see that this has been causing problems to other people in the past, and an issue was opened on Android Studio’s issue tracker, but was closed without a resolution. I’ve opened a new one here.

I could think of a (hacky!) workaround, which is to detect the presence of the inspector and deny the body reading:

class UploadWrapper(private val upload: Upload) : Upload {
    override val contentLength: Long
        get() = upload.contentLength

    override val contentType: String
        get() = upload.contentType

    override val fileName: String?
        get() = upload.fileName

    override fun writeTo(sink: BufferedSink) {
        val stacktrace = Throwable().stackTrace
        val isFromNetworkInspector = stacktrace.any { it.className.startsWith("com.android") && it.methodName == "trackRequest" }
        if (isFromNetworkInspector) {
            // Do not consume the Upload when from Android Studio's Network Inspector
        } else {
            upload.writeTo(sink)
        }
    }
}

It can be used like so:

val upload = DefaultUpload.Builder()
  // ...
  .build()

apolloClient.mutation(UploadMutation(UploadWrapper(upload))).execute()

I have resolved the issue for me using content(ByteArray) instead of content(File) since files I am uploading aren’t that big (few MB).

If your files are not too big indeed this is a solution, but it is not ideal as they are loaded into memory, which could get full. An idea: you could have this behavior (or the hack above) only in debug builds.

Thank you @BoD and @martinbonnin for the assist! I will keep an eye out for the fix. I will adopt the suggestion in the meantime.

Thank you for responding! I will inspect the payloads for both and report back.