All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.mapbox.mapboxsdk.http.HTTPRequest Maven / Gradle / Ivy

There is a newer version: 9.2.1
Show newest version
package com.mapbox.mapboxsdk.http;


import android.content.Context;
import android.content.pm.PackageInfo;
import android.os.Build;
import android.text.TextUtils;

import com.mapbox.mapboxsdk.BuildConfig;
import com.mapbox.mapboxsdk.Mapbox;
import com.mapbox.mapboxsdk.constants.MapboxConstants;

import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.NoRouteToHostException;
import java.net.ProtocolException;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.concurrent.locks.ReentrantLock;

import javax.net.ssl.SSLException;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.internal.Util;
import timber.log.Timber;

class HTTPRequest implements Callback {

  private static OkHttpClient mClient = new OkHttpClient();
  private String USER_AGENT_STRING = null;

  private static final int CONNECTION_ERROR = 0;
  private static final int TEMPORARY_ERROR = 1;
  private static final int PERMANENT_ERROR = 2;

  // Reentrancy is not needed, but "Lock" is an
  // abstract class.
  private ReentrantLock mLock = new ReentrantLock();

  private long mNativePtr = 0;

  private Call mCall;
  private Request mRequest;

  private native void nativeOnFailure(int type, String message);

  private native void nativeOnResponse(int code, String etag, String modified, String cacheControl, String expires,
                                       String retryAfter, String xRateLimitReset, byte[] body);

  private HTTPRequest(long nativePtr, String resourceUrl, String etag, String modified) {
    mNativePtr = nativePtr;

    try {
      // Don't try a request if we aren't connected
      if (!Mapbox.isConnected()) {
        throw new NoRouteToHostException("No Internet connection available.");
      }

      HttpUrl httpUrl = HttpUrl.parse(resourceUrl);
      final String host = httpUrl.host().toLowerCase(MapboxConstants.MAPBOX_LOCALE);
      if (host.equals("mapbox.com") || host.endsWith(".mapbox.com") || host.equals("mapbox.cn")
        || host.endsWith(".mapbox.cn")) {
        if (httpUrl.querySize() == 0) {
          resourceUrl = resourceUrl + "?";
        } else {
          resourceUrl = resourceUrl + "&";
        }
        resourceUrl = resourceUrl + "events=true";
      }

      Request.Builder builder = new Request.Builder()
        .url(resourceUrl)
        .tag(resourceUrl.toLowerCase(MapboxConstants.MAPBOX_LOCALE))
        .addHeader("User-Agent", getUserAgent());
      if (etag.length() > 0) {
        builder = builder.addHeader("If-None-Match", etag);
      } else if (modified.length() > 0) {
        builder = builder.addHeader("If-Modified-Since", modified);
      }
      mRequest = builder.build();
      mCall = mClient.newCall(mRequest);
      mCall.enqueue(this);
    } catch (Exception exception) {
      onFailure(exception);
    }
  }

  public void cancel() {
    // mCall can be null if the constructor gets aborted (e.g, under a NoRouteToHostException).
    if (mCall != null) {
      mCall.cancel();
    }

    // TODO: We need a lock here because we can try
    // to cancel at the same time the request is getting
    // answered on the OkHTTP thread. We could get rid of
    // this lock by using Runnable when we move Android
    // implementation of mbgl::RunLoop to Looper.
    mLock.lock();
    mNativePtr = 0;
    mLock.unlock();
  }

  @Override
  public void onResponse(Call call, Response response) throws IOException {
    if (response.isSuccessful()) {
      Timber.v(String.format("[HTTP] Request was successful (code = %d).", response.code()));
    } else {
      // We don't want to call this unsuccessful because a 304 isn't really an error
      String message = !TextUtils.isEmpty(response.message()) ? response.message() : "No additional information";
      Timber.d(String.format(
        "[HTTP] Request with response code = %d: %s",
        response.code(), message));
    }

    byte[] body;
    try {
      body = response.body().bytes();
    } catch (IOException ioException) {
      onFailure(ioException);
      // throw ioException;
      return;
    } finally {
      response.body().close();
    }

    mLock.lock();
    if (mNativePtr != 0) {
      nativeOnResponse(response.code(),
        response.header("ETag"),
        response.header("Last-Modified"),
        response.header("Cache-Control"),
        response.header("Expires"),
        response.header("Retry-After"),
        response.header("x-rate-limit-reset"),
        body);
    }
    mLock.unlock();
  }

  @Override
  public void onFailure(Call call, IOException e) {
    onFailure(e);
  }

  private void onFailure(Exception e) {
    int type = PERMANENT_ERROR;
    if ((e instanceof NoRouteToHostException) || (e instanceof UnknownHostException) || (e instanceof SocketException)
      || (e instanceof ProtocolException) || (e instanceof SSLException)) {
      type = CONNECTION_ERROR;
    } else if ((e instanceof InterruptedIOException)) {
      type = TEMPORARY_ERROR;
    }

    String errorMessage = e.getMessage() != null ? e.getMessage() : "Error processing the request";

    if (type == TEMPORARY_ERROR) {
      Timber.d(String.format(MapboxConstants.MAPBOX_LOCALE,
        "Request failed due to a temporary error: %s", errorMessage));
    } else if (type == CONNECTION_ERROR) {
      Timber.i(String.format(MapboxConstants.MAPBOX_LOCALE,
        "Request failed due to a connection error: %s", errorMessage));
    } else {
      // PERMANENT_ERROR
      Timber.w(String.format(MapboxConstants.MAPBOX_LOCALE,
        "Request failed due to a permanent error: %s", errorMessage));
    }

    mLock.lock();
    if (mNativePtr != 0) {
      nativeOnFailure(type, errorMessage);
    }
    mLock.unlock();
  }

  private String getUserAgent() {
    if (USER_AGENT_STRING == null) {
      return USER_AGENT_STRING = Util.toHumanReadableAscii(
        String.format("%s %s (%s) Android/%s (%s)",
          getApplicationIdentifier(),
          BuildConfig.MAPBOX_VERSION_STRING,
          BuildConfig.GIT_REVISION_SHORT,
          Build.VERSION.SDK_INT,
          Build.CPU_ABI)
      );
    } else {
      return USER_AGENT_STRING;
    }
  }

  private String getApplicationIdentifier() {
    try {
      Context context = Mapbox.getApplicationContext();
      PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
      return String.format("%s/%s (%s)", context.getPackageName(), packageInfo.versionName, packageInfo.versionCode);
    } catch (Exception exception) {
      return "";
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy