From 2994b28f22939036ddb69dbc8f9ea05c2f9dd469 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Fri, 21 Dec 2012 10:45:56 +0100 Subject: [PATCH] re implement ProxyServlet --- .../scm/web/proxy/BasicProxyURLProvider.java | 2 + .../scm/web/proxy/ProxyConfiguration.java | 222 +++++++++++++ .../web/proxy/ProxyConfigurationProvider.java | 55 ++++ .../java/sonia/scm/web/proxy/ProxyServet.java | 2 + .../sonia/scm/web/proxy/ProxyServlet.java | 293 ++++++++++++++++++ .../sonia/scm/web/proxy/ProxyURLProvider.java | 2 + 6 files changed, 576 insertions(+) create mode 100644 scm-core/src/main/java/sonia/scm/web/proxy/ProxyConfiguration.java create mode 100644 scm-core/src/main/java/sonia/scm/web/proxy/ProxyConfigurationProvider.java create mode 100644 scm-core/src/main/java/sonia/scm/web/proxy/ProxyServlet.java diff --git a/scm-core/src/main/java/sonia/scm/web/proxy/BasicProxyURLProvider.java b/scm-core/src/main/java/sonia/scm/web/proxy/BasicProxyURLProvider.java index 90c9ea5412..d8a91d80e6 100644 --- a/scm-core/src/main/java/sonia/scm/web/proxy/BasicProxyURLProvider.java +++ b/scm-core/src/main/java/sonia/scm/web/proxy/BasicProxyURLProvider.java @@ -41,7 +41,9 @@ import java.net.URL; * * @author Sebastian Sdorra * @since 1.8 + * @deprecated use {@link ProxyConfigurationProvider} instead. */ +@Deprecated public class BasicProxyURLProvider implements ProxyURLProvider { diff --git a/scm-core/src/main/java/sonia/scm/web/proxy/ProxyConfiguration.java b/scm-core/src/main/java/sonia/scm/web/proxy/ProxyConfiguration.java new file mode 100644 index 0000000000..b76f27c64e --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/web/proxy/ProxyConfiguration.java @@ -0,0 +1,222 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.web.proxy; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.base.Objects; + +//~--- JDK imports ------------------------------------------------------------ + +import java.net.URL; + +import java.util.Collections; +import java.util.Set; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; +import javax.xml.bind.annotation.XmlRootElement; + +/** + * + * @author Sebastian Sdorra + * @since 1.25 + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlRootElement(name = "proxy-configuration") +public class ProxyConfiguration +{ + + /** + * Constructs ... + * + */ + public ProxyConfiguration() {} + + /** + * Constructs ... + * + * + * @param url + * @param requestHeaderExcludes + * @param responseHeaderExcludes + * @param cacheEnabled + */ + public ProxyConfiguration(URL url, Set requestHeaderExcludes, + Set responseHeaderExcludes, boolean cacheEnabled) + { + this.url = url; + this.requestHeaderExcludes = requestHeaderExcludes; + this.responseHeaderExcludes = responseHeaderExcludes; + this.cacheEnabled = cacheEnabled; + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param obj + * + * @return + */ + @Override + public boolean equals(Object obj) + { + if (obj == null) + { + return false; + } + + if (getClass() != obj.getClass()) + { + return false; + } + + final ProxyConfiguration other = (ProxyConfiguration) obj; + + return Objects.equal(url, other.url) + && Objects.equal(requestHeaderExcludes, other.requestHeaderExcludes) + && Objects.equal(responseHeaderExcludes, other.responseHeaderExcludes) + && Objects.equal(cacheEnabled, other.cacheEnabled); + } + + /** + * Method description + * + * + * @return + */ + @Override + public int hashCode() + { + return Objects.hashCode(url, requestHeaderExcludes, responseHeaderExcludes, + cacheEnabled); + } + + /** + * Method description + * + * + * @return + */ + @Override + public String toString() + { + //J- + return Objects.toStringHelper(this) + .add("url", url) + .add("requestHeaderExcludes", requestHeaderExcludes) + .add("responseHeaderExcludes", responseHeaderExcludes) + .add("cacheEnabled", cacheEnabled) + .toString(); + //J+ + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @return + */ + public Set getRequestHeaderExcludes() + { + if (requestHeaderExcludes == null) + { + requestHeaderExcludes = Collections.EMPTY_SET; + } + + return requestHeaderExcludes; + } + + /** + * Method description + * + * + * @return + */ + public Set getResponseHeaderExcludes() + { + if (responseHeaderExcludes == null) + { + responseHeaderExcludes = Collections.EMPTY_SET; + } + + return responseHeaderExcludes; + } + + /** + * Method description + * + * + * @return + */ + public URL getUrl() + { + return url; + } + + /** + * Method description + * + * + * @return + */ + public boolean isCacheEnabled() + { + return cacheEnabled; + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + @XmlElement(name = "cache-enabled") + private boolean cacheEnabled = true; + + /** Field description */ + @XmlElement(name = "exclude") + @XmlElementWrapper(name = "request-header-excludes") + private Set requestHeaderExcludes; + + /** Field description */ + @XmlElement(name = "exclude") + @XmlElementWrapper(name = "response-header-excludes") + private Set responseHeaderExcludes; + + /** Field description */ + private URL url; +} diff --git a/scm-core/src/main/java/sonia/scm/web/proxy/ProxyConfigurationProvider.java b/scm-core/src/main/java/sonia/scm/web/proxy/ProxyConfigurationProvider.java new file mode 100644 index 0000000000..9f3e03e949 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/web/proxy/ProxyConfigurationProvider.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.web.proxy; + +//~--- JDK imports ------------------------------------------------------------ + +import javax.servlet.http.HttpServletRequest; + +/** + * + * @author Sebastian Sdorra + * @since 1.25 + */ +public interface ProxyConfigurationProvider +{ + + /** + * Method description + * + * + * @param request + * + * @return + */ + public ProxyConfiguration getConfiguration(HttpServletRequest request); +} diff --git a/scm-core/src/main/java/sonia/scm/web/proxy/ProxyServet.java b/scm-core/src/main/java/sonia/scm/web/proxy/ProxyServet.java index 6c1f5afeac..6dd6015d31 100644 --- a/scm-core/src/main/java/sonia/scm/web/proxy/ProxyServet.java +++ b/scm-core/src/main/java/sonia/scm/web/proxy/ProxyServet.java @@ -65,8 +65,10 @@ import javax.servlet.http.HttpServletResponse; * * @author Sebastian Sdorra * @since 1.8 + * @deprecated use {@link ProxyServlet} instead */ @Singleton +@Deprecated public class ProxyServet extends HttpServlet { diff --git a/scm-core/src/main/java/sonia/scm/web/proxy/ProxyServlet.java b/scm-core/src/main/java/sonia/scm/web/proxy/ProxyServlet.java new file mode 100644 index 0000000000..adfe7751e2 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/web/proxy/ProxyServlet.java @@ -0,0 +1,293 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.web.proxy; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.base.Preconditions; +import com.google.common.io.ByteStreams; +import com.google.common.io.Closeables; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.IOException; + +import java.net.HttpURLConnection; + +import java.util.Enumeration; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * + * @author Sebastian Sdorra + * @since 1.25 + */ +@Singleton +public class ProxyServlet extends HttpServlet +{ + + /** + * the logger for ProxyServlet + */ + private static final Logger logger = + LoggerFactory.getLogger(ProxyServlet.class); + + /** Field description */ + private static final long serialVersionUID = 5589963595604482849L; + + //~--- constructors --------------------------------------------------------- + + /** + * Constructs ... + * + * + * @param configurationProvider + */ + @Inject + public ProxyServlet(ProxyConfigurationProvider configurationProvider) + { + this.configurationProvider = configurationProvider; + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param request + * @param response + */ + @Override + public void service(HttpServletRequest request, HttpServletResponse response) + { + + HttpURLConnection con; + + try + { + ProxyConfiguration configuration = + configurationProvider.getConfiguration(request); + + Preconditions.checkNotNull(configuration); + + if (logger.isDebugEnabled()) + { + logger.debug("proxy request for {}", configuration); + } + + con = createConnection(configuration, request); + + copyRequestHeaders(configuration, request, con); + con.connect(); + + int responseCode = con.getResponseCode(); + + logger.trace("resonse returned status code {}", responseCode); + response.setStatus(responseCode); + + copyResponseHeaders(configuration, con, response); + + copyContent(con, response); + + con.disconnect(); + } + catch (Exception ex) + { + logger.error("could not proxy request", ex); + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } + + } + + /** + * Method description + * + * + * @param con + * @param response + * + * @throws IOException + */ + private void copyContent(HttpURLConnection con, HttpServletResponse response) + throws IOException + { + BufferedInputStream webToProxyBuf = null; + BufferedOutputStream proxyToClientBuf = null; + + try + { + webToProxyBuf = new BufferedInputStream(con.getInputStream()); + proxyToClientBuf = new BufferedOutputStream(response.getOutputStream()); + + long bytes = ByteStreams.copy(webToProxyBuf, proxyToClientBuf); + + logger.trace("copied {} bytes for proxy", bytes); + } + finally + { + Closeables.closeQuietly(webToProxyBuf); + Closeables.closeQuietly(proxyToClientBuf); + } + } + + /** + * Method description + * + * + * + * @param configuration + * @param request + * @param con + */ + private void copyRequestHeaders(ProxyConfiguration configuration, + HttpServletRequest request, HttpURLConnection con) + { + Enumeration names = request.getHeaderNames(); + Enumeration values; + String headerName; + String value; + + while (names.hasMoreElements()) + { + headerName = names.nextElement(); + + if (!configuration.getRequestHeaderExcludes().contains(headerName)) + { + + values = request.getHeaders(headerName); + + while (values.hasMoreElements()) + { + value = values.nextElement(); + logger.trace("set request header {}={}", headerName, value); + con.setRequestProperty(headerName, value); + } + } + else if (logger.isTraceEnabled()) + { + logger.trace( + "skip request header {}, because it is in the exclude list", + headerName); + } + } + } + + /** + * Method description + * + * + * + * @param configuration + * @param con + * @param response + */ + private void copyResponseHeaders(ProxyConfiguration configuration, + HttpURLConnection con, HttpServletResponse response) + { + Map> headers = con.getHeaderFields(); + String key; + + for (Entry> e : headers.entrySet()) + { + key = e.getKey(); + + if (key != null) + { + + if (!configuration.getResponseHeaderExcludes().contains(key)) + { + for (String v : e.getValue()) + { + logger.trace("add response header {}={}", key, v); + response.addHeader(key, v); + } + + } + else if (logger.isTraceEnabled()) + { + logger.trace( + "skip request header {}, because it is in the exclude list", key); + } + + } + else if (logger.isTraceEnabled()) + { + logger.trace("response header key is null"); + } + } + } + + /** + * Method description + * + * + * @param configuration + * @param request + * + * @return + * + * @throws IOException + */ + private HttpURLConnection createConnection(ProxyConfiguration configuration, + HttpServletRequest request) + throws IOException + { + HttpURLConnection con = + (HttpURLConnection) configuration.getUrl().openConnection(); + + con.setRequestMethod(request.getMethod()); + con.setDoOutput(true); + con.setDoInput(true); + con.setUseCaches(configuration.isCacheEnabled()); + + return con; + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private ProxyConfigurationProvider configurationProvider; +} diff --git a/scm-core/src/main/java/sonia/scm/web/proxy/ProxyURLProvider.java b/scm-core/src/main/java/sonia/scm/web/proxy/ProxyURLProvider.java index c9d4aedd0a..3d67a732b2 100644 --- a/scm-core/src/main/java/sonia/scm/web/proxy/ProxyURLProvider.java +++ b/scm-core/src/main/java/sonia/scm/web/proxy/ProxyURLProvider.java @@ -41,7 +41,9 @@ import java.net.URL; * * @author Sebastian Sdorra * @since 1.8 + * @deprecated use {@link ProxyConfigurationProvider} instead. */ +@Deprecated public interface ProxyURLProvider {