/* (c) 2014 Open Source Geospatial Foundation - all rights reserved * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.wps.executor; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.net.URL; import java.net.URLConnection; import java.util.Iterator; import java.util.logging.Level; import java.util.logging.Logger; import net.opengis.wps10.HeaderType; import net.opengis.wps10.InputReferenceType; import net.opengis.wps10.InputType; import net.opengis.wps10.MethodType; import org.apache.commons.httpclient.Header; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpConnectionManager; import org.apache.commons.httpclient.HttpMethod; import org.apache.commons.httpclient.SimpleHttpConnectionManager; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.httpclient.methods.InputStreamRequestEntity; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.methods.StringRequestEntity; import org.apache.commons.httpclient.params.HttpConnectionManagerParams; import org.geoserver.wps.WPSException; import org.geoserver.wps.ppio.ComplexPPIO; import org.geotools.data.DataUtilities; import org.geotools.util.logging.Logging; import org.opengis.util.ProgressListener; /** * Handles an internal reference to a remote location * * @author Andrea Aime - GeoSolutions */ public class RemoteRequestInputProvider extends AbstractInputProvider { static final Logger LOGGER = Logging.getLogger(RemoteRequestInputProvider.class); private int timeout; private ComplexPPIO complexPPIO; private long maxSize; public RemoteRequestInputProvider(InputType input, ComplexPPIO ppio, int timeout, long maxSize) { super(input, ppio); this.timeout = timeout; this.complexPPIO = ppio; this.maxSize = maxSize; } @Override protected Object getValueInternal(ProgressListener listener) throws Exception { InputReferenceType ref = input.getReference(); URL destination = new URL(ref.getHref()); HttpMethod method = null; GetMethod refMethod = null; InputStream input = null; InputStream refInput = null; // execute the request listener.started(); try { if ("file".equalsIgnoreCase(destination.getProtocol())) { File file = DataUtilities.urlToFile(destination); if (maxSize > 0 && maxSize < file.length()) { throw new WPSException("Input " + getInputId() + " size " + file.length() + " exceeds maximum allowed size of " + maxSize, "NoApplicableCode", getInputId()); } input = new FileInputStream(file); } else if ("http".equalsIgnoreCase(destination.getProtocol())) { // setup the client HttpClient client = new HttpClient(); // setting timeouts (30 seconds, TODO: make this configurable) HttpConnectionManagerParams params = new HttpConnectionManagerParams(); params.setSoTimeout(timeout); params.setConnectionTimeout(timeout); // TODO: make the http client a well behaved http client, no more than x connections // per server (x admin configurable maybe), persistent connections and so on HttpConnectionManager manager = new SimpleHttpConnectionManager(); manager.setParams(params); client.setHttpConnectionManager(manager); // prepare either a GET or a POST request if (ref.getMethod() == null || ref.getMethod() == MethodType.GET_LITERAL) { GetMethod get = new GetMethod(ref.getHref()); get.setFollowRedirects(true); method = get; } else { String encoding = ref.getEncoding(); if (encoding == null) { encoding = "UTF-8"; } PostMethod post = new PostMethod(ref.getHref()); Object body = ref.getBody(); if (body == null) { if (ref.getBodyReference() != null) { URL refDestination = new URL(ref.getBodyReference().getHref()); if ("http".equalsIgnoreCase(refDestination.getProtocol())) { // open with commons http client refMethod = new GetMethod(ref.getBodyReference().getHref()); refMethod.setFollowRedirects(true); client.executeMethod(refMethod); refInput = refMethod.getResponseBodyAsStream(); } else { // open with the built-in url management URLConnection conn = refDestination.openConnection(); conn.setConnectTimeout(timeout); conn.setReadTimeout(timeout); refInput = conn.getInputStream(); } post.setRequestEntity(new InputStreamRequestEntity(refInput, complexPPIO .getMimeType())); } else { throw new WPSException("A POST request should contain a non empty body"); } } else if (body instanceof String) { post.setRequestEntity(new StringRequestEntity((String) body, complexPPIO .getMimeType(), encoding)); } else { throw new WPSException( "The request body should be contained in a CDATA section, " + "otherwise it will get parsed as XML instead of being preserved as is"); } method = post; } // add eventual extra headers if (ref.getHeader() != null) { for (Iterator it = ref.getHeader().iterator(); it.hasNext();) { HeaderType header = (HeaderType) it.next(); method.setRequestHeader(header.getKey(), header.getValue()); } } int code = client.executeMethod(method); if (code == 200) { try { Header length = method.getResponseHeader("Content-Lenght"); if (maxSize > 0 && length != null && Long.parseLong(length.getValue()) > maxSize) { throw new WPSException( "Input " + getInputId() + " size " + length.getValue() + " exceeds maximum allowed size of " + maxSize + " according to HTTP Content-Lenght response header", "NoApplicableCode", getInputId()); } } catch (NumberFormatException e) { LOGGER.log(Level.FINE, "Failed to parse content lenght to check input limits respect, " + "moving on and checking data as it comes in", e); } input = method.getResponseBodyAsStream(); if (maxSize > 0) { input = new MaxSizeInputStream(input, getInputId(), maxSize); } } else { throw new WPSException("Error getting remote resources from " + ref.getHref() + ", http error " + code + ": " + method.getStatusText()); } } else { // use the normal url connection methods then... URLConnection conn = destination.openConnection(); conn.setConnectTimeout(timeout); conn.setReadTimeout(timeout); input = conn.getInputStream(); if (maxSize > 0) { input = new MaxSizeInputStream(input, getInputId(), maxSize); } } // actually parse the data if (input != null) { CancellingInputStream is = new CancellingInputStream(input, listener); return complexPPIO.decode(is); } else { throw new WPSException("Could not find a mean to read input " + inputId); } } finally { listener.progress(100); listener.complete(); // make sure to close the connection and streams no matter what if (refInput != null) { refInput.close(); } if (input != null) { input.close(); } if (method != null) { method.releaseConnection(); } if (refMethod != null) { refMethod.releaseConnection(); } } } @Override public int longStepCount() { return 1; } }