algoliasearch/src/main/java/com/algolia/search/saas/AbstractClient.java
private byte[] _requestRaw(@NonNull Method m, @NonNull String url, @Nullable Map<String, String> urlParameters, @Nullable String json, @NonNull List<String> hostsArray, int connectTimeout, int readTimeout, @Nullable RequestOptions requestOptions) throws AlgoliaException {
        String requestMethod;
        List<Exception> errors = new ArrayList<>(hostsArray.size());
        // for each host
        for (String host : hostsArray) {
            switch (m) {
                case DELETE:
                    requestMethod = "DELETE";
                    break;
                case GET:
                    requestMethod = "GET";
                    break;
                case POST:
                    requestMethod = "POST";
                    break;
                case PUT:
                    requestMethod = "PUT";
                    break;
                default:
                    throw new IllegalArgumentException("Method " + m + " is not supported");
            }

            InputStream stream = null;
            HttpURLConnection hostConnection = null;
            try {
                // Compute final URL parameters.
                final Map<String, String> parameters = new HashMap<>();
                if (urlParameters != null) {
                    parameters.putAll(urlParameters);
                }
                if (requestOptions != null) {
                    parameters.putAll(requestOptions.urlParameters);
                }

                // Build URL.
                String urlString = "https://" + host + url;
                if (!parameters.isEmpty()) {
                    urlString += "?" + AbstractQuery.build(parameters);
                }
                URL hostURL = new URL(urlString);

                // Open connection.
                hostConnection = (HttpURLConnection) hostURL.openConnection();

                //set timeouts
                hostConnection.setRequestMethod(requestMethod);
                hostConnection.setConnectTimeout(connectTimeout);
                hostConnection.setReadTimeout(readTimeout);

                // Headers
                hostConnection.setRequestProperty("Accept-Encoding", "gzip");
                hostConnection.setRequestProperty("X-Algolia-Application-Id", this.applicationID);
                // If API key is too big, send it in the request's body (if applicable).
                if (this.apiKey != null && this.apiKey.length() > MAX_API_KEY_LENGTH && json != null) {
                    try {
                        final JSONObject body = new JSONObject(json);
                        body.put("apiKey", this.apiKey);
                        json = body.toString();
                    } catch (JSONException e) {
                        throw new AlgoliaException("Failed to patch JSON body");
                    }
                } else {
                    hostConnection.setRequestProperty("X-Algolia-API-Key", this.apiKey);
                }
                // Client-level headers
                for (Map.Entry<String, String> entry : this.headers.entrySet()) {
                    hostConnection.setRequestProperty(entry.getKey(), entry.getValue());
                }
                // Request-level headers
                if (requestOptions != null) {
                    for (Map.Entry<String, String> entry : requestOptions.headers.entrySet()) {
                        hostConnection.setRequestProperty(entry.getKey(), entry.getValue());
                    }
                }

                // set user agent
                hostConnection.setRequestProperty("User-Agent", userAgentRaw);

                // write JSON entity
                if (json != null) {
                    if (!(requestMethod.equals("PUT") || requestMethod.equals("POST"))) {
                        throw new IllegalArgumentException("Method " + m + " cannot enclose entity");
                    }
                    hostConnection.setRequestProperty("Content-type", "application/json; charset=UTF-8");
                    hostConnection.setDoOutput(true);
                    OutputStreamWriter writer = new OutputStreamWriter(hostConnection.getOutputStream(), "UTF-8");
                    writer.write(json);
                    writer.close();
                }

                // read response
                int code = hostConnection.getResponseCode();
                final boolean codeIsError = code / 100 != 2;
                stream = codeIsError ? hostConnection.getErrorStream() : hostConnection.getInputStream();
                // As per the official Java docs (not the Android docs):
                // - `getErrorStream()` may return null => we have to handle this case.
                //   See <https://docs.oracle.com/javase/7/docs/api/java/net/HttpURLConnection.html#getErrorStream()>.
                // - `getInputStream()` should never return null... but let's err on the side of caution.
                //   See <https://docs.oracle.com/javase/7/docs/api/java/net/URLConnection.html#getInputStream()>.
                if (stream == null) {
                    throw new IOException(String.format("Null stream when reading connection (status %d)", code));
                }
                hostStatuses.put(host, new HostStatus(true));

                final byte[] rawResponse;
                String encoding = hostConnection.getContentEncoding();
                if (encoding != null && encoding.equals("gzip")) {
                    rawResponse = _toByteArray(new GZIPInputStream(stream));
                } else {
                    rawResponse = _toByteArray(stream);
                }

                // handle http errors
                if (codeIsError) {
                    if (code / 100 == 4) {
                        consumeQuietly(hostConnection);
                        throw new AlgoliaException(_getJSONObject(rawResponse).getString("message"), code);
                    } else {
                        consumeQuietly(hostConnection);
                        errors.add(new AlgoliaException(_toCharArray(stream), code));
                        continue;
                    }
                }
                return rawResponse;

            } catch (JSONException e) { // fatal
                consumeQuietly(hostConnection);
                throw new AlgoliaException("Invalid JSON returned by server", e);
            } catch (UnsupportedEncodingException e) { // fatal
                consumeQuietly(hostConnection);
                throw new AlgoliaException("Invalid encoding returned by server", e);
            } catch (IOException e) { // host error, continue on the next host
                hostStatuses.put(host, new HostStatus(false));
                consumeQuietly(hostConnection);
                errors.add(e);
            } finally {
                if (stream != null) {
                    try {
                        stream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

        String errorMessage = "All hosts failed: " + Arrays.toString(errors.toArray());
        // When several errors occurred, use the last one as the cause for the returned exception.
        Throwable lastError = errors.get(errors.size() - 1);
        throw new AlgoliaException(errorMessage, lastError);
    }
private byte[] _requestRaw(@NonNull Method m, @NonNull String url, @Nullable Map<String, String> urlParameters, @Nullable String json, @NonNull List<String> hostsArray, int connectTimeout, int readTimeout, @Nullable RequestOptions requestOptions) throws AlgoliaException {
        String requestMethod;
        List<Exception> errors = new ArrayList<>(hostsArray.size());
        // for each host
        for (String host : hostsArray) {
            switch (m) {
                case DELETE:
                    requestMethod = "DELETE";
                    break;
                case GET:
                    requestMethod = "GET";
                    break;
                case POST:
                    requestMethod = "POST";
                    break;
                case PUT:
                    requestMethod = "PUT";
                    break;
                default:
                    throw new IllegalArgumentException("Method " + m + " is not supported");
            }

            InputStream stream = null;
            HttpURLConnection hostConnection = null;
            try {
                // Compute final URL parameters.
                final Map<String, String> parameters = new HashMap<>();
                if (urlParameters != null) {
                    parameters.putAll(urlParameters);
                }
                if (requestOptions != null) {
                    parameters.putAll(requestOptions.urlParameters);
                }

                // Build URL.
                String urlString = "https://" + host + url;
                if (!parameters.isEmpty()) {
                    urlString += "?" + AbstractQuery.build(parameters);
                }
                URL hostURL = new URL(urlString);

                // Open connection.
                hostConnection = (HttpURLConnection) hostURL.openConnection();

                //set timeouts
                hostConnection.setRequestMethod(requestMethod);
                hostConnection.setConnectTimeout(connectTimeout);
                hostConnection.setReadTimeout(readTimeout);

                // Headers
                hostConnection.setRequestProperty("Accept-Encoding", "gzip");
                hostConnection.setRequestProperty("X-Algolia-Application-Id", this.applicationID);
                // If API key is too big, send it in the request's body (if applicable).
                if (this.apiKey != null && this.apiKey.length() > MAX_API_KEY_LENGTH && json != null) {
                    try {
                        final JSONObject body = new JSONObject(json);
                        body.put("apiKey", this.apiKey);
                        json = body.toString();
                    } catch (JSONException e) {
                        throw new AlgoliaException("Failed to patch JSON body");
                    }
                } else {
                    hostConnection.setRequestProperty("X-Algolia-API-Key", this.apiKey);
                }
                // Client-level headers
                for (Map.Entry<String, String> entry : this.headers.entrySet()) {
                    hostConnection.setRequestProperty(entry.getKey(), entry.getValue());
                }
                // Request-level headers
                if (requestOptions != null) {
                    for (Map.Entry<String, String> entry : requestOptions.headers.entrySet()) {
                        hostConnection.setRequestProperty(entry.getKey(), entry.getValue());
                    }
                }

                // set user agent
                hostConnection.setRequestProperty("User-Agent", userAgentRaw);

                // write JSON entity
                if (json != null) {
                    if (!(requestMethod.equals("PUT") || requestMethod.equals("POST"))) {
                        throw new IllegalArgumentException("Method " + m + " cannot enclose entity");
                    }
                    hostConnection.setRequestProperty("Content-type", "application/json; charset=UTF-8");
                    hostConnection.setDoOutput(true);
                    OutputStreamWriter writer = new OutputStreamWriter(hostConnection.getOutputStream(), "UTF-8");
                    writer.write(json);
                    writer.close();
                }

                // read response
                int code = hostConnection.getResponseCode();
                final boolean codeIsError = code / 100 != 2;
                stream = codeIsError ? hostConnection.getErrorStream() : hostConnection.getInputStream();
                // As per the official Java docs (not the Android docs):
                // - `getErrorStream()` may return null => we have to handle this case.
                //   See <https://docs.oracle.com/javase/7/docs/api/java/net/HttpURLConnection.html#getErrorStream()>.
                // - `getInputStream()` should never return null... but let's err on the side of caution.
                //   See <https://docs.oracle.com/javase/7/docs/api/java/net/URLConnection.html#getInputStream()>.
                if (stream == null) {
                    throw new IOException(String.format("Null stream when reading connection (status %d)", code));
                }
                hostStatuses.put(host, new HostStatus(true));

                final byte[] rawResponse;
                String encoding = hostConnection.getContentEncoding();
                if (encoding != null && encoding.equals("gzip")) {
                    rawResponse = _toByteArray(new GZIPInputStream(stream));
                } else {
                    rawResponse = _toByteArray(stream);
                }

                // handle http errors
                if (codeIsError) {
                    if (code / 100 == 4) {
                        consumeQuietly(hostConnection);
                        throw new AlgoliaException(_getJSONObject(rawResponse).getString("message"), code);
                    } else {
                        consumeQuietly(hostConnection);
                        errors.add(new AlgoliaException(_toCharArray(stream), code));
                        continue;
                    }
                }
                return rawResponse;

            } catch (JSONException e) { // fatal
                consumeQuietly(hostConnection);
                throw new AlgoliaException("Invalid JSON returned by server", e);
            } catch (UnsupportedEncodingException e) { // fatal
                consumeQuietly(hostConnection);
                throw new AlgoliaException("Invalid encoding returned by server", e);
            } catch (IOException e) { // host error, continue on the next host
                hostStatuses.put(host, new HostStatus(false));
                consumeQuietly(hostConnection);
                errors.add(e);
            } finally {
                if (stream != null) {
                    try {
                        stream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

        String errorMessage = "All hosts failed: " + Arrays.toString(errors.toArray());
        // When several errors occurred, use the last one as the cause for the returned exception.
        Throwable lastError = errors.get(errors.size() - 1);
        throw new AlgoliaException(errorMessage, lastError);
    }
Send the query according to parameters and returns its result as a JSONObject

@param m              HTTP Method to use
@param url            Endpoint URL, *without query string*. The query string is handled by `urlParameters`.
@param urlParameters  URL parameters
@param json           (optional) JSON body
@param hostsArray     array of hosts to try successively
@param connectTimeout maximum wait time to open connection
@param readTimeout    maximum time to read data on socket
@return a JSONObject containing the resulting data or error
@throws AlgoliaException in case of connection or data handling error