/*
* Copyright (c) 2010 Netcetera AG and others.
* All rights reserved.
* This program and the accompanying materials are made available under
* the terms of the Eclipse Public License v1.0 which accompanies this
* distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* - Netcetera AG: initial implementation
*/
package ch.netcetera.eclipse.common.net;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.util.zip.GZIPInputStream;
import org.apache.http.Header;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.params.ConnRoutePNames;
import org.apache.http.conn.scheme.PlainSocketFactory;
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.eclipse.core.net.proxy.IProxyData;
import org.eclipse.core.net.proxy.IProxyService;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
/**
* Base HTTP client for the different ETE subprojects.
*/
public abstract class AbstractHttpClient {
/** The default buffer size for stream operations. */
protected static final int DEFAULT_BUFFER_SIZE = 1024 * 8;
/**
* Call-back interface for handling HTTP responses.
*
* @param <R> the type of result of handling the response.
*/
public interface IResponseHandler<R> {
/**
* Handle a HTTP response.
*
* @param response the method that was executed
* @param monitor the monitor to report progress on
* @return the result of handling the response
* @throws IOException if reading the response fails
*/
R handleResponse(HttpResponse response, IProgressMonitor monitor) throws IOException;
}
/**
* An {@link InputStream} that wraps a {@link IProgressMonitor}. Reading from the stream will
* report progress.
*/
protected static final class ProgressReportingInputStream extends InputStream {
private final InputStream stream;
private final IProgressMonitor monitor;
/**
* Constructor.
*
* @param stream the stream to wrap, not {@literal null}
* @param monitor the monitor to use, not {@literal null}
*/
ProgressReportingInputStream(InputStream stream, IProgressMonitor monitor) {
this.stream = stream;
this.monitor = monitor;
}
/**
* {@inheritDoc}
*/
@Override
public int available() throws IOException {
return this.stream.available();
}
/**
* {@inheritDoc}
*/
@Override
public void close() throws IOException {
try {
this.stream.close();
} finally {
this.monitor.done();
}
}
/**
* {@inheritDoc}
*/
@Override
public void mark(int readlimit) {
this.stream.mark(readlimit);
}
/**
* {@inheritDoc}
*/
@Override
public boolean markSupported() {
return this.stream.markSupported();
}
/**
* {@inheritDoc}
*/
@Override
public int read() throws IOException {
int data = this.stream.read();
this.monitor.worked(1);
return data;
}
/**
* {@inheritDoc}
*/
@Override
public int read(byte[] b, int off, int len) throws IOException {
int read = this.stream.read(b, off, len);
this.monitor.worked(read);
return read;
}
/**
* {@inheritDoc}
*/
@Override
public int read(byte[] b) throws IOException {
int read = this.stream.read(b);
this.monitor.worked(read);
return read;
}
/**
* {@inheritDoc}
*/
@Override
public void reset() throws IOException {
// screws the monitor
this.stream.reset();
}
/**
* {@inheritDoc}
*/
@Override
public long skip(long n) throws IOException {
long skipped = this.stream.skip(n);
this.monitor.worked((int) skipped);
return skipped;
}
}
/**
* Executes a HTTP get request.
*
* @param <R> the return type
* @param url the url
* @param handler the response handler
* @param monitor the progress monitor
* @return the response
* @throws CoreException on error
*/
protected <R> R executeGetRequest(String url,
IResponseHandler<R> handler,
IProgressMonitor monitor) throws CoreException {
HttpClient client = new DefaultHttpClient();
HttpGet get = new HttpGet(url);
get.addHeader("Accept-Encoding", "gzip");
configureProxySettings(client, get);
configureSslHandling(client);
try {
HttpResponse response = client.execute(get);
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == HttpStatus.SC_OK) {
return handler.handleResponse(response, monitor);
} else {
throw convertHttpStatusToException(statusCode, get.getURI());
}
} catch (IOException e) {
throw wrapIoException(e);
}
}
private void configureProxySettings(HttpClient client, HttpGet get) {
if (getProxyService() != null
&& getProxyService().getProxyData() != null
&& getProxyService().getProxyData().length > 0) {
String requestScheme = get.getURI().getScheme();
for (IProxyData proxyData : getProxyService().getProxyData()) {
if (proxyData != null
&& proxyData.getHost() != null
&& proxyData.getType().equalsIgnoreCase(requestScheme)) {
HttpHost proxy = new HttpHost(proxyData.getHost(), proxyData.getPort(), requestScheme);
client.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
break;
}
}
}
}
private void configureSslHandling(HttpClient httpClient) throws CoreException {
Scheme http = new Scheme("http", 80, PlainSocketFactory.getSocketFactory());
Scheme https = new Scheme("https", 443, SSLSocketFactory.getSocketFactory());
SchemeRegistry sr = httpClient.getConnectionManager().getSchemeRegistry();
sr.register(http);
sr.register(https);
}
private CoreException wrapIoException(IOException e) {
return this.wrapGenericException(e);
}
private CoreException wrapGenericException(Exception e) {
IStatus status = new Status(IStatus.ERROR, getBundleSymbolicName(), e.getLocalizedMessage(), e);
return new CoreException(status);
}
/**
* @return the bundle symbolic name
*/
protected abstract String getBundleSymbolicName();
private CoreException convertHttpStatusToException(int httpStatusCode, URI uri) {
IStatus status = new Status(IStatus.WARNING, getBundleSymbolicName(),
"ETE received an unexpected HTTP status code " + httpStatusCode
+ " while connecting to " + uri);
return new CoreException(status);
}
/**
* Copy bytes from an {@link InputStream} to an {@link OutputStream}.
*
* @param input the {@link InputStream} to read from
* @param output the {@link OutputStream} to write to
* @throws NullPointerException if the input or output is null
* @throws IOException if an I/O error occurs
*/
protected static void copy(InputStream input, OutputStream output) throws IOException,
NullPointerException {
// taken from Commons IO 1.3
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
int n = 0;
while (-1 != (n = input.read(buffer))) { // NOPMD pellaton 2010-11-20 ok
output.write(buffer, 0, n);
}
}
private IProxyService proxyService;
/**
* Wraps the response stream.
*
* @param response the HTTP response
* @param stream the input stream
* @param monitor the progress monitor
* @return the wrapped input stream
* @throws IOException on error
*/
protected InputStream wrapResponseStream(HttpResponse response,
InputStream stream,
IProgressMonitor monitor) throws IOException {
InputStream input = stream;
Header contentEncoding = response.getFirstHeader("Content-Encoding");
long contentLength = response.getEntity().getContentLength();
if (contentLength != -1) {
monitor.beginTask("Parsing Response", (int) contentLength);
input = new ProgressReportingInputStream(input, monitor);
}
if (contentEncoding != null && "gzip".equalsIgnoreCase(contentEncoding.getValue())) {
input = new GZIPInputStream(input);
}
return input;
}
/**
* Binds the {@link IProxyService} service reference.
*
* @param proxyService the {@link IProxyService} service reference to bind
*/
public void bindProxyService(IProxyService proxyService) {
this.proxyService = proxyService;
}
/**
* Unbinds the {@link IProxyService} service reference.
*
* @param proxyService the {@link IProxyService} service reference to unbind
*/
public void unbindProxyService(@SuppressWarnings("unused") IProxyService proxyService) {
this.proxyService = null;
}
/**
* Gets the {@link IProxyService} instance.
*
* @return the {@link IProxyService} instance
*/
protected IProxyService getProxyService() {
return proxyService;
}
}