forked from facebook/stetho
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add StethoInterceptor for OkHttp 2.2.0+
This makes it possible to enable Stetho network inspection by a one-line change to existing projects that use OkHttp 2.2.0+. This is made possible by the new Interceptors feature: https://github.com/square/okhttp/wiki/Interceptors
- Loading branch information
Josh Guilfoyle
committed
Feb 3, 2015
1 parent
5ac8e9c
commit eef68e1
Showing
7 changed files
with
498 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
include ':stetho' | ||
include ':stetho-urlconnection' | ||
include ':stetho-okhttp' | ||
include ':stetho-sample' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/build |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
apply plugin: 'com.android.library' | ||
apply plugin: 'robolectric' | ||
|
||
android { | ||
compileSdkVersion 21 | ||
buildToolsVersion "21.1.2" | ||
|
||
defaultConfig { | ||
minSdkVersion 9 | ||
targetSdkVersion 21 | ||
versionCode 1 | ||
versionName "1.0" | ||
} | ||
|
||
sourceSets { | ||
androidTest { | ||
setRoot('src/test') | ||
} | ||
} | ||
|
||
lintOptions { | ||
// This seems to be firing due to okio referencing java.nio.File | ||
// which is harmless for us. Not sure how to disable this in | ||
// more targeted fashion... | ||
warning 'InvalidPackage' | ||
} | ||
} | ||
|
||
robolectric { | ||
include '**/*Test.class' | ||
exclude '**/espresso/**/*.class' | ||
} | ||
|
||
dependencies { | ||
compile project(':stetho') | ||
compile 'com.google.code.findbugs:jsr305:2.0.1' | ||
//noinspection GradleDynamicVersion | ||
compile 'com.squareup.okhttp:okhttp:2.2.0+' | ||
|
||
androidTestCompile 'junit:junit:4.12' | ||
androidTestCompile('org.robolectric:robolectric:2.4') { | ||
exclude module: 'commons-logging' | ||
exclude module: 'httpclient' | ||
} | ||
androidTestCompile 'org.powermock:powermock-api-mockito:1.6.1' | ||
androidTestCompile 'org.powermock:powermock-module-junit4:1.6.1' | ||
|
||
// Needed for Robolectric and PowerMock to be combined in a single test. | ||
androidTestCompile 'org.powermock:powermock-module-junit4-rule:1.6.1' | ||
androidTestCompile 'org.powermock:powermock-classloading-xstream:1.6.1' | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||
package="com.facebook.stetho.okhttp"> | ||
|
||
<application /> | ||
|
||
</manifest> |
286 changes: 286 additions & 0 deletions
286
stetho-okhttp/src/main/java/com/facebook/stetho/okhttp/StethoInterceptor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,286 @@ | ||
package com.facebook.stetho.okhttp; | ||
|
||
import javax.annotation.Nullable; | ||
|
||
import java.io.ByteArrayOutputStream; | ||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import java.util.concurrent.atomic.AtomicInteger; | ||
|
||
import com.facebook.stetho.inspector.network.DefaultResponseHandler; | ||
import com.facebook.stetho.inspector.network.NetworkEventReporter; | ||
import com.facebook.stetho.inspector.network.NetworkEventReporterImpl; | ||
|
||
import com.squareup.okhttp.Connection; | ||
import com.squareup.okhttp.Interceptor; | ||
import com.squareup.okhttp.MediaType; | ||
import com.squareup.okhttp.Request; | ||
import com.squareup.okhttp.RequestBody; | ||
import com.squareup.okhttp.Response; | ||
import com.squareup.okhttp.ResponseBody; | ||
import okio.BufferedSink; | ||
import okio.BufferedSource; | ||
import okio.Okio; | ||
|
||
/** | ||
* Provides easy integration with <a href="http://square.github.io/okhttp/">OkHttp</a> 2.2.0+ | ||
* by way of the new <a href="https://github.com/square/okhttp/wiki/Interceptors">Interceptor</a> | ||
* system. To use: | ||
* <pre> | ||
* OkHttpClient client = new OkHttpClient(); | ||
* client.networkInterceptors().add(new StethoInterceptor()); | ||
* </pre> | ||
*/ | ||
public class StethoInterceptor implements Interceptor { | ||
private final NetworkEventReporter mEventReporter = NetworkEventReporterImpl.get(); | ||
|
||
private final AtomicInteger mNextRequestId = new AtomicInteger(0); | ||
|
||
@Override | ||
public Response intercept(Chain chain) throws IOException { | ||
String requestId = String.valueOf(mNextRequestId.getAndIncrement()); | ||
|
||
Request request = chain.request(); | ||
|
||
int requestSize = 0; | ||
if (mEventReporter.isEnabled()) { | ||
OkHttpInspectorRequest inspectorRequest = new OkHttpInspectorRequest(requestId, request); | ||
mEventReporter.requestWillBeSent(inspectorRequest); | ||
byte[] requestBody = inspectorRequest.body(); | ||
if (requestBody != null) { | ||
requestSize += requestBody.length; | ||
} | ||
} | ||
|
||
Response response; | ||
try { | ||
response = chain.proceed(request); | ||
} catch (IOException e) { | ||
if (mEventReporter.isEnabled()) { | ||
mEventReporter.httpExchangeFailed(requestId, e.toString()); | ||
} | ||
throw e; | ||
} | ||
|
||
if (mEventReporter.isEnabled()) { | ||
if (requestSize > 0) { | ||
mEventReporter.dataSent(requestId, requestSize, requestSize); | ||
} | ||
|
||
Connection connection = chain.connection(); | ||
mEventReporter.responseHeadersReceived( | ||
new OkHttpInspectorResponse( | ||
requestId, | ||
request, | ||
response, | ||
connection)); | ||
|
||
ResponseBody body = response.body(); | ||
MediaType contentType = null; | ||
InputStream responseStream = null; | ||
if (body != null) { | ||
contentType = body.contentType(); | ||
responseStream = body.byteStream(); | ||
} | ||
|
||
responseStream = mEventReporter.interpretResponseStream( | ||
requestId, | ||
contentType != null ? contentType.toString() : null, | ||
responseStream, | ||
new DefaultResponseHandler(mEventReporter, requestId)); | ||
if (responseStream != null) { | ||
response = response.newBuilder() | ||
.body(new ForwardingResponseBody(body, responseStream)) | ||
.build(); | ||
} | ||
} | ||
|
||
return response; | ||
} | ||
|
||
private static class OkHttpInspectorRequest implements NetworkEventReporter.InspectorRequest { | ||
private final String mRequestId; | ||
private final Request mRequest; | ||
private byte[] mBody; | ||
private boolean mBodyGenerated; | ||
|
||
public OkHttpInspectorRequest(String requestId, Request request) { | ||
mRequestId = requestId; | ||
mRequest = request; | ||
} | ||
|
||
@Override | ||
public String id() { | ||
return mRequestId; | ||
} | ||
|
||
@Override | ||
public String friendlyName() { | ||
// Hmm, can we do better? tag() perhaps? | ||
return null; | ||
} | ||
|
||
@Nullable | ||
@Override | ||
public Integer friendlyNameExtra() { | ||
return null; | ||
} | ||
|
||
@Override | ||
public String url() { | ||
return mRequest.urlString(); | ||
} | ||
|
||
@Override | ||
public String method() { | ||
return mRequest.method(); | ||
} | ||
|
||
@Nullable | ||
@Override | ||
public byte[] body() throws IOException { | ||
if (!mBodyGenerated) { | ||
mBodyGenerated = true; | ||
mBody = generateBody(); | ||
} | ||
return mBody; | ||
} | ||
|
||
private byte[] generateBody() throws IOException { | ||
RequestBody body = mRequest.body(); | ||
if (body != null) { | ||
ByteArrayOutputStream out = new ByteArrayOutputStream(); | ||
BufferedSink sink = Okio.buffer(Okio.sink(out)); | ||
body.writeTo(sink); | ||
sink.flush(); | ||
return out.toByteArray(); | ||
} else { | ||
return null; | ||
} | ||
} | ||
|
||
@Override | ||
public int headerCount() { | ||
return mRequest.headers().size(); | ||
} | ||
|
||
@Override | ||
public String headerName(int index) { | ||
return mRequest.headers().name(index); | ||
} | ||
|
||
@Override | ||
public String headerValue(int index) { | ||
return mRequest.headers().value(index); | ||
} | ||
|
||
@Nullable | ||
@Override | ||
public String firstHeaderValue(String name) { | ||
return mRequest.header(name); | ||
} | ||
} | ||
|
||
private static class OkHttpInspectorResponse implements NetworkEventReporter.InspectorResponse { | ||
private final String mRequestId; | ||
private final Request mRequest; | ||
private final Response mResponse; | ||
private final Connection mConnection; | ||
|
||
public OkHttpInspectorResponse( | ||
String requestId, | ||
Request request, | ||
Response response, | ||
Connection connection) { | ||
mRequestId = requestId; | ||
mRequest = request; | ||
mResponse = response; | ||
mConnection = connection; | ||
} | ||
|
||
@Override | ||
public String requestId() { | ||
return mRequestId; | ||
} | ||
|
||
@Override | ||
public String url() { | ||
return mRequest.urlString(); | ||
} | ||
|
||
@Override | ||
public int statusCode() { | ||
return mResponse.code(); | ||
} | ||
|
||
@Override | ||
public String reasonPhrase() { | ||
return mResponse.message(); | ||
} | ||
|
||
@Override | ||
public boolean connectionReused() { | ||
// Not sure... | ||
return false; | ||
} | ||
|
||
@Override | ||
public int connectionId() { | ||
return mConnection.hashCode(); | ||
} | ||
|
||
@Override | ||
public boolean fromDiskCache() { | ||
return mResponse.cacheResponse() != null; | ||
} | ||
|
||
@Override | ||
public int headerCount() { | ||
return mResponse.headers().size(); | ||
} | ||
|
||
@Override | ||
public String headerName(int index) { | ||
return mResponse.headers().name(index); | ||
} | ||
|
||
@Override | ||
public String headerValue(int index) { | ||
return mResponse.headers().value(index); | ||
} | ||
|
||
@Nullable | ||
@Override | ||
public String firstHeaderValue(String name) { | ||
return mResponse.header(name); | ||
} | ||
} | ||
|
||
private static class ForwardingResponseBody extends ResponseBody { | ||
private final ResponseBody mBody; | ||
private final BufferedSource mInterceptedSource; | ||
|
||
public ForwardingResponseBody(ResponseBody body, InputStream interceptedStream) { | ||
mBody = body; | ||
mInterceptedSource = Okio.buffer(Okio.source(interceptedStream)); | ||
} | ||
|
||
@Override | ||
public MediaType contentType() { | ||
return mBody.contentType(); | ||
} | ||
|
||
@Override | ||
public long contentLength() { | ||
return mBody.contentLength(); | ||
} | ||
|
||
@Override | ||
public BufferedSource source() { | ||
// close on the delegating body will actually close this intercepted source, but it | ||
// was derived from mBody.byteStream() therefore the close will be forwarded all the | ||
// way to the original. | ||
return mInterceptedSource; | ||
} | ||
} | ||
} |
Oops, something went wrong.