/*
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.stetho.urlconnection;
import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
/**
* Individual connection flow manager that aids in communicating network events to Stetho
* via the {@link com.facebook.stetho.inspector.network.NetworkEventReporter} API. This class is
* stateful and should be instantiated for each individual HTTP request.
* <p>
* Be aware that there are caveats with inspection using {@link HttpURLConnection} on Android:
* <ul>
* <li>Compressed payload sizes are typically not available, even when compression was in use over
* the wire.
* <li>Redirects are by default handled internally, making it impossible to visualize them.
* To visualize them, redirects must be handled manually by invoking
* {@link HttpURLConnection#setFollowRedirects(boolean)}.
* </ul>
*/
@NotThreadSafe
public class StethoURLConnectionManager {
private static final boolean sIsStethoPresent;
static {
boolean isStethoPresent = false;
try {
Class.forName("com.facebook.stetho.Stetho");
isStethoPresent = true;
} catch (ClassNotFoundException e) {
}
sIsStethoPresent = isStethoPresent;
}
@Nullable
private final Holder mHolder;
// Holder hides StethoURLConnectionManagerImpl from the class verifier as per:
// http://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom
private static class Holder {
private final StethoURLConnectionManagerImpl impl;
public Holder(@Nullable String friendlyName) {
impl = new StethoURLConnectionManagerImpl(friendlyName);
}
}
public StethoURLConnectionManager(@Nullable String friendlyName) {
if (sIsStethoPresent) {
mHolder = new Holder(friendlyName);
} else {
mHolder = null;
}
}
public boolean isStethoEnabled() {
return mHolder != null && mHolder.impl.isStethoActive();
}
/**
* Indicates that the {@link HttpURLConnection} instance has been configured and is about
* to be used to initiate an actual HTTP connection. Call this method before any of the
* active methods such as {@link HttpURLConnection#connect()},
* {@link HttpURLConnection#getInputStream()}, or {@link HttpURLConnection#getOutputStream()}
*
* @param connection Connection instance configured with a method and headers.
* @param requestEntity Represents the request body if the request method supports it.
*/
public void preConnect(
HttpURLConnection connection,
@Nullable SimpleRequestEntity requestEntity) {
if (mHolder != null) {
mHolder.impl.preConnect(connection, requestEntity);
}
}
/**
* Indicates that the {@link HttpURLConnection} has just successfully exchanged HTTP messages
* (request headers + body and response headers) with the server but has not yet consumed
* the response body.
*
* @throws IOException May throw an exception internally due to {@link HttpURLConnection}
* method signatures. The request should be considered aborted/failed if this method
* throws.
*/
public void postConnect() throws IOException {
if (mHolder != null) {
mHolder.impl.postConnect();
}
}
/**
* Indicates that there was a non-recoverable failure during HTTP message exchange at some
* point between {@link #preConnect} and {@link #interpretResponseStream}.
*
* @param ex Relay the exception that was thrown from {@link java.net.HttpURLConnection}
*/
public void httpExchangeFailed(IOException ex) {
if (mHolder != null) {
mHolder.impl.httpExchangeFailed(ex);
}
}
/**
* Deliver the response stream from {@link HttpURLConnection#getInputStream()} to
* Stetho so that it can be intercepted. Note that compression is transparently
* supported on modern Android systems and no special awareness is necessary for
* gzip compression on the wire. Unfortunately this means that it is sometimes impossible
* to determine whether compression actually occurred and so Stetho may report inflated
* byte counts.
* <p>
* If the {@code Content-Length} header is provided by the server, this will be assumed to be
* the raw byte count on the wire.
*
* @param responseStream Stream as furnished by {@link HttpURLConnection#getInputStream()}.
*
* @return The filtering stream which is to be read after this method is called.
*/
public InputStream interpretResponseStream(@Nullable InputStream responseStream) {
if (mHolder != null) {
return mHolder.impl.interpretResponseStream(responseStream);
} else {
return responseStream;
}
}
/**
* Convenience method to access the lower level
* {@link com.facebook.stetho.inspector.network.NetworkEventReporter} API (must be explicitly
* cast).
*
* @deprecated This should no longer be used as it could potentially break the mechanism
* we use to allow convenient stripping of Stetho from release builds when using this
* module. If you need access to this, consider writing your own custom version of this
* module.
*/
@Deprecated
@Nullable
public Object getStethoHook() {
if (mHolder != null) {
return mHolder.impl.getStethoHook();
} else {
return null;
}
}
/**
* Low level method to access this request's unique identifier according to
* {@link com.facebook.stetho.inspector.network.NetworkEventReporter}. Most callers won't
* need this.
*/
@Nullable
public String getStethoRequestId() {
if (mHolder != null) {
return mHolder.impl.getStethoRequestId();
} else {
return null;
}
}
}