/** Copyright 2008, 2009 Mark Hooijkaas This file is part of the RelayConnector framework. The RelayConnector framework is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. The RelayConnector framework is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with the RelayConnector framework. If not, see <http://www.gnu.org/licenses/>. */ package org.kisst.http4j; import java.io.IOException; import java.util.concurrent.TimeUnit; import org.apache.http.ParseException; import org.apache.http.auth.AuthScope; import org.apache.http.auth.AuthenticationException; import org.apache.http.auth.Credentials; import org.apache.http.auth.NTCredentials; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.CredentialsProvider; import org.apache.http.client.config.AuthSchemes; import org.apache.http.client.config.RequestConfig; 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.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.impl.auth.BasicScheme; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.util.EntityUtils; import org.kisst.cfg4j.CompositeSetting; import org.kisst.cfg4j.IntSetting; import org.kisst.cfg4j.LongSetting; import org.kisst.cfg4j.StringSetting; import org.kisst.props4j.Props; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class HttpCaller { private final static Logger logger=LoggerFactory.getLogger(HttpCaller.class); public static class Response { final private int code; final private String response; public Response(CloseableHttpResponse response) { this.code=response.getStatusLine().getStatusCode(); try { this.response = EntityUtils.toString(response.getEntity()); } catch (ParseException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } } public int getCode() { return code; } public String getResponseString() { return response;} } public static class Settings extends CompositeSetting { public Settings(CompositeSetting parent, String name) { super(parent, name); } public final StringSetting host = new StringSetting(this, "host","esb1"); public final LongSetting closeIdleConnections = new LongSetting(this, "closeIdleConnections", -1); public final IntSetting timeout = new IntSetting(this, "timeout", 30000); public final StringSetting returnCodesToIgnore= new StringSetting(this, "returnCodesToIgnore", null); // public final StringSetting urlPostfix = new StringSetting(this, "urlPostfix", null); } private static final PoolingHttpClientConnectionManager connmngr = new PoolingHttpClientConnectionManager(); private static final IdleConnectionMonitorThread idleThread = new IdleConnectionMonitorThread(connmngr);//can not be static because multiple classes use this, so there are multiple instances private static final CredentialsProvider credsProvider = new BasicCredentialsProvider(); private static final CloseableHttpClient client = HttpClients.custom() .setDefaultCredentialsProvider(credsProvider) .setConnectionManager(connmngr) .build(); //private static final AuthCache authCache = new BasicAuthCache(); //private static final BasicScheme basicAuth = new BasicScheme(); //private static final HttpClientContext localContext = HttpClientContext.create(); static { idleThread.setDaemon(true); idleThread.start(); //localContext.setAuthCache(authCache); } protected final Props props; private final long closeIdleConnections; protected final HttpHost host; private final int timeout; private final int[] returnCodesToIgnore; private final Credentials credentials; private static final int[] EMPTY_INT_ARRAY = new int[0]; // private final String urlPostfix; public HttpCaller(HttpHostMap hostMap, Props props) { this(hostMap, props, new Settings(null, null)); } public HttpCaller(HttpHostMap hostMap, Props props, Settings settings) { this.props = props; closeIdleConnections = settings.closeIdleConnections.get(props); String hostname = settings.host.get(props); // if (hostname==null) // throw new RuntimeException("host config parameter should be set"); host = hostMap.getHttpHost(hostname.trim()); timeout = settings.timeout.get(props); // urlPostfix=settings.urlPostfix.get(props); credentials = host.getCredentials(); if (credentials!=null) { AuthScope scope; if (credentials instanceof NTCredentials) scope= new AuthScope(getHostFromUrl(host.url), host.port, AuthScope.ANY_REALM, AuthSchemes.NTLM); else scope=new AuthScope(getHostFromUrl(host.url), host.port, AuthScope.ANY_REALM, AuthSchemes.BASIC); credsProvider.setCredentials(scope, credentials); } String codes=settings.returnCodesToIgnore.get(props); if (codes==null) this.returnCodesToIgnore=EMPTY_INT_ARRAY; else { String[] codeList = codes.split(","); this.returnCodesToIgnore=new int[codeList.length]; for (int i=0; i<codeList.length; i++) this.returnCodesToIgnore[i]=Integer.parseInt(codeList[i]); } } public String getCompleteUrl(String url) { return host.url + url; } // TODO: make smarter with / and ? handling public String httpGet(String url) { HttpGet method = new HttpGet(getCompleteUrl(url)); logger.info("Calling url: {}", url); return httpCall(method).getResponseString(); } public String httpPost(String url, String body) { HttpPost method = new HttpPost(getCompleteUrl(url)); method.setEntity(new StringEntity(body, ContentType.create("text/xml", "UTF-8"))); return httpCall(method).getResponseString(); } @SuppressWarnings("deprecation") protected Response httpCall(final HttpRequestBase method) { try { if (credentials instanceof UsernamePasswordCredentials) method.addHeader(new BasicScheme().authenticate(credentials, method)); } catch (AuthenticationException e) { throw new RuntimeException(e);} method.setConfig(RequestConfig.custom().setSocketTimeout(timeout).build());// setStaleConnectionCheckEnabled()? try { if (closeIdleConnections >= 0) { // Hack because often some idle connections were closed which resulted in 401 errors connmngr.closeIdleConnections(closeIdleConnections, TimeUnit.SECONDS); } Response response = new Response(client.execute(method)); checkResponseForErrors(response); return response; } catch (IOException e) { throw new RuntimeException(e); } finally { method.releaseConnection(); // TODO: what if connection not yet borrowed? } } protected void checkResponseForErrors(Response response) { if (response.getCode() >= 300) { boolean ignore=false; for (int responseCode: returnCodesToIgnore) { if (responseCode==response.getCode()) ignore=true; } if (! ignore) throw new RuntimeException("HTTP call returned " + response.getCode() + "\n" + response.getResponseString()); } } private String getHostFromUrl(String url) { String result = url; if (url.contains("http://")) { result = result.replaceAll("http://", ""); } if (result.contains("/")) { result = result.substring(0, result.indexOf("/")); } return result; } }