Skip to content

Commit 20a6818

Browse files
committed
Merge pull request #48 from ParsePlatform/wangmengyan.add_Stetho_interceptor
Add ParseStethoInterceptor
2 parents bccbcbb + 14ca2da commit 20a6818

File tree

2 files changed

+287
-0
lines changed

2 files changed

+287
-0
lines changed

Parse/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ dependencies {
4242
compile 'com.parse.bolts:bolts-android:1.2.1'
4343

4444
provided 'com.squareup.okhttp:okhttp:2.4.0'
45+
provided 'com.facebook.stetho:stetho:1.1.1'
4546

4647
testCompile 'org.robolectric:robolectric:3.0'
4748
testCompile 'org.skyscreamer:jsonassert:1.2.3'
Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
/*
2+
* Copyright (c) 2015-present, Parse, LLC.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
package com.parse;
10+
11+
import com.facebook.stetho.inspector.network.DefaultResponseHandler;
12+
import com.facebook.stetho.inspector.network.NetworkEventReporter;
13+
import com.facebook.stetho.inspector.network.NetworkEventReporterImpl;
14+
15+
import java.io.ByteArrayOutputStream;
16+
import java.io.IOException;
17+
import java.io.InputStream;
18+
import java.util.ArrayList;
19+
import java.util.List;
20+
import java.util.Map;
21+
import java.util.concurrent.atomic.AtomicInteger;
22+
23+
import javax.annotation.Nullable;
24+
25+
/**
26+
* {@code ParseStethoInterceptor} is used to log the request and response through Stetho to chrome
27+
* browser debugger.
28+
*/
29+
/** package */ class ParseStethoInterceptor implements ParseNetworkInterceptor {
30+
31+
private static final String CONTENT_LENGTH_HEADER = "Content-Length";
32+
private static final String CONTENT_TYPE_HEADER = "Content-Type";
33+
34+
// Implementation of Stetho request
35+
private static class ParseInterceptorHttpRequest
36+
implements NetworkEventReporter.InspectorRequest {
37+
private final String requestId;
38+
private final ParseHttpRequest request;
39+
private byte[] body;
40+
private boolean hasGeneratedBody;
41+
// Since Stetho use index to get header, we use a list to store them
42+
private List<String> headers;
43+
44+
public ParseInterceptorHttpRequest(String requestId, ParseHttpRequest request) {
45+
this.requestId = requestId;
46+
this.request = request;
47+
48+
// Add content-length and content-type header to the interceptor. These headers are added when
49+
// a real httpclient send the request. Since we still want users to see these headers, we
50+
// manually add them to Interceptor request if they are not in the header list
51+
headers = new ArrayList<>();
52+
for (Map.Entry<String, String> headerEntry : request.getAllHeaders().entrySet()) {
53+
headers.add(headerEntry.getKey());
54+
headers.add(headerEntry.getValue());
55+
}
56+
if (request.getBody() != null) {
57+
if (!headers.contains(CONTENT_LENGTH_HEADER)) {
58+
headers.add(CONTENT_LENGTH_HEADER);
59+
headers.add(String.valueOf(request.getBody().getContentLength()));
60+
}
61+
// If user does not set contentType when create ParseFile, it may be null
62+
if (request.getBody().getContentType() != null && !headers.contains(CONTENT_TYPE_HEADER)) {
63+
headers.add(CONTENT_TYPE_HEADER);
64+
headers.add(request.getBody().getContentType());
65+
}
66+
}
67+
}
68+
69+
@Override
70+
public String id() {
71+
return requestId;
72+
}
73+
74+
@Override
75+
public String friendlyName() {
76+
return null;
77+
}
78+
79+
@Nullable
80+
@Override
81+
public Integer friendlyNameExtra() {
82+
return null;
83+
}
84+
85+
@Override
86+
public String url() {
87+
return request.getUrl();
88+
}
89+
90+
@Override
91+
public String method() {
92+
return request.getMethod().toString();
93+
}
94+
95+
@Nullable
96+
@Override
97+
public byte[] body() throws IOException {
98+
if (!hasGeneratedBody) {
99+
hasGeneratedBody = true;
100+
body = generateBody();
101+
}
102+
return body;
103+
}
104+
105+
private byte[] generateBody() throws IOException {
106+
ParseHttpBody body = request.getBody();
107+
if (body == null) {
108+
return null;
109+
}
110+
ByteArrayOutputStream out = new ByteArrayOutputStream();
111+
body.writeTo(out);
112+
return out.toByteArray();
113+
}
114+
115+
@Override
116+
public int headerCount() {
117+
return headers.size() / 2;
118+
}
119+
120+
@Override
121+
public String headerName(int index) {
122+
return headers.get(index * 2);
123+
}
124+
125+
@Override
126+
public String headerValue(int index) {
127+
return headers.get(index * 2 + 1);
128+
}
129+
130+
@Nullable
131+
@Override
132+
public String firstHeaderValue(String name) {
133+
int index = headers.indexOf(name);
134+
return index >= 0 ? headers.get(index + 1) : null;
135+
}
136+
}
137+
138+
// Implementation of Stetho response
139+
private static class ParseInspectorHttpResponse
140+
implements NetworkEventReporter.InspectorResponse {
141+
private final String requestId;
142+
private final ParseHttpRequest request;
143+
private final ParseHttpResponse response;
144+
// Since stetho use index to get header, we use a list to store them
145+
private List<String> responseHeaders;
146+
147+
public ParseInspectorHttpResponse(
148+
String requestId,
149+
ParseHttpRequest request,
150+
ParseHttpResponse response) {
151+
this.requestId = requestId;
152+
this.request = request;
153+
this.response = response;
154+
responseHeaders = new ArrayList<>();
155+
for (Map.Entry<String, String> headerEntry : response.getAllHeaders().entrySet()) {
156+
responseHeaders.add(headerEntry.getKey());
157+
responseHeaders.add(headerEntry.getValue());
158+
}
159+
}
160+
161+
@Override
162+
public String requestId() {
163+
return requestId;
164+
}
165+
166+
@Override
167+
public String url() {
168+
return request.getUrl();
169+
}
170+
171+
@Override
172+
public int statusCode() {
173+
return response.getStatusCode();
174+
}
175+
176+
@Override
177+
public String reasonPhrase() {
178+
return response.getReasonPhrase();
179+
}
180+
181+
@Override
182+
public boolean connectionReused() {
183+
// Follow Stetho URLConnectionInspectorResponse
184+
return false;
185+
}
186+
187+
@Override
188+
public int connectionId() {
189+
// Follow Stetho URLConnectionInspectorResponse
190+
return requestId.hashCode();
191+
}
192+
193+
@Override
194+
public boolean fromDiskCache() {
195+
// Follow Stetho URLConnectionInspectorResponse
196+
return false;
197+
}
198+
199+
@Override
200+
public int headerCount() {
201+
return response.getAllHeaders().size();
202+
}
203+
204+
@Override
205+
public String headerName(int index) {
206+
return responseHeaders.get(index * 2);
207+
}
208+
209+
@Override
210+
public String headerValue(int index) {
211+
return responseHeaders.get(index * 2 + 1);
212+
}
213+
214+
@Nullable
215+
@Override
216+
public String firstHeaderValue(String name) {
217+
int index = responseHeaders.indexOf(name);
218+
return index >= 0 ? responseHeaders.get(index + 1) : null;
219+
}
220+
}
221+
222+
// Stetho reporter
223+
private final NetworkEventReporter stethoEventReporter = NetworkEventReporterImpl.get();
224+
225+
// Request Id generator
226+
private final AtomicInteger nextRequestId = new AtomicInteger(0);
227+
228+
@Override
229+
public ParseHttpResponse intercept(Chain chain) throws IOException {
230+
// Intercept request
231+
String requestId = String.valueOf(nextRequestId.getAndIncrement());
232+
ParseHttpRequest request = chain.getRequest();
233+
234+
// If Stetho debugger is available (chrome debugger is open), intercept the request
235+
if (stethoEventReporter.isEnabled()) {
236+
ParseInterceptorHttpRequest inspectorRequest =
237+
new ParseInterceptorHttpRequest(requestId, chain.getRequest());
238+
stethoEventReporter.requestWillBeSent(inspectorRequest);
239+
}
240+
241+
242+
ParseHttpResponse response;
243+
try {
244+
response = chain.proceed(request);
245+
} catch (IOException e) {
246+
// If Stetho debugger is available (chrome debugger is open), intercept the exception
247+
if (stethoEventReporter.isEnabled()) {
248+
stethoEventReporter.httpExchangeFailed(requestId, e.toString());
249+
}
250+
throw e;
251+
}
252+
253+
if (stethoEventReporter.isEnabled()) {
254+
// If Stetho debugger is available (chrome debugger is open), intercept the response body
255+
if (request.getBody() != null) {
256+
stethoEventReporter.dataSent(requestId, (int)(request.getBody().getContentLength()),
257+
(int)(request.getBody().getContentLength()));
258+
}
259+
260+
// If Stetho debugger is available (chrome debugger is open), intercept the response headers
261+
stethoEventReporter.responseHeadersReceived(
262+
new ParseInspectorHttpResponse(requestId, request, response));
263+
264+
InputStream responseStream = null;
265+
if (response.getContent() != null) {
266+
responseStream = response.getContent();
267+
}
268+
269+
// Create the Stetho proxy inputStream, when Parse read this stream, it will proxy the
270+
// response body to Stetho reporter.
271+
responseStream = stethoEventReporter.interpretResponseStream(
272+
requestId,
273+
response.getContentType(),
274+
response.getAllHeaders().get("Content-Encoding"),
275+
responseStream,
276+
new DefaultResponseHandler(stethoEventReporter, requestId)
277+
);
278+
if (responseStream != null) {
279+
response = response.newBuilder()
280+
.setContent(responseStream)
281+
.build();
282+
}
283+
}
284+
return response;
285+
}
286+
}

0 commit comments

Comments
 (0)