/* This file is part of RouteConverter. RouteConverter 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 2 of the License, or (at your option) any later version. RouteConverter 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 RouteConverter; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Copyright (C) 2007 Christian Pesch. All Rights Reserved. */ package slash.navigation.rest; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpHost; import org.apache.http.HttpResponse; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.AuthCache; import org.apache.http.client.CredentialsProvider; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.protocol.HttpClientContext; import org.apache.http.conn.HttpClientConnectionManager; import org.apache.http.impl.auth.BasicScheme; import org.apache.http.impl.client.BasicAuthCache; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.DefaultHttpRequestRetryHandler; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.message.BasicHttpResponse; import slash.navigation.rest.ssl.SSLConnectionManagerFactory; import javax.swing.*; import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.net.InetSocketAddress; import java.net.Proxy; import java.net.ProxySelector; import java.net.SocketAddress; import java.net.SocketException; import java.net.URI; import java.util.List; import java.util.logging.Logger; import static java.lang.String.format; import static java.net.Proxy.NO_PROXY; import static java.net.Proxy.Type.DIRECT; import static java.util.Arrays.asList; import static org.apache.http.HttpStatus.SC_BAD_REQUEST; import static org.apache.http.HttpStatus.SC_FORBIDDEN; import static org.apache.http.HttpStatus.SC_MULTIPLE_CHOICES; import static org.apache.http.HttpStatus.SC_NOT_FOUND; import static org.apache.http.HttpStatus.SC_NOT_MODIFIED; import static org.apache.http.HttpStatus.SC_OK; import static org.apache.http.HttpStatus.SC_PARTIAL_CONTENT; import static org.apache.http.HttpStatus.SC_PRECONDITION_FAILED; import static org.apache.http.HttpStatus.SC_UNAUTHORIZED; import static org.apache.http.HttpVersion.HTTP_1_1; import static slash.common.helpers.ExceptionHelper.getLocalizedMessage; import static slash.common.io.InputOutput.readBytes; import static slash.common.io.Transfer.UTF8_ENCODING; /** * Wrapper for a simple HTTP Request. * * @author Christian Pesch */ public abstract class HttpRequest { public static final String APPLICATION_JSON = "application/json"; public static final String USER_AGENT = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36"; private final Logger log; private final HttpClientBuilder clientBuilder = HttpClientBuilder.create(); private final HttpRequestBase method; private HttpResponse response; private HttpClientContext context; private RequestConfig.Builder requestConfigBuilder; HttpRequest(HttpRequestBase method) { this.log = Logger.getLogger(getClass().getName()); requestConfigBuilder = RequestConfig.custom(); requestConfigBuilder.setConnectTimeout(15 * 1000); requestConfigBuilder.setSocketTimeout(90 * 1000); clientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(0, false)); try { HttpClientConnectionManager connectionManager = new SSLConnectionManagerFactory().createConnectionManager(); clientBuilder.setConnectionManager(connectionManager); } catch (Exception e) { log.severe("Cannot create SSL connection manager that supports letsencrypt root certificate: " + getLocalizedMessage(e)); } setUserAgent("RouteConverter REST Client/" + System.getProperty("rest", "1.8")); this.method = method; } HttpRequest(HttpRequestBase method, Credentials credentials) { this(method); if (credentials != null) setAuthentication(credentials); } HttpRequestBase getMethod() { return method; } private void setAuthentication(String userName, String password, AuthScope authScope) { CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); credentialsProvider.setCredentials(authScope, new UsernamePasswordCredentials(userName, password)); AuthCache authCache = new BasicAuthCache(); BasicScheme basicAuth = new BasicScheme(); HttpHost targetHost = new HttpHost(authScope.getHost(), authScope.getPort(), authScope.getScheme()); authCache.put(targetHost, basicAuth); context = HttpClientContext.create(); context.setAuthCache(authCache); context.setCredentialsProvider(credentialsProvider); } private void setAuthentication(Credentials credentials) { URI uri = method.getURI(); setAuthentication(credentials.getUserName(), credentials.getPassword(), new AuthScope(uri.getHost(), uri.getPort(), "api")); } public void setUserAgent(String userAgent) { clientBuilder.setUserAgent(userAgent); } public void setSocketTimeout(int socketTimeout) { requestConfigBuilder.setSocketTimeout(socketTimeout); } protected void setHeader(String name, String value) { getMethod().setHeader(name, value); } protected void disableContentCompression() { clientBuilder.disableContentCompression(); } protected boolean throwsSocketExceptionIfUnAuthorized() { return false; } private Proxy findProxy(URI uri) { try { ProxySelector selector = ProxySelector.getDefault(); List<Proxy> proxyList = selector.select(uri); if (proxyList.size() > 0) return proxyList.get(0); } catch (IllegalArgumentException e) { log.severe("Exception while finding proxy for " + uri + ": " + getLocalizedMessage(e)); } return NO_PROXY; } protected HttpResponse execute() throws IOException { Proxy proxy = findProxy(method.getURI()); if(proxy != NO_PROXY && !proxy.type().equals(DIRECT)) { SocketAddress address = proxy.address(); if(address instanceof InetSocketAddress) { InetSocketAddress inetSocketAddress = (InetSocketAddress) address; requestConfigBuilder.setProxy(new HttpHost(inetSocketAddress.getHostName(), inetSocketAddress.getPort())); log.info(format("Using proxy %s for %s", proxy.toString(), method.getURI())); } } RequestConfig requestConfig = requestConfigBuilder.build(); clientBuilder.setDefaultRequestConfig(requestConfig); try { return clientBuilder.build().execute(method, context); } catch (SocketException e) { if (throwsSocketExceptionIfUnAuthorized()) return new BasicHttpResponse(HTTP_1_1, SC_UNAUTHORIZED, "socket exception since unauthorized"); else throw e; } } public String executeAsString() throws IOException { try { this.response = execute(); HttpEntity entity = response.getEntity(); // HEAD requests don't have a body String body = entity != null ? new String(readBytes(entity.getContent()), UTF8_ENCODING) : null; if (!isSuccessful() && body != null) log.warning(format("Body of %s not null: %s", response, body)); return body; } finally { release(); } } public InputStream executeAsStream() throws IOException { this.response = execute(); // no response body then HttpEntity entity = response.getEntity(); InputStream body = entity != null ? entity.getContent() : null; if (!isSuccessful() && !isNotModified()) log.warning(format("Cannot read response body for %s", method.getURI())); return body; } public void release() throws IOException { if(response instanceof Closeable) ((Closeable)response).close(); method.reset(); } private void assertExecuted() throws IOException { if (response == null) throw new IOException("No request executed yet"); } protected String getHeader(String name) throws IOException { assertExecuted(); Header header = response.getFirstHeader(name); return header != null ? header.getValue() : null; } public/*for tests*/ List<Header> getHeaders() throws IOException { assertExecuted(); return asList(response.getAllHeaders()); } public int getStatusCode() throws IOException { assertExecuted(); return response.getStatusLine().getStatusCode(); } public boolean isSuccessful() throws IOException { return getStatusCode() >= SC_OK && getStatusCode() < SC_MULTIPLE_CHOICES; } public boolean isOk() throws IOException { return getStatusCode() == SC_OK; } public boolean isNotModified() throws IOException { return getStatusCode() == SC_NOT_MODIFIED; } public boolean isPartialContent() throws IOException { return getStatusCode() == SC_PARTIAL_CONTENT; } public boolean isUnAuthorized() throws IOException { return getStatusCode() == SC_UNAUTHORIZED; } public boolean isForbidden() throws IOException { return getStatusCode() == SC_FORBIDDEN; } public boolean isNotFound() throws IOException { return getStatusCode() == SC_NOT_FOUND; } public boolean isBadRequest() throws IOException { return getStatusCode() == SC_BAD_REQUEST; } public boolean isPreconditionFailed() throws IOException { return getStatusCode() == SC_PRECONDITION_FAILED; } }