package io.searchbox.client.http; import com.google.gson.Gson; import io.searchbox.action.Action; import io.searchbox.client.AbstractJestClient; import io.searchbox.client.JestResult; import io.searchbox.client.JestResultHandler; import io.searchbox.client.config.exception.CouldNotConnectException; import io.searchbox.client.http.apache.HttpDeleteWithEntity; import io.searchbox.client.http.apache.HttpGetWithEntity; import org.apache.http.Header; import org.apache.http.HttpEntityEnclosingRequest; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.entity.EntityBuilder; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpHead; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.protocol.HttpClientContext; import org.apache.http.concurrent.FutureCallback; import org.apache.http.conn.HttpHostConnectException; import org.apache.http.entity.ContentType; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.nio.client.CloseableHttpAsyncClient; import org.apache.http.util.EntityUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.Map.Entry; import java.util.concurrent.Future; /** * @author Dogukan Sonmez * @author cihat keser */ public class JestHttpClient extends AbstractJestClient { private final static Logger log = LoggerFactory.getLogger(JestHttpClient.class); protected ContentType requestContentType = ContentType.APPLICATION_JSON.withCharset("utf-8"); private CloseableHttpClient httpClient; private CloseableHttpAsyncClient asyncClient; private HttpClientContext httpClientContextTemplate; /** * @throws IOException in case of a problem or the connection was aborted during request, * or in case of a problem while reading the response stream * @throws CouldNotConnectException if an {@link HttpHostConnectException} is encountered */ @Override public <T extends JestResult> T execute(Action<T> clientRequest) throws IOException { return execute(clientRequest, null); } public <T extends JestResult> T execute(Action<T> clientRequest, RequestConfig requestConfig) throws IOException { HttpUriRequest request = prepareRequest(clientRequest, requestConfig); CloseableHttpResponse response = null; try { response = executeRequest(request); return deserializeResponse(response, request, clientRequest); } catch (HttpHostConnectException ex) { throw new CouldNotConnectException(ex.getHost().toURI(), ex); } finally { if (response != null) { try { response.close(); } catch (IOException ex) { log.error("Exception occurred while closing response stream.", ex); } } } } @Override public <T extends JestResult> void executeAsync(final Action<T> clientRequest, final JestResultHandler<? super T> resultHandler) { executeAsync(clientRequest, resultHandler, null); } public <T extends JestResult> void executeAsync(final Action<T> clientRequest, final JestResultHandler<? super T> resultHandler, final RequestConfig requestConfig) { synchronized (this) { if (!asyncClient.isRunning()) { asyncClient.start(); } } HttpUriRequest request = prepareRequest(clientRequest, requestConfig); executeAsyncRequest(clientRequest, resultHandler, request); } @Override public void shutdownClient() { super.shutdownClient(); try { asyncClient.close(); } catch (IOException ex) { log.error("Exception occurred while shutting down the async client.", ex); } try { httpClient.close(); } catch (IOException ex) { log.error("Exception occurred while shutting down the sync client.", ex); } } protected <T extends JestResult> HttpUriRequest prepareRequest(final Action<T> clientRequest, final RequestConfig requestConfig) { String elasticSearchRestUrl = getRequestURL(getNextServer(), clientRequest.getURI()); HttpUriRequest request = constructHttpMethod(clientRequest.getRestMethodName(), elasticSearchRestUrl, clientRequest.getData(gson), requestConfig); log.debug("Request method={} url={}", clientRequest.getRestMethodName(), elasticSearchRestUrl); // add headers added to action for (Entry<String, Object> header : clientRequest.getHeaders().entrySet()) { request.addHeader(header.getKey(), header.getValue().toString()); } return request; } protected CloseableHttpResponse executeRequest(HttpUriRequest request) throws IOException { if (httpClientContextTemplate != null) { return httpClient.execute(request, createContextInstance()); } return httpClient.execute(request); } protected <T extends JestResult> Future<HttpResponse> executeAsyncRequest(Action<T> clientRequest, JestResultHandler<? super T> resultHandler, HttpUriRequest request) { if (httpClientContextTemplate != null) { return asyncClient.execute(request, createContextInstance(), new DefaultCallback<T>(clientRequest, request, resultHandler)); } return asyncClient.execute(request, new DefaultCallback<T>(clientRequest, request, resultHandler)); } protected HttpClientContext createContextInstance() { HttpClientContext context = HttpClientContext.create(); context.setCredentialsProvider(httpClientContextTemplate.getCredentialsProvider()); context.setAuthCache(httpClientContextTemplate.getAuthCache()); return context; } protected HttpUriRequest constructHttpMethod(String methodName, String url, String payload, RequestConfig requestConfig) { HttpUriRequest httpUriRequest = null; if (methodName.equalsIgnoreCase("POST")) { httpUriRequest = new HttpPost(url); log.debug("POST method created based on client request"); } else if (methodName.equalsIgnoreCase("PUT")) { httpUriRequest = new HttpPut(url); log.debug("PUT method created based on client request"); } else if (methodName.equalsIgnoreCase("DELETE")) { httpUriRequest = new HttpDeleteWithEntity(url); log.debug("DELETE method created based on client request"); } else if (methodName.equalsIgnoreCase("GET")) { httpUriRequest = new HttpGetWithEntity(url); log.debug("GET method created based on client request"); } else if (methodName.equalsIgnoreCase("HEAD")) { httpUriRequest = new HttpHead(url); log.debug("HEAD method created based on client request"); } if (httpUriRequest instanceof HttpRequestBase && requestConfig != null) { ((HttpRequestBase) httpUriRequest).setConfig(requestConfig); } if (httpUriRequest != null && httpUriRequest instanceof HttpEntityEnclosingRequest && payload != null) { EntityBuilder entityBuilder = EntityBuilder.create() .setText(payload) .setContentType(requestContentType); if (isRequestCompressionEnabled()) { entityBuilder.gzipCompress(); } ((HttpEntityEnclosingRequest) httpUriRequest).setEntity(entityBuilder.build()); } return httpUriRequest; } private <T extends JestResult> T deserializeResponse(HttpResponse response, final HttpRequest httpRequest, Action<T> clientRequest) throws IOException { StatusLine statusLine = response.getStatusLine(); try { return clientRequest.createNewElasticSearchResult( response.getEntity() == null ? null : EntityUtils.toString(response.getEntity()), statusLine.getStatusCode(), statusLine.getReasonPhrase(), gson ); } catch (com.google.gson.JsonSyntaxException e) { for (Header header : response.getHeaders("Content-Type")) { final String mimeType = header.getValue(); if (!mimeType.startsWith("application/json")) { // probably a proxy that responded in text/html final String message = "Request " + httpRequest.toString() + " yielded " + mimeType + ", should be json: " + statusLine.toString(); throw new IOException(message, e); } } throw e; } } public CloseableHttpClient getHttpClient() { return httpClient; } public void setHttpClient(CloseableHttpClient httpClient) { this.httpClient = httpClient; } public CloseableHttpAsyncClient getAsyncClient() { return asyncClient; } public void setAsyncClient(CloseableHttpAsyncClient asyncClient) { this.asyncClient = asyncClient; } public Gson getGson() { return gson; } public void setGson(Gson gson) { this.gson = gson; } public HttpClientContext getHttpClientContextTemplate() { return httpClientContextTemplate; } public void setHttpClientContextTemplate(HttpClientContext httpClientContext) { this.httpClientContextTemplate = httpClientContext; } protected class DefaultCallback<T extends JestResult> implements FutureCallback<HttpResponse> { private final Action<T> clientRequest; private final HttpRequest request; private final JestResultHandler<? super T> resultHandler; public DefaultCallback(Action<T> clientRequest, final HttpRequest request, JestResultHandler<? super T> resultHandler) { this.clientRequest = clientRequest; this.request = request; this.resultHandler = resultHandler; } @Override public void completed(final HttpResponse response) { T jestResult = null; try { jestResult = deserializeResponse(response, request, clientRequest); } catch (Exception e) { failed(e); } catch (Throwable t) { failed(new Exception("Problem during request processing", t)); } if (jestResult != null) resultHandler.completed(jestResult); } @Override public void failed(final Exception ex) { log.error("Exception occurred during async execution.", ex); if (ex instanceof HttpHostConnectException) { String host = ((HttpHostConnectException) ex).getHost().toURI(); resultHandler.failed(new CouldNotConnectException(host, ex)); return; } resultHandler.failed(ex); } @Override public void cancelled() { log.warn("Async execution was cancelled; this is not expected to occur under normal operation."); } } }