Search code examples
androidhttplistenerandroid-glidehttp-response-codes

How to get http responseCode in Glide listener?


I am using Glide to show previews of images located on the remote server. There is a need to handle different response codes in a different way. For example when the server responses with code 202, I have to wait for certain socket event. Here is my code:

fun ImageView.loadRounded(glideUrl: GlideUrl, radius: Int = 20) {
    GlideApp.with(this.context)
            .load(glideUrl)
            .apply(RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL))
            .apply(RequestOptions.bitmapTransform(RoundedCorners(radius)))
            .listener(object : RequestListener<Drawable> {
                override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean {
                    e?.rootCauses?.forEach {
                        logd("${it.message}")
                    }
                    return false
                }
                override fun onResourceReady(resource: Drawable?, model: Any?, target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
                    return false
                }
            })
            .into(this)
}

In the case when server sends response code 202, in onLoadFailed I get this output:

java.io.IOException(java.lang.RuntimeException: setDataSource failed: status = 0x80000000)

It happens because 202 is one of successfull status codes. Glide tries to download picture, but it is not there.

Method e?.logRootCauses() doesn't give response codes neither.

Is there a way to get http response code in Glide listener?


Solution

  • Found a way to intercept status code and replace it with another one. For this I have replaced DataFetcher implemetation used in Glide with modified version. Here are the steps:

    Create @GlideModule if you don't have it already:

    @GlideModule
    public class MyGlideModule extends AppGlideModule {
        @Override
        public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
            registry.append(GlideUrl.class, InputStream.class,
                new MyOkHttpUrlLoader.Factory(new OkHttpClient()));
        }
    }
    

    where standard OkHttpUrlLoader is also replaced with modified version. Unfortunately, you can't just extend standard implementation since you have to interact with private members of it. So It should be a full copy of original OkHttpUrlLoader, but return custom DataFetcher in buildLoadData method. Also build method should return instance of modified version of loader:

    import androidx.annotation.NonNull;
    
    import com.bumptech.glide.load.Options;
    import com.bumptech.glide.load.model.GlideUrl;
    import com.bumptech.glide.load.model.ModelLoader;
    import com.bumptech.glide.load.model.ModelLoaderFactory;
    import com.bumptech.glide.load.model.MultiModelLoaderFactory;
    
    import java.io.InputStream;
    
    import okhttp3.Call;
    import okhttp3.OkHttpClient;
    
    /**
     * A simple model loader for fetching media over http/https using OkHttp.
     */
    public class MyOkHttpUrlLoader implements ModelLoader<GlideUrl, InputStream> {
    
        private final Call.Factory client;
    
        // Public API.
        @SuppressWarnings("WeakerAccess")
        public MyOkHttpUrlLoader(@NonNull Call.Factory client) {
            this.client = client;
        }
    
        @Override
        public boolean handles(@NonNull GlideUrl url) {
            return true;
        }
    
        @Override
        public LoadData<InputStream> buildLoadData(@NonNull GlideUrl model, int width, int height,
                                                   @NonNull Options options) {
            return new LoadData<>(model, new MyOkHttpStreamFetcher(client, model));
        }
    
        /**
         * The default factory for {@link com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader}s.
         */
        // Public API.
        @SuppressWarnings("WeakerAccess")
        public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {
            private static volatile Call.Factory internalClient;
            private final Call.Factory client;
    
            /**
             * Constructor for a new Factory that runs requests using a static singleton client.
             */
            public Factory() {
                this(getInternalClient());
            }
    
            /**
             * Constructor for a new Factory that runs requests using given client.
             *
             * @param client this is typically an instance of {@code OkHttpClient}.
             */
            public Factory(@NonNull Call.Factory client) {
                this.client = client;
            }
    
            private static Call.Factory getInternalClient() {
                if (internalClient == null) {
                    synchronized (Factory.class) {
                        if (internalClient == null) {
                            internalClient = new OkHttpClient();
                        }
                    }
                }
                return internalClient;
            }
    
            @NonNull
            @Override
            public ModelLoader<GlideUrl, InputStream> build(MultiModelLoaderFactory multiFactory) {
                return new MyOkHttpUrlLoader(client);
            }
    
            @Override
            public void teardown() {
                // Do nothing, this instance doesn't own the client.
            }
        }
     }
    

    And here is my modified version of DataFetcher, which is almost a copy of a standard implementation (OkHttpStreamFetcher). Difference is that in onResponse method I check code, and in case it is 202 I replace it with error code 418:

    import android.util.Log;
    
    import androidx.annotation.NonNull;
    
    import com.bumptech.glide.Priority;
    import com.bumptech.glide.load.DataSource;
    import com.bumptech.glide.load.HttpException;
    import com.bumptech.glide.load.data.DataFetcher;
    import com.bumptech.glide.load.model.GlideUrl;
    import com.bumptech.glide.util.ContentLengthInputStream;
    import com.bumptech.glide.util.Preconditions;
    import com.zultys.mobiledroid.utils.logger.Logger;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.Map;
    import okhttp3.Call;
    import okhttp3.Request;
    import okhttp3.Response;
    import okhttp3.ResponseBody;
    
    /**
     * Fetches an {@link InputStream} using the okhttp library.
     */
    public class MyOkHttpStreamFetcher implements DataFetcher<InputStream>, okhttp3.Callback {
        private static final String TAG = "OkHttpFetcher";
        private final Call.Factory client;
        private final GlideUrl url;
        private InputStream stream;
        private ResponseBody responseBody;
        private DataCallback<? super InputStream> callback;
        // call may be accessed on the main thread while the object is in use on other threads. All other
        // accesses to variables may occur on different threads, but only one at a time.
        private volatile Call call;
    
        // Public API.
        @SuppressWarnings("WeakerAccess")
        public ZultysOkHttpStreamFetcher(Call.Factory client, GlideUrl url) {
            this.client = client;
            this.url = url;
        }
    
        @Override
        public void loadData(@NonNull Priority priority,
                             @NonNull final DataCallback<? super InputStream> callback) {
            Request.Builder requestBuilder = new Request.Builder().url(url.toStringUrl());
            for (Map.Entry<String, String> headerEntry : url.getHeaders().entrySet()) {
                String key = headerEntry.getKey();
                requestBuilder.addHeader(key, headerEntry.getValue());
            }
            Request request = requestBuilder.build();
            this.callback = callback;
    
            call = client.newCall(request);
            call.enqueue(this);
        }
    
        @Override
        public void onFailure(@NonNull Call call, @NonNull IOException e) {
            if (Log.isLoggable(TAG, Log.DEBUG)) {
                Log.d(TAG, "OkHttp failed to obtain result", e);
            }
            callback.onLoadFailed(e);
        }
    
        @Override
        public void onResponse(@NonNull Call call, @NonNull Response response) {
            responseBody = response.body();
            // Code 202 means that file was sent from another MX in MX net,
            // and you have to wait until it gets copied to your MX. After that you will receive
            // csta packet with messageStatus IMS_FILE_DOWNLOAD, and can repeat same request.
            if (response.code() == 202) {
                callback.onLoadFailed(new HttpException("I'm a teapot", 418));
            } else if (response.isSuccessful()) {
                long contentLength = Preconditions.checkNotNull(responseBody).contentLength();
                stream = ContentLengthInputStream.obtain(responseBody.byteStream(), contentLength);
                callback.onDataReady(stream);
            } else {
                callback.onLoadFailed(new HttpException(response.message(), response.code()));
            }
        }
    
        @Override
        public void cleanup() {
            try {
                if (stream != null) {
                    stream.close();
                }
            } catch (IOException e) {
                // Ignored
            }
            if (responseBody != null) {
                responseBody.close();
            }
            callback = null;
        }
    
        @Override
        public void cancel() {
            Call local = call;
            if (local != null) {
                local.cancel();
            }
        }
    
        @NonNull
        @Override
        public Class<InputStream> getDataClass() {
            return InputStream.class;
        }
    
        @NonNull
        @Override
        public DataSource getDataSource() {
            return DataSource.REMOTE;
        }
     }