17
17
import java .net .HttpURLConnection ;
18
18
import java .net .InetAddress ;
19
19
import java .net .URL ;
20
+ import java .nio .charset .StandardCharsets ;
20
21
import java .util .Map ;
22
+ import java .util .Objects ;
23
+ import java .util .concurrent .TimeUnit ;
21
24
import java .util .zip .GZIPOutputStream ;
22
25
23
26
import javax .net .ssl .HttpsURLConnection ;
@@ -30,28 +33,33 @@ public class HttpService implements RemoteService {
30
33
31
34
32
35
private final boolean shouldGzipRequestPayload ;
36
+ private final MixpanelNetworkErrorListener networkErrorListener ;
33
37
34
38
private static boolean sIsMixpanelBlocked ;
35
39
private static final int MIN_UNAVAILABLE_HTTP_RESPONSE_CODE = HttpURLConnection .HTTP_INTERNAL_ERROR ;
36
40
private static final int MAX_UNAVAILABLE_HTTP_RESPONSE_CODE = 599 ;
37
41
38
- public HttpService (boolean shouldGzipRequestPayload ) {
42
+ public HttpService (boolean shouldGzipRequestPayload , MixpanelNetworkErrorListener networkErrorListener ) {
39
43
this .shouldGzipRequestPayload = shouldGzipRequestPayload ;
44
+ this .networkErrorListener = networkErrorListener ;
40
45
}
41
46
42
47
public HttpService () {
43
- this (false );
48
+ this (false , null );
44
49
}
45
50
@ Override
46
51
public void checkIsMixpanelBlocked () {
47
52
Thread t = new Thread (new Runnable () {
48
53
public void run () {
49
54
try {
50
- InetAddress apiMixpanelInet = InetAddress .getByName ("api.mixpanel.com" );
55
+ long startTimeNanos = System .nanoTime ();
56
+ String host = "api.mixpanel.com" ;
57
+ InetAddress apiMixpanelInet = InetAddress .getByName (host );
51
58
sIsMixpanelBlocked = apiMixpanelInet .isLoopbackAddress () ||
52
59
apiMixpanelInet .isAnyLocalAddress ();
53
60
if (sIsMixpanelBlocked ) {
54
61
MPLog .v (LOGTAG , "AdBlocker is enabled. Won't be able to use Mixpanel services." );
62
+ onNetworkError (null , host , apiMixpanelInet .getHostAddress (), startTimeNanos , -1 , -1 , new IOException (host + " is blocked" ));
55
63
}
56
64
} catch (Exception e ) {
57
65
}
@@ -115,11 +123,18 @@ public byte[] performRequest(String endpointUrl, ProxyServerInteractor interacto
115
123
while (retries < 3 && !succeeded ) {
116
124
InputStream in = null ;
117
125
OutputStream out = null ;
118
- OutputStream bout = null ;
119
126
HttpURLConnection connection = null ;
120
127
128
+ String targetIpAddress = null ;
129
+ long startTimeNanos = System .nanoTime ();
130
+ long uncompressedBodySize = -1 ;
131
+ long compressedBodySize = -1 ;
132
+
121
133
try {
122
134
final URL url = new URL (endpointUrl );
135
+
136
+ InetAddress inetAddress = InetAddress .getByName (url .getHost ());
137
+ targetIpAddress = inetAddress .getHostAddress ();
123
138
connection = (HttpURLConnection ) url .openConnection ();
124
139
if (null != socketFactory && connection instanceof HttpsURLConnection ) {
125
140
((HttpsURLConnection ) connection ).setSSLSocketFactory (socketFactory );
@@ -136,25 +151,35 @@ public byte[] performRequest(String endpointUrl, ProxyServerInteractor interacto
136
151
137
152
connection .setConnectTimeout (2000 );
138
153
connection .setReadTimeout (30000 );
154
+
155
+ byte [] bodyBytesToSend = null ;
139
156
if (null != params ) {
140
157
Uri .Builder builder = new Uri .Builder ();
141
158
for (Map .Entry <String , Object > param : params .entrySet ()) {
142
159
builder .appendQueryParameter (param .getKey (), param .getValue ().toString ());
143
160
}
144
161
String query = builder .build ().getEncodedQuery ();
162
+ byte [] originalBodyBytes = Objects .requireNonNull (query ).getBytes (StandardCharsets .UTF_8 );
163
+ uncompressedBodySize = originalBodyBytes .length ;
164
+
145
165
if (shouldGzipRequestPayload ) {
166
+ ByteArrayOutputStream baos = new ByteArrayOutputStream ();
167
+ try (GZIPOutputStream gzipOut = new GZIPOutputStream (baos )) {
168
+ gzipOut .write (originalBodyBytes );
169
+ }
170
+ bodyBytesToSend = baos .toByteArray ();
171
+ compressedBodySize = bodyBytesToSend .length ;
146
172
connection .setRequestProperty (CONTENT_ENCODING_HEADER , GZIP_CONTENT_TYPE_HEADER );
173
+ connection .setFixedLengthStreamingMode (compressedBodySize );
147
174
} else {
148
- connection .setFixedLengthStreamingMode (query .getBytes ().length );
175
+ bodyBytesToSend = originalBodyBytes ;
176
+ connection .setFixedLengthStreamingMode (uncompressedBodySize );
149
177
}
150
178
connection .setDoOutput (true );
151
179
connection .setRequestMethod ("POST" );
152
180
out = connection .getOutputStream ();
153
- bout = getBufferedOutputStream (out );
154
- bout .write (query .getBytes ("UTF-8" ));
155
- bout .flush ();
156
- bout .close ();
157
- bout = null ;
181
+ out .write (bodyBytesToSend );
182
+ out .flush ();
158
183
out .close ();
159
184
out = null ;
160
185
}
@@ -167,18 +192,21 @@ public byte[] performRequest(String endpointUrl, ProxyServerInteractor interacto
167
192
in = null ;
168
193
succeeded = true ;
169
194
} catch (final EOFException e ) {
195
+ onNetworkError (connection , endpointUrl , targetIpAddress , startTimeNanos , uncompressedBodySize , compressedBodySize , e );
170
196
MPLog .d (LOGTAG , "Failure to connect, likely caused by a known issue with Android lib. Retrying." );
171
197
retries = retries + 1 ;
172
198
} catch (final IOException e ) {
199
+ onNetworkError (connection , endpointUrl , targetIpAddress , startTimeNanos , uncompressedBodySize , compressedBodySize , e );
173
200
if (connection != null && connection .getResponseCode () >= MIN_UNAVAILABLE_HTTP_RESPONSE_CODE && connection .getResponseCode () <= MAX_UNAVAILABLE_HTTP_RESPONSE_CODE ) {
174
201
throw new ServiceUnavailableException ("Service Unavailable" , connection .getHeaderField ("Retry-After" ));
175
202
} else {
176
203
throw e ;
177
204
}
205
+ } catch (final Exception e ) {
206
+ onNetworkError (connection , endpointUrl , targetIpAddress , startTimeNanos , uncompressedBodySize , compressedBodySize , e );
207
+ throw e ;
178
208
}
179
209
finally {
180
- if (null != bout )
181
- try { bout .close (); } catch (final IOException e ) {}
182
210
if (null != out )
183
211
try { out .close (); } catch (final IOException e ) {}
184
212
if (null != in )
@@ -193,6 +221,25 @@ public byte[] performRequest(String endpointUrl, ProxyServerInteractor interacto
193
221
return response ;
194
222
}
195
223
224
+ private void onNetworkError (HttpURLConnection connection , String endpointUrl , String targetIpAddress , long startTimeNanos , long uncompressedBodySize , long compressedBodySize , Exception e ) {
225
+ if (this .networkErrorListener != null ) {
226
+ long endTimeNanos = System .nanoTime ();
227
+ long durationMillis = TimeUnit .NANOSECONDS .toMillis (endTimeNanos - startTimeNanos );
228
+ int responseCode = -1 ;
229
+ String responseMessage = "" ;
230
+ if (connection != null ) {
231
+ try {
232
+ responseCode = connection .getResponseCode ();
233
+ responseMessage = connection .getResponseMessage ();
234
+ } catch (Exception respExc ) {
235
+ MPLog .w (LOGTAG , "Could not retrieve response code/message after error" , respExc );
236
+ }
237
+ }
238
+ String ip = (targetIpAddress == null ) ? "N/A" : targetIpAddress ;
239
+ this .networkErrorListener .onNetworkError (endpointUrl , ip , durationMillis , uncompressedBodySize , compressedBodySize , responseCode , responseMessage , e );
240
+ }
241
+ }
242
+
196
243
private OutputStream getBufferedOutputStream (OutputStream out ) throws IOException {
197
244
if (shouldGzipRequestPayload ) {
198
245
return new GZIPOutputStream (new BufferedOutputStream (out ), HTTP_OUTPUT_STREAM_BUFFER_SIZE );
@@ -225,3 +272,4 @@ private static byte[] slurp(final InputStream inputStream)
225
272
private static final String CONTENT_ENCODING_HEADER = "Content-Encoding" ;
226
273
private static final String GZIP_CONTENT_TYPE_HEADER = "gzip" ;
227
274
}
275
+
0 commit comments