23
23
24
24
package com .algolia .search .saas ;
25
25
26
- import org .apache .http .HttpEntity ;
27
- import org .apache .http .HttpResponse ;
28
- import org .apache .http .client .methods .HttpDelete ;
29
- import org .apache .http .client .methods .HttpEntityEnclosingRequestBase ;
30
- import org .apache .http .client .methods .HttpGet ;
31
- import org .apache .http .client .methods .HttpPost ;
32
- import org .apache .http .client .methods .HttpPut ;
33
- import org .apache .http .client .methods .HttpRequestBase ;
34
26
import org .apache .http .entity .StringEntity ;
35
- import org .apache .http .impl .client .DefaultHttpClient ;
36
27
import org .apache .http .message .BasicHeader ;
37
28
import org .apache .http .protocol .HTTP ;
38
- import org .apache .http .util .EntityUtils ;
39
29
import org .json .JSONArray ;
40
30
import org .json .JSONException ;
41
31
import org .json .JSONObject ;
45
35
import java .io .InputStream ;
46
36
import java .io .InputStreamReader ;
47
37
import java .io .UnsupportedEncodingException ;
48
- import java .net .URI ;
49
- import java .net .URISyntaxException ;
38
+ import java .net .HttpURLConnection ;
39
+ import java .net .URL ;
50
40
import java .net .URLEncoder ;
51
41
import java .util .Arrays ;
52
42
import java .util .HashMap ;
@@ -68,7 +58,6 @@ abstract class BaseAPIClient {
68
58
private final String apiKey ;
69
59
private final List <String > readHostsArray ;
70
60
private final List <String > writeHostsArray ;
71
- private final DefaultHttpClient httpClient ;
72
61
private String tagFilters ;
73
62
private String userToken ;
74
63
private HashMap <String , String > headers ;
@@ -102,7 +91,6 @@ protected BaseAPIClient(String applicationID, String apiKey, List<String> hostsA
102
91
} else {
103
92
readHostsArray = writeHostsArray = hostsArray ;
104
93
}
105
- httpClient = new DefaultHttpClient ();
106
94
headers = new HashMap <String , String >();
107
95
}
108
96
@@ -329,7 +317,7 @@ protected JSONObject putRequest(String url, String obj) throws AlgoliaException
329
317
return _request (Method .PUT , url , obj , writeHostsArray , httpConnectTimeoutMS , httpSocketTimeoutMS );
330
318
}
331
319
332
- private JSONObject _getAnswerObject (InputStream istream ) throws IOException , JSONException {
320
+ private String _getAnswer (InputStream istream ) throws IOException {
333
321
InputStreamReader is = new InputStreamReader (istream , "UTF-8" );
334
322
StringBuilder builder = new StringBuilder ();
335
323
char [] buf = new char [1000 ];
@@ -338,121 +326,144 @@ private JSONObject _getAnswerObject(InputStream istream) throws IOException, JSO
338
326
builder .append (buf , 0 , l );
339
327
l = is .read (buf );
340
328
}
341
- JSONTokener tokener = new JSONTokener (builder .toString ());
342
- JSONObject res = new JSONObject (tokener );
343
329
is .close ();
344
- return res ;
330
+ return builder .toString ();
331
+ }
332
+
333
+ private JSONObject _getJSONObject (String input ) throws JSONException {
334
+ return new JSONObject (new JSONTokener (input ));
335
+ }
336
+
337
+ private JSONObject _getAnswerObject (InputStream istream ) throws IOException , JSONException {
338
+ return _getJSONObject (_getAnswer (istream ));
345
339
}
346
340
347
341
private synchronized JSONObject _request (Method m , String url , String json , List <String > hostsArray , int connectTimeout , int readTimeout ) throws AlgoliaException {
348
- HttpRequestBase req ;
342
+ String requestMethod ;
349
343
HashMap <String , String > errors = new HashMap <String , String >();
350
344
// for each host
351
345
for (String host : hostsArray ) {
352
346
switch (m ) {
353
347
case DELETE :
354
- req = new HttpDelete () ;
348
+ requestMethod = "DELETE" ;
355
349
break ;
356
350
case GET :
357
- req = new HttpGet () ;
351
+ requestMethod = "GET" ;
358
352
break ;
359
353
case POST :
360
- req = new HttpPost () ;
354
+ requestMethod = "POST" ;
361
355
break ;
362
356
case PUT :
363
- req = new HttpPut () ;
357
+ requestMethod = "PUT" ;
364
358
break ;
365
359
default :
366
360
throw new IllegalArgumentException ("Method " + m + " is not supported" );
367
361
}
368
362
369
363
// set URL
370
- try {
371
- req .setURI (new URI ("https://" + host + url ));
372
- } catch (URISyntaxException e ) {
373
- // never reached
374
- throw new IllegalStateException (e );
364
+ URL hostURL ;
365
+ HttpURLConnection hostConnection ;
366
+ try {
367
+ hostURL = new URL ("https://" + host + url );
368
+ hostConnection = (HttpURLConnection ) hostURL .openConnection ();
369
+ hostConnection .setRequestMethod (requestMethod );
370
+ } catch (IOException e ) {
371
+ // on error continue on the next host
372
+ addError (errors , host , e );
373
+ continue ;
375
374
}
376
375
376
+ hostConnection .setConnectTimeout (connectTimeout );
377
+ hostConnection .setReadTimeout (readTimeout );
378
+
377
379
// set auth headers
378
- req . setHeader ("X-Algolia-Application-Id" , this .applicationID );
379
- req . setHeader ("X-Algolia-API-Key" , this .apiKey );
380
+ hostConnection . setRequestProperty ("X-Algolia-Application-Id" , this .applicationID );
381
+ hostConnection . setRequestProperty ("X-Algolia-API-Key" , this .apiKey );
380
382
for (Map .Entry <String , String > entry : headers .entrySet ()) {
381
- req . setHeader (entry .getKey (), entry .getValue ());
383
+ hostConnection . setRequestProperty (entry .getKey (), entry .getValue ());
382
384
}
383
385
384
386
// set user agent
385
- req .setHeader ("User-Agent" , "Algolia for Android " + version );
387
+ hostConnection .setRequestProperty ("User-Agent" , "Algolia for Android " + version );
388
+
386
389
387
390
// set optional headers
388
391
if (this .userToken != null ) {
389
- req . setHeader ("X-Algolia-UserToken" , this .userToken );
392
+ hostConnection . setRequestProperty ("X-Algolia-UserToken" , this .userToken );
390
393
}
391
394
if (this .tagFilters != null ) {
392
- req . setHeader ("X-Algolia-TagFilters" , this .tagFilters );
395
+ hostConnection . setRequestProperty ("X-Algolia-TagFilters" , this .tagFilters );
393
396
}
394
- req .addHeader ("Accept-Encoding" ,"gzip" );
395
397
396
- // set JSON entity
398
+ // write JSON entity
397
399
if (json != null ) {
398
- if (!(req instanceof HttpEntityEnclosingRequestBase )) {
400
+ if (!(requestMethod . equals ( "PUT" ) || requestMethod . equals ( "POST" ) )) {
399
401
throw new IllegalArgumentException ("Method " + m + " cannot enclose entity" );
400
402
}
401
- req .setHeader ("Content-type" , "application/json" );
403
+ hostConnection .setRequestProperty ("Content-type" , "application/json" );
404
+ hostConnection .setDoOutput (true );
402
405
try {
403
406
StringEntity se = new StringEntity (json , "UTF-8" );
404
407
se .setContentEncoding (new BasicHeader (HTTP .CONTENT_TYPE , "application/json" ));
405
- (( HttpEntityEnclosingRequestBase ) req ). setEntity ( se );
408
+ se . writeTo ( hostConnection . getOutputStream () );
406
409
} catch (UnsupportedEncodingException e ) {
407
410
throw new AlgoliaException ("Invalid JSON Object: " + json );
411
+ } catch (IOException e ) {
412
+ throw new AlgoliaException ("Could not open output stream: " + e .getLocalizedMessage ());
408
413
}
409
414
}
410
415
411
- httpClient .getParams ().setParameter ("http.socket.timeout" , readTimeout );
412
- httpClient .getParams ().setParameter ("http.connection.timeout" , connectTimeout );
413
-
414
- HttpResponse response ;
416
+ int code ;
415
417
try {
416
- response = httpClient . execute ( req );
418
+ code = hostConnection . getResponseCode ( );
417
419
} catch (IOException e ) {
418
420
// on error continue on the next host
419
- errors . put ( host , String . format ( "%s=%s" , e . getClass (). getName (), e . getMessage ()) );
421
+ addError ( errors , host , e );
420
422
continue ;
421
423
}
422
- int code = response .getStatusLine ().getStatusCode ();
424
+
425
+ InputStream stream = hostConnection .getErrorStream (); // Response is in ErrorStream unless code = 200
423
426
if (code / 100 == 2 ) {
424
427
// OK
428
+ try {
429
+ stream = hostConnection .getInputStream ();
430
+ } catch (IOException e ) {
431
+ throw new AlgoliaException ("Could not open input stream: " + e .getLocalizedMessage ());
432
+ }
425
433
} else if (code / 100 == 4 ) {
426
434
String message = "Error detected in backend" ;
427
435
try {
428
- message = _getAnswerObject (response . getEntity (). getContent () ).getString ("message" );
436
+ message = _getAnswerObject (stream ).getString ("message" );
429
437
} catch (IOException e ) {
438
+ addError (errors , host , e );
430
439
continue ;
431
440
} catch (JSONException e ) {
432
441
throw new AlgoliaException ("JSON decode error:" + e .getMessage ());
433
442
}
434
- consumeQuietly (response . getEntity () );
443
+ consumeQuietly (hostConnection );
435
444
throw new AlgoliaException (message );
436
445
} else {
437
446
try {
438
- errors .put (host , EntityUtils . toString ( response . getEntity () ));
447
+ errors .put (host , _getAnswer ( stream ));
439
448
} catch (IOException e ) {
440
449
errors .put (host , String .valueOf (code ));
441
450
}
442
- consumeQuietly (response . getEntity () );
451
+ consumeQuietly (hostConnection );
443
452
// KO, continue
444
453
continue ;
445
454
}
446
455
try {
447
- String encoding = response . getEntity (). getContentEncoding () != null ? response . getEntity (). getContentEncoding (). getValue () : null ;
448
- if (encoding != null && encoding .contains ("gzip" ))
449
- return _getAnswerObject (new GZIPInputStream (response . getEntity (). getContent () ));
450
- else
451
- return _getAnswerObject ( response . getEntity (). getContent ());
452
- } catch ( IOException e ) {
453
- continue ;
456
+ String encoding = hostConnection . getContentEncoding ();
457
+ if (encoding != null && encoding .contains ("gzip" )) {
458
+ return _getAnswerObject (new GZIPInputStream (stream ));
459
+ }
460
+ else {
461
+ return _getAnswerObject ( stream );
462
+ }
454
463
} catch (JSONException e ) {
455
464
throw new AlgoliaException ("JSON decode error:" + e .getMessage ());
465
+ } catch (IOException e ) {
466
+ throw new AlgoliaException ("Data decoding error:" + e .getMessage ());
456
467
}
457
468
}
458
469
StringBuilder builder = new StringBuilder ("Hosts unreachable: " );
@@ -467,23 +478,29 @@ private synchronized JSONObject _request(Method m, String url, String json, List
467
478
throw new AlgoliaException (builder .toString ());
468
479
}
469
480
481
+ private void addError (HashMap <String , String > errors , String host , IOException e ) {
482
+ errors .put (host , String .format ("%s=%s" , e .getClass ().getName (), e .getMessage ()));
483
+ }
484
+
470
485
/**
471
486
* Ensures that the entity content is fully consumed and the content stream, if exists,
472
487
* is closed.
473
488
*/
474
- private void consumeQuietly (final HttpEntity entity ) {
475
- if (entity == null ) {
476
- return ;
477
- }
489
+ private void consumeQuietly (final HttpURLConnection connection ) {
478
490
try {
479
- if (entity .isStreaming ()) {
480
- InputStream instream = entity .getContent ();
481
- if (instream != null ) {
482
- instream .close ();
483
- }
491
+ int read = 0 ;
492
+ while (read != -1 ) {
493
+ read = connection .getInputStream ().read ();
494
+ }
495
+ connection .getInputStream ().close ();
496
+ read = 0 ;
497
+ while (read != -1 ) {
498
+ read = connection .getErrorStream ().read ();
484
499
}
500
+ connection .getErrorStream ().close ();
501
+ connection .disconnect ();
485
502
} catch (IOException e ) {
486
- // not fatal
503
+ // no inputStream to close
487
504
}
488
505
}
489
506
}
0 commit comments