/**
* Copyright 2014 Yahoo! Inc. Licensed under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
* or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License. See accompanying
* LICENSE file.
*/
package com.yahoo.sql4d.sql4ddriver;
import java.io.IOException;
import java.util.Map;
import java.util.HashMap;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.http.HttpHost;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.DefaultProxyRoutePlanner;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.pool.PoolStats;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Base class for all druid types.
* TODO: Make the configuration malleable for pool.
* @author srikalyan
*/
public class DruidNodeAccessor {
private static final Logger log = LoggerFactory.getLogger(DruidNodeAccessor.class);
private static String PROXY_HOST;
private static int PROXY_PORT;
private static final PoolingHttpClientConnectionManager pool = new PoolingHttpClientConnectionManager();
private static DefaultProxyRoutePlanner customRouterPlanner = null;
public static Map<String, Integer> getConnectionPoolStats() {
Map<String, Integer> stats = new HashMap<>();
PoolStats poolStats = pool.getTotalStats();
stats.put("availableConnections", poolStats.getAvailable());
stats.put("maxConnections", poolStats.getMax());
stats.put("leasedConnections", poolStats.getLeased());
stats.put("pendingConnections", poolStats.getPending());
stats.put("defaultMaxPerRoute", pool.getDefaultMaxPerRoute());
return stats;
}
/**
* For each route we explicitly set max Connections.
* @param host
* @param port
* @param maxConnsPerRout
*/
public DruidNodeAccessor(String host, int port, int maxConnsPerRout) {
proxyInit();
if (host != null) {
HttpHost targetHost = new HttpHost(host, port);
pool.setMaxPerRoute(new HttpRoute(targetHost), maxConnsPerRout);
}
}
public static void setProxy(String h, int p) {
PROXY_HOST = h;
PROXY_PORT = p;
proxyInit();
}
public static void setMaxConnections(int max) {
pool.setMaxTotal(max);
}
public static void setDefaultMaxConnectionsPerRout(int max) {
pool.setDefaultMaxPerRoute(max);
}
/**
* To ensure customRouterPlanner is initialized only once.
*/
private static synchronized void proxyInit() {
if (PROXY_HOST != null && customRouterPlanner == null) {
HttpHost proxy = new HttpHost(PROXY_HOST, PROXY_PORT);
customRouterPlanner = new DefaultProxyRoutePlanner(proxy);
}
}
public CloseableHttpClient getClient() {
HttpClientBuilder builder = HttpClients.custom().setConnectionManager(pool);
return (customRouterPlanner != null)?builder.setRoutePlanner(customRouterPlanner).build():builder.build();
}
/**
* This ensures the response body is completely consumed and hence the HttpClient
* object will be return back to the pool successfully.
* @param resp
*/
public void returnClient(CloseableHttpResponse resp) {
try {
if (resp != null) {
EntityUtils.consume(resp.getEntity());
}
} catch (IOException ex) {
//TODO: Could not consume response completely. This is serious because the client will not be returned back to pool.
log.error("Error returning client to pool {}", ExceptionUtils.getStackTrace(ex));
}
}
public void shutdown() {
try {
pool.close();
} catch (Exception ex) {
log.error("Error shutting down the NodeAccessor for druid {}", ExceptionUtils.getStackTrace(ex));
}
}
/**
* Convenient method for POSTing json strings. It is the responsibility of the
* caller to call returnClient() to ensure clean state of the pool.
* @param url
* @param json
* @param reqHeaders
* @return
* @throws IOException
*/
public CloseableHttpResponse postJson(String url, String json, Map<String, String> reqHeaders) throws IOException {
CloseableHttpClient req = getClient();
CloseableHttpResponse resp = null;
HttpPost post = new HttpPost(url);
addHeaders(post, reqHeaders);
post.setHeader(json, url);
StringEntity input = new StringEntity(json, ContentType.APPLICATION_JSON);
post.setEntity(input);
resp = req.execute(post);
return resp;
}
/**
* Convenient method for GETing. It is the responsibility of the
* caller to call returnClient() to ensure clean state of the pool.
* @param url
* @param reqHeaders
* @return
* @throws IOException
*/
public CloseableHttpResponse get(String url, Map<String, String> reqHeaders) throws IOException {
CloseableHttpClient req = getClient();
CloseableHttpResponse resp = null;
HttpGet get = new HttpGet(url);
addHeaders(get, reqHeaders);
resp = req.execute(get);
return resp;
}
private void addHeaders(HttpRequestBase req, Map<String, String> reqHeaders) {
if (reqHeaders == null)
return;
for (String key:reqHeaders.keySet()) {
req.setHeader(key, reqHeaders.get(key));
}
}
}