Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Parse/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ dependencies {
compile 'com.parse.bolts:bolts-android:1.2.1'

provided 'com.squareup.okhttp:okhttp:2.4.0'
provided 'com.facebook.stetho:stetho:1.1.1'

testCompile 'org.robolectric:robolectric:3.0'
testCompile 'org.skyscreamer:jsonassert:1.2.3'
Expand Down
286 changes: 286 additions & 0 deletions Parse/src/main/java/com/parse/ParseStethoInterceptor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
/*
* Copyright (c) 2015-present, Parse, LLC.
* 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.parse;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add license headers


import com.facebook.stetho.inspector.network.DefaultResponseHandler;
import com.facebook.stetho.inspector.network.NetworkEventReporter;
import com.facebook.stetho.inspector.network.NetworkEventReporterImpl;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import javax.annotation.Nullable;

/**
* {@code ParseStethoInterceptor} is used to log the request and response through Stetho to chrome
* browser debugger.
*/
/** package */ class ParseStethoInterceptor implements ParseNetworkInterceptor {

private static final String CONTENT_LENGTH_HEADER = "Content-Length";
private static final String CONTENT_TYPE_HEADER = "Content-Type";

// Implementation of Stetho request
private static class ParseInterceptorHttpRequest
implements NetworkEventReporter.InspectorRequest {
private final String requestId;
private final ParseHttpRequest request;
private byte[] body;
private boolean hasGeneratedBody;
// Since Stetho use index to get header, we use a list to store them
private List<String> headers;

public ParseInterceptorHttpRequest(String requestId, ParseHttpRequest request) {
this.requestId = requestId;
this.request = request;

// Add content-length and content-type header to the interceptor. These headers are added when
// a real httpclient send the request. Since we still want users to see these headers, we
// manually add them to Interceptor request if they are not in the header list
headers = new ArrayList<>();
for (Map.Entry<String, String> headerEntry : request.getAllHeaders().entrySet()) {
headers.add(headerEntry.getKey());
headers.add(headerEntry.getValue());
}
if (request.getBody() != null) {
if (!headers.contains(CONTENT_LENGTH_HEADER)) {
headers.add(CONTENT_LENGTH_HEADER);
headers.add(String.valueOf(request.getBody().getContentLength()));
}
// If user does not set contentType when create ParseFile, it may be null
if (request.getBody().getContentType() != null && !headers.contains(CONTENT_TYPE_HEADER)) {
headers.add(CONTENT_TYPE_HEADER);
headers.add(request.getBody().getContentType());
}
}
}

@Override
public String id() {
return requestId;
}

@Override
public String friendlyName() {
return null;
}

@Nullable
@Override
public Integer friendlyNameExtra() {
return null;
}

@Override
public String url() {
return request.getUrl();
}

@Override
public String method() {
return request.getMethod().toString();
}

@Nullable
@Override
public byte[] body() throws IOException {
if (!hasGeneratedBody) {
hasGeneratedBody = true;
body = generateBody();
}
return body;
}

private byte[] generateBody() throws IOException {
ParseHttpBody body = request.getBody();
if (body == null) {
return null;
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
body.writeTo(out);
return out.toByteArray();
}

@Override
public int headerCount() {
return headers.size() / 2;
}

@Override
public String headerName(int index) {
return headers.get(index * 2);
}

@Override
public String headerValue(int index) {
return headers.get(index * 2 + 1);
}

@Nullable
@Override
public String firstHeaderValue(String name) {
int index = headers.indexOf(name);
return index >= 0 ? headers.get(index + 1) : null;
}
}

// Implementation of Stetho response
private static class ParseInspectorHttpResponse
implements NetworkEventReporter.InspectorResponse {
private final String requestId;
private final ParseHttpRequest request;
private final ParseHttpResponse response;
// Since stetho use index to get header, we use a list to store them
private List<String> responseHeaders;

public ParseInspectorHttpResponse(
String requestId,
ParseHttpRequest request,
ParseHttpResponse response) {
this.requestId = requestId;
this.request = request;
this.response = response;
responseHeaders = new ArrayList<>();
for (Map.Entry<String, String> headerEntry : response.getAllHeaders().entrySet()) {
responseHeaders.add(headerEntry.getKey());
responseHeaders.add(headerEntry.getValue());
}
}

@Override
public String requestId() {
return requestId;
}

@Override
public String url() {
return request.getUrl();
}

@Override
public int statusCode() {
return response.getStatusCode();
}

@Override
public String reasonPhrase() {
return response.getReasonPhrase();
}

@Override
public boolean connectionReused() {
// Follow Stetho URLConnectionInspectorResponse
return false;
}

@Override
public int connectionId() {
// Follow Stetho URLConnectionInspectorResponse
return requestId.hashCode();
}

@Override
public boolean fromDiskCache() {
// Follow Stetho URLConnectionInspectorResponse
return false;
}

@Override
public int headerCount() {
return response.getAllHeaders().size();
}

@Override
public String headerName(int index) {
return responseHeaders.get(index * 2);
}

@Override
public String headerValue(int index) {
return responseHeaders.get(index * 2 + 1);
}

@Nullable
@Override
public String firstHeaderValue(String name) {
int index = responseHeaders.indexOf(name);
return index >= 0 ? responseHeaders.get(index + 1) : null;
}
}

// Stetho reporter
private final NetworkEventReporter stethoEventReporter = NetworkEventReporterImpl.get();

// Request Id generator
private final AtomicInteger nextRequestId = new AtomicInteger(0);

@Override
public ParseHttpResponse intercept(Chain chain) throws IOException {
// Intercept request
String requestId = String.valueOf(nextRequestId.getAndIncrement());
ParseHttpRequest request = chain.getRequest();

// If Stetho debugger is available (chrome debugger is open), intercept the request
if (stethoEventReporter.isEnabled()) {
ParseInterceptorHttpRequest inspectorRequest =
new ParseInterceptorHttpRequest(requestId, chain.getRequest());
stethoEventReporter.requestWillBeSent(inspectorRequest);
}


ParseHttpResponse response;
try {
response = chain.proceed(request);
} catch (IOException e) {
// If Stetho debugger is available (chrome debugger is open), intercept the exception
if (stethoEventReporter.isEnabled()) {
stethoEventReporter.httpExchangeFailed(requestId, e.toString());
}
throw e;
}

if (stethoEventReporter.isEnabled()) {
// If Stetho debugger is available (chrome debugger is open), intercept the response body
if (request.getBody() != null) {
stethoEventReporter.dataSent(requestId, (int)(request.getBody().getContentLength()),
(int)(request.getBody().getContentLength()));
}

// If Stetho debugger is available (chrome debugger is open), intercept the response headers
stethoEventReporter.responseHeadersReceived(
new ParseInspectorHttpResponse(requestId, request, response));

InputStream responseStream = null;
if (response.getContent() != null) {
responseStream = response.getContent();
}

// Create the Stetho proxy inputStream, when Parse read this stream, it will proxy the
// response body to Stetho reporter.
responseStream = stethoEventReporter.interpretResponseStream(
requestId,
response.getContentType(),
response.getAllHeaders().get("Content-Encoding"),
responseStream,
new DefaultResponseHandler(stethoEventReporter, requestId)
);
if (responseStream != null) {
response = response.newBuilder()
.setContent(responseStream)
.build();
}
}
return response;
}
}