/*
* Copyright (c) 2015 EMC Corporation
* All Rights Reserved
*/
package render;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Map;
import com.emc.vipr.client.impl.SSLUtil;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.apache.http.impl.conn.SchemeRegistryFactory;
import play.Logger;
import play.exceptions.UnexpectedException;
import play.mvc.Http.Request;
import play.mvc.Http.Response;
import play.mvc.results.Result;
import com.emc.vipr.client.impl.Constants;
import com.google.common.collect.Maps;
import plugin.StorageOsPlugin;
import util.BourneUtil;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.X509TrustManager;
/**
* Renders a proxied HTTP request, streaming back the content directly to the client.
* <p>
* NOTE: Any requests that use this result will appear to hang in the browser if they go directly to the play server (instead through
* NGINX). If the client requests a keep-alive connection (which seems to be default) then play doesn't ever close the channel when all the
* content is written out. When the connections go through NGINX it changes those Connection: keep-alive to Connection: close (in our
* configuration) and everything works fine.
*/
public class RenderProxy extends Result {
private String url;
private Map<String, String> headers;
public static void renderProxy(String url, Map<String, String> headers) {
throw new RenderProxy(url, headers);
}
public static void renderViprProxy(String url, String authToken, String accept) {
Map<String, String> headers = Maps.newHashMap();
headers.put(Constants.AUTH_TOKEN_KEY, authToken);
headers.put("X-EMC-REST-CLIENT", "TRUE");
if (StringUtils.isNotBlank(accept)) {
headers.put("Accept", accept);
}
renderProxy(url, headers);
}
public RenderProxy(String url) {
this(url, new HashMap<String, String>());
}
public RenderProxy(String url, Map<String, String> headers) {
this.url = url;
this.headers = headers;
}
@Override
public void apply(Request request, Response response) {
HttpGet httpGet = new HttpGet(url);
for (Map.Entry<String, String> header : headers.entrySet()) {
httpGet.setHeader(header.getKey(), header.getValue());
}
HttpClient client = new DefaultHttpClient(createClientConnectionManager());
try {
HttpResponse httpResponse = client.execute(httpGet);
HttpEntity entity = httpResponse.getEntity();
if (entity.getContentType() != null) {
setContentTypeIfNotSet(response, entity.getContentType().getValue());
}
long length = entity.getContentLength();
if (length > -1) {
response.setHeader("Content-Length", String.valueOf(length));
}
response.status = httpResponse.getStatusLine().getStatusCode();
response.direct = new HttpClientInputStream(client, entity.getContent());
} catch (Exception e) {
Logger.error(e, "Failed to execute Proxy URL [%s]", url);
client.getConnectionManager().shutdown();
throw new UnexpectedException(e);
}
}
private static ClientConnectionManager createClientConnectionManager() {
SchemeRegistry schemeRegistry = SchemeRegistryFactory.createDefault();
SSLSocketFactory sf;
if (StorageOsPlugin.isEnabled()) {
try {
// initialize an SSLContext with the vipr keystore and trustmanager.
// This is basically a dup of most of the ViPRSSLSocketFactory constructor,
// and could be extracted
X509TrustManager[] trustManagers = { BourneUtil.getTrustManager() };
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(BourneUtil.getKeyStore(), "".toCharArray());
SSLContext context = SSLContext.getInstance("TLS");
context.init(kmf.getKeyManagers(), trustManagers, new SecureRandom());
sf = new SSLSocketFactory(context, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
} catch (Exception e) {
throw new RuntimeException("Unable to initialize the ViPRX509TrustManager for RenderProxy", e);
}
} else {
sf = new SSLSocketFactory(SSLUtil.getTrustAllContext(), SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
}
Scheme httpsScheme = new Scheme("https", 443, sf);
schemeRegistry.register(httpsScheme);
ClientConnectionManager connectionManager = new PoolingClientConnectionManager(schemeRegistry);
return connectionManager;
}
private static class HttpClientInputStream extends FilterInputStream {
private HttpClient client;
protected HttpClientInputStream(HttpClient client, InputStream stream) {
super(stream);
this.client = client;
}
@Override
public void close() throws IOException {
try {
super.close();
} finally {
client.getConnectionManager().shutdown();
}
}
}
}