karaf-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jbono...@apache.org
Subject [karaf] branch master updated: [KARAF-5614] Add HTTP ProxyService (#474)
Date Fri, 30 Mar 2018 03:51:09 GMT
This is an automated email from the ASF dual-hosted git repository.

jbonofre pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/karaf.git


The following commit(s) were added to refs/heads/master by this push:
     new d1ba855  [KARAF-5614] Add HTTP ProxyService (#474)
d1ba855 is described below

commit d1ba85556af689efb14e94230ba3ed8950a2dfb3
Author: Jean-Baptiste Onofré <jbonofre@apache.org>
AuthorDate: Fri Mar 30 05:51:05 2018 +0200

    [KARAF-5614] Add HTTP ProxyService (#474)
    
    [KARAF-5614] Add HTTP ProxyService
---
 http/pom.xml                                       |  15 +-
 .../apache/karaf/http/command/ProxyAddCommand.java |  46 ++
 .../karaf/http/command/ProxyListCommand.java       |  45 ++
 .../karaf/http/command/ProxyRemoveCommand.java     |  45 ++
 .../http/command/completers/ProxyUrlCompleter.java |  50 +++
 .../java/org/apache/karaf/http/core/HttpMBean.java |  16 +
 .../core/{HttpMBean.java => ProxyService.java}     |  20 +-
 .../karaf/http/core/internal/HttpMBeanImpl.java    |  37 +-
 .../karaf/http/core/internal/ProxyServiceImpl.java |  52 +++
 .../karaf/http/core/internal/ProxyServlet.java     | 485 +++++++++++++++++++++
 .../karaf/http/core/internal/osgi/Activator.java   |  29 +-
 .../http/core/internal/HttpMBeanImplTest.java      |   2 +-
 .../src/main/asciidoc/user-guide/webcontainer.adoc |  48 ++
 webconsole/http/pom.xml                            |   5 +
 .../apache/karaf/webconsole/http/Activator.java    |  10 +-
 .../apache/karaf/webconsole/http/HttpPlugin.java   |  19 +
 .../src/main/resources/res/ui/http-contexts.js     |   1 +
 17 files changed, 900 insertions(+), 25 deletions(-)

diff --git a/http/pom.xml b/http/pom.xml
index 4deed4e..c021f0c 100644
--- a/http/pom.xml
+++ b/http/pom.xml
@@ -44,6 +44,11 @@
             <artifactId>org.osgi.core</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+            <scope>provided</scope>
+        </dependency>
 		<dependency>
 			<groupId>org.ops4j.pax.web</groupId>
 			<artifactId>pax-web-spi</artifactId>
@@ -69,6 +74,11 @@
             <artifactId>org.apache.karaf.shell.core</artifactId>
             <optional>true</optional>
         </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <version>4.5.3</version>
+        </dependency>
     </dependencies>
 
     <build>
@@ -98,14 +108,17 @@
                 <configuration>
                     <instructions>
                         <Export-Package>
-                            org.apache.karaf.http.core,
+                            org.apache.karaf.http.core
                         </Export-Package>
                         <Private-Package>
                             org.apache.karaf.http.command,
+                            org.apache.karaf.http.command.completers,
                             org.apache.karaf.http.core.internal,
                             org.apache.karaf.http.core.internal.osgi,
                             org.apache.felix.utils.version,
                             org.apache.felix.utils.manifest,
+                            org.apache.http*,
+                            org.apache.commons.codec*
                         </Private-Package>
                     </instructions>
                 </configuration>
diff --git a/http/src/main/java/org/apache/karaf/http/command/ProxyAddCommand.java b/http/src/main/java/org/apache/karaf/http/command/ProxyAddCommand.java
new file mode 100644
index 0000000..467ce4c
--- /dev/null
+++ b/http/src/main/java/org/apache/karaf/http/command/ProxyAddCommand.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.http.command;
+
+import org.apache.karaf.http.core.ProxyService;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Command(scope = "http", name = "proxy-add", description = "Add a new HTTP proxy")
+@Service
+public class ProxyAddCommand implements Action {
+
+    @Reference
+    private ProxyService proxyService;
+
+    @Argument(index = 0, name = "url", description = "HTTP proxy URL", required = true, multiValued = false)
+    String url;
+
+    @Argument(index = 1, name = "proxyTo", description = "HTTP location to proxy on the prefix", required = true, multiValued = false)
+    String proxyTo;
+
+    @Override
+    public Object execute() throws Exception {
+        proxyService.addProxy(url, proxyTo);
+        return null;
+    }
+
+}
diff --git a/http/src/main/java/org/apache/karaf/http/command/ProxyListCommand.java b/http/src/main/java/org/apache/karaf/http/command/ProxyListCommand.java
new file mode 100644
index 0000000..2e5380d
--- /dev/null
+++ b/http/src/main/java/org/apache/karaf/http/command/ProxyListCommand.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.http.command;
+
+import org.apache.karaf.http.core.ProxyService;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.support.table.ShellTable;
+
+@Command(scope = "http", name = "proxies", description = "List the HTTP proxies")
+@Service
+public class ProxyListCommand implements Action {
+
+    @Reference
+    private ProxyService proxyService;
+
+    @Override
+    public Object execute() throws Exception {
+        ShellTable table = new ShellTable();
+        table.column("URL");
+        table.column("ProxyTo");
+        for (String url : proxyService.getProxies().keySet()) {
+             table.addRow().addContent(url, proxyService.getProxies().get(url));
+        }
+        table.print(System.out);
+        return null;
+    }
+
+}
diff --git a/http/src/main/java/org/apache/karaf/http/command/ProxyRemoveCommand.java b/http/src/main/java/org/apache/karaf/http/command/ProxyRemoveCommand.java
new file mode 100644
index 0000000..540a7ed
--- /dev/null
+++ b/http/src/main/java/org/apache/karaf/http/command/ProxyRemoveCommand.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.http.command;
+
+import org.apache.karaf.http.command.completers.ProxyUrlCompleter;
+import org.apache.karaf.http.core.ProxyService;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Command(scope = "http", name = "proxy-remove", description = "Remove an existing HTTP proxy")
+@Service
+public class ProxyRemoveCommand implements Action {
+
+    @Reference
+    private ProxyService proxyService;
+
+    @Argument(name = "prefix", description = "The HTTP proxy prefix", required = true, multiValued = false)
+    @Completion(ProxyUrlCompleter.class)
+    String prefix;
+
+    @Override
+    public Object execute() throws Exception {
+        proxyService.removeProxy(prefix);
+        return null;
+    }
+
+}
diff --git a/http/src/main/java/org/apache/karaf/http/command/completers/ProxyUrlCompleter.java b/http/src/main/java/org/apache/karaf/http/command/completers/ProxyUrlCompleter.java
new file mode 100644
index 0000000..8250e70
--- /dev/null
+++ b/http/src/main/java/org/apache/karaf/http/command/completers/ProxyUrlCompleter.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.http.command.completers;
+
+import org.apache.karaf.http.core.ProxyService;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.support.completers.StringsCompleter;
+
+import java.util.List;
+import java.util.Set;
+
+@Service
+public class ProxyUrlCompleter implements Completer {
+
+    @Reference
+    private ProxyService proxyService;
+
+    @Override
+    public int complete(Session session, CommandLine commandLine, List<String> candidates) {
+        StringsCompleter delegate = new StringsCompleter();
+        try {
+            Set<String> urls = proxyService.getProxies().keySet();
+            for (String url : urls) {
+                delegate.getStrings().add(url);
+            }
+        } catch (Exception e) {
+            // nothing to do
+        }
+        return delegate.complete(session, commandLine, candidates);
+    }
+
+}
diff --git a/http/src/main/java/org/apache/karaf/http/core/HttpMBean.java b/http/src/main/java/org/apache/karaf/http/core/HttpMBean.java
index eddc647..f224694 100644
--- a/http/src/main/java/org/apache/karaf/http/core/HttpMBean.java
+++ b/http/src/main/java/org/apache/karaf/http/core/HttpMBean.java
@@ -18,6 +18,7 @@ package org.apache.karaf.http.core;
 
 import javax.management.MBeanException;
 import javax.management.openmbean.TabularData;
+import java.util.Map;
 
 /**
  * HTTP MBean.
@@ -32,4 +33,19 @@ public interface HttpMBean {
      */
     TabularData getServlets() throws MBeanException;
 
+    /**
+     * List configured HTTP proxies.
+     */
+    Map<String, String> getProxies() throws MBeanException;
+
+    /**
+     * Add a new HTTP proxy using URL, proxyTo and prefix.
+     */
+    void addProxy(String url, String proxyTo) throws MBeanException;
+
+    /**
+     * Remove an existing HTTP proxy identified by URL.
+     */
+    void removeProxy(String url) throws MBeanException;
+
 }
diff --git a/http/src/main/java/org/apache/karaf/http/core/HttpMBean.java b/http/src/main/java/org/apache/karaf/http/core/ProxyService.java
similarity index 69%
copy from http/src/main/java/org/apache/karaf/http/core/HttpMBean.java
copy to http/src/main/java/org/apache/karaf/http/core/ProxyService.java
index eddc647..63d03fe 100644
--- a/http/src/main/java/org/apache/karaf/http/core/HttpMBean.java
+++ b/http/src/main/java/org/apache/karaf/http/core/ProxyService.java
@@ -16,20 +16,14 @@
  */
 package org.apache.karaf.http.core;
 
-import javax.management.MBeanException;
-import javax.management.openmbean.TabularData;
+import java.util.Map;
 
-/**
- * HTTP MBean.
- */
-public interface HttpMBean {
+public interface ProxyService {
+
+    Map<String, String> getProxies();
+
+    void addProxy(String url, String proxyTo) throws Exception;
 
-    /**
-     * List details for servlets.
-     *
-     * @return A {@link TabularData} containing the servlets information.
-     * @throws MBeanException In case of MBean failure.
-     */
-    TabularData getServlets() throws MBeanException;
+    void removeProxy(String url) throws Exception;
 
 }
diff --git a/http/src/main/java/org/apache/karaf/http/core/internal/HttpMBeanImpl.java b/http/src/main/java/org/apache/karaf/http/core/internal/HttpMBeanImpl.java
index f290aef..6717db2 100644
--- a/http/src/main/java/org/apache/karaf/http/core/internal/HttpMBeanImpl.java
+++ b/http/src/main/java/org/apache/karaf/http/core/internal/HttpMBeanImpl.java
@@ -17,7 +17,9 @@
 package org.apache.karaf.http.core.internal;
 
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 
 import javax.management.MBeanException;
 import javax.management.NotCompliantMBeanException;
@@ -31,21 +33,23 @@ import javax.management.openmbean.TabularData;
 import javax.management.openmbean.TabularDataSupport;
 import javax.management.openmbean.TabularType;
 
-import org.apache.karaf.http.core.HttpMBean;
-import org.apache.karaf.http.core.ServletInfo;
-import org.apache.karaf.http.core.ServletService;
+import org.apache.karaf.http.core.*;
 
 /**
  * Implementation of the HTTP MBean.
  */
 public class HttpMBeanImpl extends StandardMBean implements HttpMBean {
+
     private ServletService servletService;
+    private ProxyService proxyService;
 
-    public HttpMBeanImpl(ServletService servletService) throws NotCompliantMBeanException {
+    public HttpMBeanImpl(ServletService servletService, ProxyService proxyService) throws NotCompliantMBeanException {
         super(HttpMBean.class);
         this.servletService = servletService;
+        this.proxyService = proxyService;
     }
 
+    @Override
     public TabularData getServlets() throws MBeanException {
         try {
             CompositeType servletType = new CompositeType("Servlet", "HTTP Servlet",
@@ -56,12 +60,10 @@ public class HttpMBeanImpl extends StandardMBean implements HttpMBean {
             TabularData table = new TabularDataSupport(tableType);
             List<ServletInfo> servletInfos = servletService.getServlets();
             for (ServletInfo info : servletInfos) {
-            
                 CompositeData data = new CompositeDataSupport(servletType,
                         new String[]{"Bundle-ID", "Servlet", "Servlet Name", "State", "Alias", "URL"},
                         new Object[]{info.getBundleId(), info.getClassName(), info.getName(), info.getStateString(), info.getAlias(), Arrays.toString(info.getUrls())});
                 table.put(data);
-            
             }
             return table;
         } catch (Exception e) {
@@ -69,4 +71,27 @@ public class HttpMBeanImpl extends StandardMBean implements HttpMBean {
         }
     }
 
+    @Override
+    public Map<String, String> getProxies() throws MBeanException {
+        return proxyService.getProxies();
+    }
+
+    @Override
+    public void addProxy(String url, String proxyTo) throws MBeanException {
+        try {
+            proxyService.addProxy(url, proxyTo);
+        } catch (Exception e) {
+            throw new MBeanException(null, e.toString());
+        }
+    }
+
+    @Override
+    public void removeProxy(String url) throws MBeanException {
+        try {
+            proxyService.removeProxy(url);
+        } catch (Exception e) {
+            throw new MBeanException(null, e.toString());
+        }
+    }
+
 }
diff --git a/http/src/main/java/org/apache/karaf/http/core/internal/ProxyServiceImpl.java b/http/src/main/java/org/apache/karaf/http/core/internal/ProxyServiceImpl.java
new file mode 100644
index 0000000..16e481f
--- /dev/null
+++ b/http/src/main/java/org/apache/karaf/http/core/internal/ProxyServiceImpl.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.http.core.internal;
+
+import org.apache.karaf.http.core.ProxyService;
+import org.osgi.service.http.HttpService;
+
+import java.util.*;
+
+public class ProxyServiceImpl implements ProxyService {
+
+    private HttpService httpService;
+    private Map<String, String> proxies;
+
+    public ProxyServiceImpl(HttpService httpService) {
+        this.httpService = httpService;
+        this.proxies = new HashMap<>();
+    }
+
+    @Override
+    public Map<String, String> getProxies() {
+        return proxies;
+    }
+
+    @Override
+    public void addProxy(String url, String proxyTo) throws Exception {
+        ProxyServlet proxyServlet = new ProxyServlet();
+        proxyServlet.setProxyTo(proxyTo);
+        httpService.registerServlet(url, proxyServlet, new Hashtable(), null);
+        proxies.put(url, proxyTo);
+    }
+
+    @Override
+    public void removeProxy(String url) throws Exception {
+        httpService.unregister(url);
+        proxies.remove(url);
+    }
+}
diff --git a/http/src/main/java/org/apache/karaf/http/core/internal/ProxyServlet.java b/http/src/main/java/org/apache/karaf/http/core/internal/ProxyServlet.java
new file mode 100644
index 0000000..597e5dd
--- /dev/null
+++ b/http/src/main/java/org/apache/karaf/http/core/internal/ProxyServlet.java
@@ -0,0 +1,485 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.http.core.internal;
+
+import org.apache.http.*;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.AbortableHttpRequest;
+import org.apache.http.client.params.ClientPNames;
+import org.apache.http.client.params.CookiePolicy;
+import org.apache.http.client.utils.URIUtils;
+import org.apache.http.cookie.SM;
+import org.apache.http.entity.InputStreamEntity;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
+import org.apache.http.message.BasicHeader;
+import org.apache.http.message.BasicHttpEntityEnclosingRequest;
+import org.apache.http.message.BasicHttpRequest;
+import org.apache.http.message.HeaderGroup;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.HttpParams;
+import org.apache.http.util.EntityUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.reflect.Constructor;
+import java.net.HttpCookie;
+import java.net.URI;
+import java.util.BitSet;
+import java.util.Enumeration;
+import java.util.Formatter;
+import java.util.List;
+
+/**
+ * This is a simple servlet acting as a HTTP reverse proxy/gateway. It works with any webcontainer as it's a regular servlet.
+ */
+public class ProxyServlet extends HttpServlet {
+
+    private final static Logger LOGGER = LoggerFactory.getLogger(ProxyServlet.class);
+
+    protected String proxyTo;
+    protected boolean doForwardIP = true;
+    protected boolean doSendUrlFragment = true;
+
+    private HttpClient proxyClient;
+
+    public void setIPForwarding(boolean ipForwarding) {
+        this.doForwardIP = ipForwarding;
+    }
+
+    public void setProxyTo(String proxyTo) {
+        this.proxyTo = proxyTo;
+    }
+
+    @Override
+    public String getServletInfo() {
+        return "Apache Karaf Proxy Servlet";
+    }
+
+    @Override
+    public void init() throws ServletException {
+        HttpParams hcParams = new BasicHttpParams();
+        hcParams.setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.IGNORE_COOKIES);
+        proxyClient = createHttpClient(hcParams);
+    }
+
+    protected HttpClient createHttpClient(HttpParams hcParams) {
+        try {
+            Class clientClazz = Class.forName("og.apache.http.impl.client.SystemDefaultHttpClient");
+            Constructor constructor = clientClazz.getConstructor(HttpParams.class);
+            return (HttpClient) constructor.newInstance(hcParams);
+        } catch (ClassNotFoundException e) {
+            // not a problem, we fallback on the "old" client
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+
+        // fallback on using "old" client style
+        return new DefaultHttpClient(new ThreadSafeClientConnManager(), hcParams);
+    }
+
+    @Override
+    public void destroy() {
+        // starting for HttpComponents 4.3, clients implements Closeable
+        if (proxyClient instanceof Closeable) {
+            try {
+                ((Closeable) proxyClient).close();
+            } catch (IOException e) {
+                log("Error occurred when closing HTTP client in the proxy servlet destroy", e);
+            }
+        } else {
+            // older HttpComponents version requires to close the client
+            if (proxyClient != null) {
+                proxyClient.getConnectionManager().shutdown();
+            }
+        }
+        super.destroy();
+    }
+
+    @Override
+    protected void service(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws ServletException, IOException {
+        URI locationUri = URI.create(proxyTo);
+        HttpHost host = URIUtils.extractHost(locationUri);
+
+        LOGGER.debug("Proxy to {} (host {})", locationUri, host);
+
+        String method = servletRequest.getMethod();
+        String proxyRequestUri = rewriteUrlFromRequest(servletRequest, proxyTo);
+        HttpRequest proxyRequest;
+
+        // spec: RFC 2616, sec 4.3: either of these two headers means there is a message body
+        if (servletRequest.getHeader(HttpHeaders.CONTENT_LENGTH) != null || servletRequest.getHeader(HttpHeaders.TRANSFER_ENCODING) != null) {
+            HttpEntityEnclosingRequest entityProxyRequest = new BasicHttpEntityEnclosingRequest(method, proxyRequestUri);
+            entityProxyRequest.setEntity(new InputStreamEntity(servletRequest.getInputStream(), servletRequest.getContentLength()));
+            proxyRequest = entityProxyRequest;
+        } else {
+            proxyRequest = new BasicHttpRequest(method, proxyRequestUri);
+        }
+
+        copyRequestHeaders(servletRequest, proxyRequest, host);
+
+        setXForwardedForHeader(servletRequest, proxyRequest);
+
+        HttpResponse proxyResponse = null;
+        try {
+            // execute the request
+            proxyResponse = proxyClient.execute(host, proxyRequest);
+
+            // process the response
+            int statusCode = proxyResponse.getStatusLine().getStatusCode();
+
+            // copying response headers to make sure SESSIONID or other Cookie which comes from the remote host
+            // will be saved in client when the proxied URL was redirect to another one.
+            copyResponseHeaders(proxyResponse, servletRequest, servletResponse);
+
+            if (doResponseRedirect(servletRequest, servletResponse, proxyResponse, statusCode, proxyTo)) {
+                // the response is already "committed" now without any body to send
+                return;
+            }
+
+            // pass the response code
+            servletResponse.setStatus(statusCode, proxyResponse.getStatusLine().getReasonPhrase());
+
+            // send the content to the client
+            copyResponseEntity(proxyResponse, servletResponse);
+        } catch (Exception e) {
+            // abort request
+            if (proxyRequest instanceof AbortableHttpRequest) {
+                AbortableHttpRequest abortableHttpRequest = (AbortableHttpRequest) proxyRequest;
+                abortableHttpRequest.abort();
+            }
+            if (e instanceof RuntimeException) {
+                throw (RuntimeException) e;
+            }
+            if (e instanceof ServletException) {
+                throw (IOException) e;
+            }
+            throw new RuntimeException(e);
+        } finally {
+            if (proxyResponse != null) {
+                consumeQuietly(proxyResponse.getEntity());
+            }
+        }
+    }
+
+    protected boolean doResponseRedirect(HttpServletRequest servletRequest, HttpServletResponse servletResponse, HttpResponse proxyResponse, int statusCode, String proxyTo) throws ServletException, IOException {
+        // check if the proxy is a redirect
+        if (statusCode >= HttpServletResponse.SC_MULTIPLE_CHOICES && statusCode < HttpServletResponse.SC_NOT_MODIFIED) {
+            Header locationHeader = proxyResponse.getLastHeader(HttpHeaders.LOCATION);
+            if (locationHeader != null) {
+                throw new ServletException("Received a redirect (" + statusCode + ") but without location (" + HttpHeaders.LOCATION + " header)");
+            }
+            // modify the redirect to go to this proxy servlet rather than the proxied host
+            String locationString = rewriteUrlFromResponse(servletRequest, locationHeader.getValue(), proxyTo);
+            servletResponse.sendRedirect(locationString);
+            return true;
+        }
+        // 304 needs special handling.  See:
+        // http://www.ics.uci.edu/pub/ietf/http/rfc1945.html#Code304
+        // We get a 304 whenever passed an 'If-Modified-Since'
+        // header and the data on disk has not changed; server
+        // responds w/ a 304 saying I'm not going to send the
+        // body because the file has not changed.
+        if (statusCode == HttpServletResponse.SC_NOT_MODIFIED) {
+            servletResponse.setIntHeader(HttpHeaders.CONTENT_LENGTH, 0);
+            servletResponse.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+            return true;
+        }
+        return false;
+    }
+
+    protected void close(Closeable closeable) {
+        try {
+            closeable.close();
+        } catch (IOException e) {
+            log(e.getMessage(), e);
+        }
+    }
+
+    protected void consumeQuietly(HttpEntity httpEntity) {
+        try {
+            EntityUtils.consume(httpEntity);
+        } catch (IOException e) {
+            log(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * These are the "hop-by-hop" headers that should not be copied.
+     * http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
+     * We use an HttpClient HeaderGroup class instead of Set of String because this
+     * approach does case insensitive lookup faster.
+     */
+    protected static final HeaderGroup hopByHopHeaders;
+
+    static {
+        hopByHopHeaders = new HeaderGroup();
+        String[] headers = new String[] {"Connection", "Keep-Alive", "Proxy-Authenticate", "Proxy-Authorization", "TE", "Trailers", "Transfer-Encoding", "Upgrade"};
+        for (String header : headers) {
+            hopByHopHeaders.addHeader(new BasicHeader(header, null));
+        }
+    }
+
+    /**
+     * Copy request headers from the servlet client to the proxy request.
+     */
+    protected void copyRequestHeaders(HttpServletRequest servletRequest, HttpRequest proxyRequest, HttpHost host) {
+        Enumeration headerNames = servletRequest.getHeaderNames();
+        while (headerNames.hasMoreElements()) {
+            String headerName = (String) headerNames.nextElement();
+            // instead the content-length is effectively set via InputStreamEntity
+            if (headerName.equalsIgnoreCase(HttpHeaders.CONTENT_LENGTH)) {
+                continue;
+            }
+            if (hopByHopHeaders.containsHeader(headerName)) {
+                continue;
+            }
+
+            Enumeration headers = servletRequest.getHeaders(headerName);
+            while (headers.hasMoreElements()) {
+                String headerValue = (String) headers.nextElement();
+                // in case the proxy host is running multiple virtual servers, rewrite the Host header to guarantee we get content from the correct virtual server
+                if (headerName.equalsIgnoreCase(HttpHeaders.HOST)) {
+                    headerValue = host.getHostName();
+                    if (host.getPort() != -1) {
+                        headerValue += ":" + host.getPort();
+                    }
+                } else if (headerName.equalsIgnoreCase(SM.COOKIE)) {
+                    headerValue = getRealCookie(headerValue);
+                }
+                proxyRequest.addHeader(headerName, headerValue);
+            }
+        }
+    }
+
+    private void setXForwardedForHeader(HttpServletRequest servletRequest, HttpRequest proxyRequest) {
+        if (doForwardIP) {
+            String newHeader = servletRequest.getRemoteAddr();
+            String existingHeader = servletRequest.getHeader("X-Forwarded-For");
+            if (existingHeader != null) {
+                newHeader = existingHeader + ", " + newHeader;
+            }
+            proxyRequest.setHeader("X-Forwarded-For", newHeader);
+        }
+    }
+
+    protected void copyResponseHeaders(HttpResponse proxyResponse, HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
+        for (Header header : proxyResponse.getAllHeaders()) {
+            if (hopByHopHeaders.containsHeader(header.getName())) {
+                continue;
+            }
+            if (header.getName().equalsIgnoreCase(SM.SET_COOKIE) || header.getName().equalsIgnoreCase(SM.SET_COOKIE2)) {
+                copyProxyCookie(servletRequest, servletResponse, header);
+            } else {
+                servletResponse.setHeader(header.getName(), header.getValue());
+            }
+        }
+    }
+
+    /**
+     * Copy cookie from the proxy to the servlet client.
+     * Replaces cookie path to local path and renames cookie to avoid collisions.
+     */
+    protected void copyProxyCookie(HttpServletRequest servletRequest,
+                                   HttpServletResponse servletResponse, Header header) {
+        List<HttpCookie> cookies = HttpCookie.parse(header.getValue());
+        String path = servletRequest.getContextPath(); // path starts with / or is empty string
+        path += servletRequest.getServletPath(); // servlet path starts with / or is empty string
+
+        for (HttpCookie cookie : cookies) {
+            //set cookie name prefixed w/ a proxy value so it won't collide w/ other cookies
+            String proxyCookieName = getCookieNamePrefix() + cookie.getName();
+            Cookie servletCookie = new Cookie(proxyCookieName, cookie.getValue());
+            servletCookie.setComment(cookie.getComment());
+            servletCookie.setMaxAge((int) cookie.getMaxAge());
+            servletCookie.setPath(path); //set to the path of the proxy servlet
+            // don't set cookie domain
+            servletCookie.setSecure(cookie.getSecure());
+            servletCookie.setVersion(cookie.getVersion());
+            servletResponse.addCookie(servletCookie);
+        }
+    }
+
+    /**
+     * Take any client cookies that were originally from the proxy and prepare them to send to the
+     * proxy.  This relies on cookie headers being set correctly according to RFC 6265 Sec 5.4.
+     * This also blocks any local cookies from being sent to the proxy.
+     */
+    protected String getRealCookie(String cookieValue) {
+        StringBuilder escapedCookie = new StringBuilder();
+        String cookies[] = cookieValue.split("; ");
+        for (String cookie : cookies) {
+            String cookieSplit[] = cookie.split("=");
+            if (cookieSplit.length == 2) {
+                String cookieName = cookieSplit[0];
+                if (cookieName.startsWith(getCookieNamePrefix())) {
+                    cookieName = cookieName.substring(getCookieNamePrefix().length());
+                    if (escapedCookie.length() > 0) {
+                        escapedCookie.append("; ");
+                    }
+                    escapedCookie.append(cookieName).append("=").append(cookieSplit[1]);
+                }
+            }
+
+            cookieValue = escapedCookie.toString();
+        }
+        return cookieValue;
+    }
+
+    /**
+     * The string prefixing rewritten cookies.
+     */
+    protected String getCookieNamePrefix() {
+        return "!Proxy!" + getServletConfig().getServletName();
+    }
+
+    /**
+     * Copy response body data (the entity) from the proxy to the servlet client.
+     */
+    protected void copyResponseEntity(HttpResponse proxyResponse, HttpServletResponse servletResponse) throws IOException {
+        HttpEntity entity = proxyResponse.getEntity();
+        if (entity != null) {
+            OutputStream servletOutputStream = servletResponse.getOutputStream();
+            entity.writeTo(servletOutputStream);
+        }
+    }
+
+    /**
+     * Reads the request URI from {@code servletRequest} and rewrites it, considering targetUri.
+     * It's used to make the new request.
+     */
+    protected String rewriteUrlFromRequest(HttpServletRequest servletRequest, String location) {
+        StringBuilder uri = new StringBuilder(500);
+        uri.append(location);
+        // Handle the path given to the servlet
+        if (servletRequest.getPathInfo() != null) {//ex: /my/path.html
+            uri.append(encodeUriQuery(servletRequest.getPathInfo()));
+        }
+        // Handle the query string & fragment
+        String queryString = servletRequest.getQueryString();//ex:(following '?'): name=value&foo=bar#fragment
+        String fragment = null;
+        //split off fragment from queryString, updating queryString if found
+        if (queryString != null) {
+            int fragIdx = queryString.indexOf('#');
+            if (fragIdx >= 0) {
+                fragment = queryString.substring(fragIdx + 1);
+                queryString = queryString.substring(0, fragIdx);
+            }
+        }
+
+        queryString = rewriteQueryStringFromRequest(servletRequest, queryString);
+        if (queryString != null && queryString.length() > 0) {
+            uri.append('?');
+            uri.append(encodeUriQuery(queryString));
+        }
+
+        if (doSendUrlFragment && fragment != null) {
+            uri.append('#');
+            uri.append(encodeUriQuery(fragment));
+        }
+        return uri.toString();
+    }
+
+    protected String rewriteQueryStringFromRequest(HttpServletRequest servletRequest, String queryString) {
+        return queryString;
+    }
+
+    /**
+     * For a redirect response from the target server, this translates {@code theUrl} to redirect to
+     * and translates it to one the original client can use.
+     */
+    protected String rewriteUrlFromResponse(HttpServletRequest servletRequest, String theUrl, String location) {
+        if (theUrl.startsWith(location)) {
+            String curUrl = servletRequest.getRequestURL().toString(); // no query
+            String pathInfo = servletRequest.getPathInfo();
+            if (pathInfo != null) {
+                assert curUrl.endsWith(pathInfo);
+                curUrl = curUrl.substring(0, curUrl.length() - pathInfo.length()); // take pathInfo off
+            }
+            theUrl = curUrl + theUrl.substring(location.length());
+        }
+        return theUrl;
+    }
+
+    /**
+     * Encodes characters in the query or fragment part of the URI.
+     *
+     * <p>Unfortunately, an incoming URI sometimes has characters disallowed by the spec.  HttpClient
+     * insists that the outgoing proxied request has a valid URI because it uses Java's {@link URI}.
+     * To be more forgiving, we must escape the problematic characters.  See the URI class for the
+     * spec.</p>
+     *
+     * @param in The {@link CharSequence} to encode.
+     */
+    protected static CharSequence encodeUriQuery(CharSequence in) {
+        //Note that I can't simply use URI.java to encode because it will escape pre-existing escaped things.
+        StringBuilder outBuf = null;
+        Formatter formatter = null;
+        for (int i = 0; i < in.length(); i++) {
+            char c = in.charAt(i);
+            boolean escape = true;
+            if (c < 128) {
+                if (asciiQueryChars.get((int) c)) {
+                    escape = false;
+                }
+            } else if (!Character.isISOControl(c) && !Character.isSpaceChar(c)) {//not-ascii
+                escape = false;
+            }
+            if (!escape) {
+                if (outBuf != null)
+                    outBuf.append(c);
+            } else {
+                //escape
+                if (outBuf == null) {
+                    outBuf = new StringBuilder(in.length() + 5 * 3);
+                    outBuf.append(in, 0, i);
+                    formatter = new Formatter(outBuf);
+                }
+                //leading %, 0 padded, width 2, capital hex
+                formatter.format("%%%02X", (int) c);//TODO
+            }
+        }
+        return outBuf != null ? outBuf : in;
+    }
+
+    protected static final BitSet asciiQueryChars;
+
+    static {
+        char[] c_unreserved = "_-!.~'()*".toCharArray();//plus alphanum
+        char[] c_punct = ",;:$&+=".toCharArray();
+        char[] c_reserved = "?/[]@".toCharArray();//plus punct
+
+        asciiQueryChars = new BitSet(128);
+        for (char c = 'a'; c <= 'z'; c++) asciiQueryChars.set((int) c);
+        for (char c = 'A'; c <= 'Z'; c++) asciiQueryChars.set((int) c);
+        for (char c = '0'; c <= '9'; c++) asciiQueryChars.set((int) c);
+        for (char c : c_unreserved) asciiQueryChars.set((int) c);
+        for (char c : c_punct) asciiQueryChars.set((int) c);
+        for (char c : c_reserved) asciiQueryChars.set((int) c);
+
+        asciiQueryChars.set((int) '%');//leave existing percent escapes in place
+    }
+
+}
diff --git a/http/src/main/java/org/apache/karaf/http/core/internal/osgi/Activator.java b/http/src/main/java/org/apache/karaf/http/core/internal/osgi/Activator.java
index 9bff3b4..8c68b03 100644
--- a/http/src/main/java/org/apache/karaf/http/core/internal/osgi/Activator.java
+++ b/http/src/main/java/org/apache/karaf/http/core/internal/osgi/Activator.java
@@ -16,24 +16,41 @@
  */
 package org.apache.karaf.http.core.internal.osgi;
 
+import org.apache.karaf.http.core.ProxyService;
 import org.apache.karaf.http.core.ServletService;
 import org.apache.karaf.http.core.internal.HttpMBeanImpl;
+import org.apache.karaf.http.core.internal.ProxyServiceImpl;
 import org.apache.karaf.http.core.internal.ServletEventHandler;
 import org.apache.karaf.http.core.internal.ServletServiceImpl;
 import org.apache.karaf.util.tracker.BaseActivator;
 import org.apache.karaf.util.tracker.annotation.ProvideService;
+import org.apache.karaf.util.tracker.annotation.RequireService;
 import org.apache.karaf.util.tracker.annotation.Services;
 import org.ops4j.pax.web.service.spi.ServletListener;
 import org.osgi.framework.BundleEvent;
 import org.osgi.framework.BundleListener;
+import org.osgi.service.http.HttpService;
 
-@Services(provides = @ProvideService(ServletService.class))
+@Services(
+        requires = {
+                @RequireService(HttpService.class)
+        },
+        provides = {
+                @ProvideService(ServletService.class),
+                @ProvideService(ProxyService.class)
+        }
+)
 public class Activator extends BaseActivator {
 
     private BundleListener listener;
 
     @Override
     protected void doStart() throws Exception {
+        HttpService httpService = getTrackedService(HttpService.class);
+        if (httpService == null) {
+            return;
+        }
+
         final ServletEventHandler servletEventHandler = new ServletEventHandler();
         register(ServletListener.class, servletEventHandler);
 
@@ -49,13 +66,19 @@ public class Activator extends BaseActivator {
         };
         bundleContext.addBundleListener(listener);
 
-        HttpMBeanImpl httpMBean = new HttpMBeanImpl(servletService);
+        ProxyServiceImpl proxyService = new ProxyServiceImpl(httpService);
+        register(ProxyService.class, proxyService);
+
+        HttpMBeanImpl httpMBean = new HttpMBeanImpl(servletService, proxyService);
         registerMBean(httpMBean, "type=http");
     }
 
     @Override
     protected void doStop() {
-        bundleContext.removeBundleListener(listener);
+        if (listener != null) {
+            bundleContext.removeBundleListener(listener);
+            listener = null;
+        }
         super.doStop();
     }
 }
diff --git a/http/src/test/java/org/apache/karaf/http/core/internal/HttpMBeanImplTest.java b/http/src/test/java/org/apache/karaf/http/core/internal/HttpMBeanImplTest.java
index cb2f8fd..91d13f9 100644
--- a/http/src/test/java/org/apache/karaf/http/core/internal/HttpMBeanImplTest.java
+++ b/http/src/test/java/org/apache/karaf/http/core/internal/HttpMBeanImplTest.java
@@ -28,7 +28,7 @@ public class HttpMBeanImplTest {
 
     @Test
     public void testRegisterMBean() throws Exception {
-        HttpMBeanImpl httpMBean = new HttpMBeanImpl(new ServletServiceImpl(new ServletEventHandler()));
+        HttpMBeanImpl httpMBean = new HttpMBeanImpl(new ServletServiceImpl(new ServletEventHandler()), new ProxyServiceImpl(null));
         MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
         mbeanServer.registerMBean(httpMBean, new ObjectName("org.apache.karaf:type=http,name=root"));
         
diff --git a/manual/src/main/asciidoc/user-guide/webcontainer.adoc b/manual/src/main/asciidoc/user-guide/webcontainer.adoc
index 53f4432..e743756 100644
--- a/manual/src/main/asciidoc/user-guide/webcontainer.adoc
+++ b/manual/src/main/asciidoc/user-guide/webcontainer.adoc
@@ -229,6 +229,13 @@ karaf@root()> bundle:install -s "webbundle:http://tomcat.apache.org/tomcat-7.0-d
 
 You can note the `webbundle` prefix, and the `Bundle-SymbolicName` and `Web-ContextPath` headers on the URL.
 
+====== HTTP proxy
+
+Apache Karaf provides a HTTP proxy service. It allows you to proxy any HTTP URLs within Karaf. It allows you to expose
+remote web applications in Karaf.
+
+You can use the Karaf `ProxyService` programmatically, or via the corresponding shell commands and MBeans.
+
 ===== Commands
 
 ====== `http:list`
@@ -287,6 +294,36 @@ For instance, to start the Apache Karaf manual web application:
 karaf@root()> web:start 111
 ----
 
+====== `http:proxies`
+
+The `http:proxies` command list the configured HTTP proxies:
+
+----
+karaf@root()> http:proxies
+karaf@root()> http:proxies
+URL         │ ProxyTo
+────────────┼─────────────────────────────────────
+/webconsole │ http://localhost:8181/system/console
+----
+
+===== `http:proxy-add`
+
+The `http:proxy-add` registers a new HTTP proxy. For instance, you can proxy the Karaf WebConsole on another URL of your choice using:
+
+----
+karaf@root()> http:proxy-add /webconsole http://localhost:8181/system/console
+----
+
+Karaf HTTP Proxy can proxy any URL, like a backend running on Docker or a remote URL.
+
+===== `http:proxy-remove`
+
+The `http:proxy-remove` removes an existing HTTP proxy:
+
+----
+karaf@root()> http:proxy-remove /webconsole
+----
+
 ===== JMX HttpMBean
 
 On the JMX layer, you have a MBean dedicated to the manipulation of the Servlets: the HttpMBean.
@@ -303,6 +340,17 @@ The `Servlets` attribute provides a tabular data providing the list of deployed
 * `State` is the current Servlet state (`Deployed` or `Undeployed`).
 * `URL` is the URL of the Servlet (the Servlet context path).
 
+The `Proxies` attribute provides a tabular data providing the list of HTTP proxies including:
+
+* `URL` is the proxy URL.
+* `proxyTo` is the proxy target.
+* `prefix` is optional proxy prefix.
+
+====== Operations
+
+* `addProxy(url, proxyTo, prefix)` registers a new HTTP proxy.
+* `removeProxy(url)` removes an existing HTTP proxy.
+
 ===== JMX WebMBean
 
 On the JMX layer, you have a MBean dedicated to the manipulation of the Web Applications: the WebMBean.
diff --git a/webconsole/http/pom.xml b/webconsole/http/pom.xml
index e1d6eae..41077ff 100644
--- a/webconsole/http/pom.xml
+++ b/webconsole/http/pom.xml
@@ -74,6 +74,11 @@
 			<scope>provided</scope>
 		</dependency>
         <dependency>
+            <groupId>org.apache.karaf.http</groupId>
+            <artifactId>org.apache.karaf.http.core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>org.apache.karaf.features</groupId>
             <artifactId>org.apache.karaf.features.core</artifactId>
         </dependency>
diff --git a/webconsole/http/src/main/java/org/apache/karaf/webconsole/http/Activator.java b/webconsole/http/src/main/java/org/apache/karaf/webconsole/http/Activator.java
index 74744d0..e6a115e 100644
--- a/webconsole/http/src/main/java/org/apache/karaf/webconsole/http/Activator.java
+++ b/webconsole/http/src/main/java/org/apache/karaf/webconsole/http/Activator.java
@@ -20,12 +20,14 @@ import javax.servlet.Servlet;
 import java.util.Dictionary;
 import java.util.Hashtable;
 
+import org.apache.karaf.http.core.ProxyService;
 import org.apache.karaf.util.tracker.BaseActivator;
+import org.apache.karaf.util.tracker.annotation.RequireService;
 import org.apache.karaf.util.tracker.annotation.Services;
 import org.ops4j.pax.web.service.spi.ServletListener;
 import org.ops4j.pax.web.service.spi.WebListener;
 
-@Services
+@Services(requires = @RequireService(ProxyService.class))
 public class Activator extends BaseActivator {
 
     private HttpPlugin httpPlugin;
@@ -34,6 +36,11 @@ public class Activator extends BaseActivator {
 
     @Override
     protected void doStart() throws Exception {
+        ProxyService proxyService = getTrackedService(ProxyService.class);
+        if (proxyService == null) {
+            return;
+        }
+
         eaHandler = new ServletEventHandler();
         eaHandler.setBundleContext(bundleContext);
         eaHandler.init();
@@ -48,6 +55,7 @@ public class Activator extends BaseActivator {
         httpPlugin.setBundleContext(bundleContext);
         httpPlugin.setServletEventHandler(eaHandler);
         httpPlugin.setWebEventHandler(webEaHandler);
+        httpPlugin.setProxyService(proxyService);
         httpPlugin.start();
 
         Dictionary<String, String> props = new Hashtable<>();
diff --git a/webconsole/http/src/main/java/org/apache/karaf/webconsole/http/HttpPlugin.java b/webconsole/http/src/main/java/org/apache/karaf/webconsole/http/HttpPlugin.java
index 8692157..433cbad 100644
--- a/webconsole/http/src/main/java/org/apache/karaf/webconsole/http/HttpPlugin.java
+++ b/webconsole/http/src/main/java/org/apache/karaf/webconsole/http/HttpPlugin.java
@@ -34,6 +34,7 @@ import javax.servlet.http.HttpServletResponse;
 import org.apache.felix.utils.json.JSONWriter;
 import org.apache.felix.webconsole.AbstractWebConsolePlugin;
 import org.apache.felix.webconsole.WebConsoleConstants;
+import org.apache.karaf.http.core.ProxyService;
 import org.ops4j.pax.web.service.spi.ServletEvent;
 import org.ops4j.pax.web.service.spi.WebEvent;
 import org.osgi.framework.Bundle;
@@ -56,6 +57,7 @@ public class HttpPlugin extends AbstractWebConsolePlugin {
     private ServletEventHandler servletEventHandler;
     private WebEventHandler webEventHandler;
     private BundleContext bundleContext;
+    private ProxyService proxyService;
 
     @Override
     protected boolean isHtmlRequest(HttpServletRequest request) {
@@ -145,6 +147,7 @@ public class HttpPlugin extends AbstractWebConsolePlugin {
 
         final List<ServletDetails> servlets = this.getServletDetails();
         final List<WebDetail> web = this.getWebDetails();
+        final Map<String, String> proxies = proxyService.getProxies();
         final String statusLine = this.getStatusLine(servlets, web);
         final JSONWriter jw = new JSONWriter(pw);
 
@@ -193,6 +196,18 @@ public class HttpPlugin extends AbstractWebConsolePlugin {
         }
         jw.endArray();
 
+        jw.key("proxy");
+        jw.array();
+        for (String proxy : proxies.keySet()) {
+            jw.object();
+            jw.key("url");
+            jw.value(proxy);
+            jw.key("proxyTo");
+            jw.value(proxies.get(proxy));
+            jw.endObject();
+        }
+        jw.endArray();
+
         jw.endObject();
     }
 
@@ -312,4 +327,8 @@ public class HttpPlugin extends AbstractWebConsolePlugin {
         this.bundleContext = bundleContext;
     }
 
+    public void setProxyService(ProxyService proxyService) {
+        this.proxyService = proxyService;
+    }
+
 }
diff --git a/webconsole/http/src/main/resources/res/ui/http-contexts.js b/webconsole/http/src/main/resources/res/ui/http-contexts.js
index 79d6220..d1eae05 100644
--- a/webconsole/http/src/main/resources/res/ui/http-contexts.js
+++ b/webconsole/http/src/main/resources/res/ui/http-contexts.js
@@ -26,6 +26,7 @@ function renderView() {
     renderStatusLine();
     renderTable( "HTTP Contexts", "context_table", ["ID", "Servlet", "Name", "State", "Alias", "urls"] );
     renderTable( "Web Contexts", "webctxt_table", ["ID", "BundleState", "Web Context", "State"] );
+    renderTable(" HTTP Proxies", "proxy_table", ["URL", "ProxyTo"]);
     renderStatusLine();
 }    
     

-- 
To stop receiving notification emails like this one, please contact
jbonofre@apache.org.

Mime
View raw message