Merge "Add PublishRecord Unit Tests"
[dmaap/datarouter.git] / datarouter-prov / src / main / java / org / onap / dmaap / datarouter / provisioning / ProxyServlet.java
1 /*******************************************************************************
2  * ============LICENSE_START==================================================
3  * * org.onap.dmaap
4  * * ===========================================================================
5  * * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
6  * * ===========================================================================
7  * * Licensed under the Apache License, Version 2.0 (the "License");
8  * * you may not use this file except in compliance with the License.
9  * * You may obtain a copy of the License at
10  * *
11  *  *      http://www.apache.org/licenses/LICENSE-2.0
12  * *
13  *  * Unless required by applicable law or agreed to in writing, software
14  * * distributed under the License is distributed on an "AS IS" BASIS,
15  * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * * See the License for the specific language governing permissions and
17  * * limitations under the License.
18  * * ============LICENSE_END====================================================
19  * *
20  * * ECOMP is a trademark and service mark of AT&T Intellectual Property.
21  * *
22  ******************************************************************************/
23
24
25 package org.onap.dmaap.datarouter.provisioning;
26
27 import java.io.File;
28 import java.io.FileInputStream;
29 import java.io.FileNotFoundException;
30 import java.io.IOException;
31 import java.io.InputStream;
32 import java.net.URI;
33 import java.security.KeyStore;
34 import java.security.KeyStoreException;
35 import java.util.Collections;
36 import java.util.List;
37 import java.util.Properties;
38
39 import javax.servlet.ServletConfig;
40 import javax.servlet.ServletException;
41 import javax.servlet.http.HttpServletRequest;
42 import javax.servlet.http.HttpServletResponse;
43
44 import org.apache.commons.io.IOUtils;
45 import org.apache.http.Header;
46 import org.apache.http.HttpEntity;
47 import org.apache.http.HttpResponse;
48 import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
49 import org.apache.http.client.methods.HttpGet;
50 import org.apache.http.client.methods.HttpRequestBase;
51 import org.apache.http.conn.scheme.Scheme;
52 import org.apache.http.conn.ssl.SSLSocketFactory;
53 import org.apache.http.entity.BasicHttpEntity;
54 import org.apache.http.impl.client.AbstractHttpClient;
55 import org.apache.http.impl.client.DefaultHttpClient;
56 import org.onap.dmaap.datarouter.provisioning.utils.DB;
57 import org.onap.dmaap.datarouter.provisioning.utils.URLUtilities;
58
59 /**
60  * This class is the base class for those servlets that need to proxy their requests from the
61  * standby to active server.  Its methods perform the proxy function to the active server. If the
62  * active server is not reachable, a 503 (SC_SERVICE_UNAVAILABLE) is returned.  Only
63  * DELETE/GET/PUT/POST are supported.
64  *
65  * @author Robert Eby
66  * @version $Id: ProxyServlet.java,v 1.3 2014/03/24 18:47:10 eby Exp $
67  */
68 @SuppressWarnings("serial")
69 public class ProxyServlet extends BaseServlet {
70     private boolean inited = false;
71     private Scheme sch;
72
73     /**
74      * Initialize this servlet, by setting up SSL.
75      */
76     @SuppressWarnings("deprecation")
77     @Override
78     public void init(ServletConfig config) throws ServletException {
79         super.init(config);
80         try {
81             // Set up keystore
82             Properties props = (new DB()).getProperties();
83             String type  = props.getProperty(Main.KEYSTORE_TYPE_PROPERTY, "jks");
84             String store = props.getProperty(Main.KEYSTORE_PATH_PROPERTY);
85             String pass  = props.getProperty(Main.KEYSTORE_PASSWORD_PROPERTY);
86             KeyStore keyStore = readStore(store, pass, type);
87
88             store = props.getProperty(Main.TRUSTSTORE_PATH_PROPERTY);
89             pass  = props.getProperty(Main.TRUSTSTORE_PASSWORD_PROPERTY);
90             if (store == null || store.length() == 0) {
91                 store = Main.DEFAULT_TRUSTSTORE;
92                 pass = "changeit";
93             }
94             KeyStore trustStore = readStore(store, pass, KeyStore.getDefaultType());
95
96             // We are connecting with the node name, but the certificate will have the CNAME
97             // So we need to accept a non-matching certificate name
98             SSLSocketFactory socketFactory = new SSLSocketFactory(keyStore, "changeit", trustStore);
99             socketFactory.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
100             sch = new Scheme("https", 443, socketFactory);
101             inited = true;
102         } catch (Exception e) {
103             e.printStackTrace();
104         }
105         intlogger.info("ProxyServlet: inited = "+inited);
106     }
107     private KeyStore readStore(String store, String pass, String type) throws KeyStoreException, FileNotFoundException {
108         KeyStore ks = KeyStore.getInstance(type);
109         FileInputStream instream = new FileInputStream(new File(store));
110         try {
111             ks.load(instream, pass.toCharArray());
112         } catch (Exception x) {
113             System.err.println("READING TRUSTSTORE: "+x);
114         } finally {
115             try { instream.close(); } catch (Exception ignore) {}
116         }
117         return ks;
118     }
119     /**
120      * Return <i>true</i> if the requester has NOT set the <i>noproxy</i> CGI variable.
121      * If they have, this indicates they want to forcibly turn the proxy off.
122      * @param req the HTTP request
123      * @return true or false
124      */
125     protected boolean isProxyOK(final HttpServletRequest req) {
126         String t = req.getQueryString();
127         if (t != null) {
128             t = t.replaceAll("&amp;", "&");
129             for (String s : t.split("&")) {
130                 if (s.equals("noproxy") || s.startsWith("noproxy="))
131                     return false;
132             }
133         }
134         return true;
135     }
136     /**
137      * Is this the standby server?  If it is, the proxy functions can be used.
138      * If not, the proxy functions should not be called, and will send a response of 500
139      * (Internal Server Error).
140      * @return true if this server is the standby (and hence a proxy server).
141      */
142     public boolean isProxyServer() {
143         SynchronizerTask st = SynchronizerTask.getSynchronizer();
144         return st.getState() == SynchronizerTask.STANDBY;
145     }
146     /**
147      * Issue a proxy DELETE to the active provisioning server.
148      */
149     @Override
150     public void doDelete(HttpServletRequest req, HttpServletResponse resp) throws IOException {
151         doProxy(req, resp, "DELETE");
152     }
153     /**
154      * Issue a proxy GET to the active provisioning server.
155      */
156     @Override
157     public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
158         doProxy(req, resp, "GET");
159     }
160     /**
161      * Issue a proxy PUT to the active provisioning server.
162      */
163     @Override
164     public void doPut(HttpServletRequest req, HttpServletResponse resp) throws IOException {
165         doProxy(req, resp, "PUT");
166     }
167     /**
168      * Issue a proxy POST to the active provisioning server.
169      */
170     @Override
171     public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
172         doProxy(req, resp, "POST");
173     }
174     /**
175      * Issue a proxy GET to the active provisioning server.  Unlike doGet() above,
176      * this method will allow the caller to fall back to other code if the remote server is unreachable.
177      * @return true if the proxy succeeded
178      */
179     public boolean doGetWithFallback(HttpServletRequest req, HttpServletResponse resp) throws IOException {
180         boolean rv = false;
181         if (inited) {
182             String url = buildUrl(req);
183             intlogger.info("ProxyServlet: proxying with fallback GET "+url);
184             try(AbstractHttpClient httpclient = new DefaultHttpClient()){
185             HttpRequestBase proxy = new HttpGet(url);
186             try {
187                 httpclient.getConnectionManager().getSchemeRegistry().register(sch);
188
189                 // Copy request headers and request body
190                 copyRequestHeaders(req, proxy);
191
192                 // Execute the request
193                 HttpResponse pxy_response = httpclient.execute(proxy);
194
195                 // Get response headers and body
196                 int code = pxy_response.getStatusLine().getStatusCode();
197                 resp.setStatus(code);
198                 copyResponseHeaders(pxy_response, resp);
199
200                 HttpEntity entity = pxy_response.getEntity();
201                 if (entity != null) {
202                     InputStream in = entity.getContent();
203                     IOUtils.copy(in, resp.getOutputStream());
204                     in.close();
205                 }
206                 rv = true;
207
208             } catch (IOException e) {
209                 System.err.println("ProxyServlet: "+e);
210                 e.printStackTrace();
211             } finally {
212                 proxy.releaseConnection();
213                 httpclient.getConnectionManager().shutdown();
214             }
215             }
216         } else {
217             intlogger.warn("ProxyServlet: proxy disabled");
218         }
219         return rv;
220     }
221     private void doProxy(HttpServletRequest req, HttpServletResponse resp, final String method) throws IOException {
222         if (inited && isProxyServer()) {
223             String url = buildUrl(req);
224             intlogger.info("ProxyServlet: proxying "+method + " "+url);
225             try(AbstractHttpClient httpclient = new DefaultHttpClient()) {
226                 ProxyHttpRequest proxy = new ProxyHttpRequest(method, url);
227                 try {
228                     httpclient.getConnectionManager().getSchemeRegistry().register(sch);
229
230                     // Copy request headers and request body
231                     copyRequestHeaders(req, proxy);
232                     if (method.equals("POST") || method.equals("PUT")) {
233                         BasicHttpEntity body = new BasicHttpEntity();
234                         body.setContent(req.getInputStream());
235                         body.setContentLength(-1);    // -1 = unknown
236                         proxy.setEntity(body);
237                     }
238
239                     // Execute the request
240                     HttpResponse pxy_response = httpclient.execute(proxy);
241
242                     // Get response headers and body
243                     int code = pxy_response.getStatusLine().getStatusCode();
244                     resp.setStatus(code);
245                     copyResponseHeaders(pxy_response, resp);
246
247                     HttpEntity entity = pxy_response.getEntity();
248                     if (entity != null) {
249                         InputStream in = entity.getContent();
250                         IOUtils.copy(in, resp.getOutputStream());
251                         in.close();
252                     }
253                 } catch (IOException e) {
254                     intlogger.warn("ProxyServlet: " + e);
255                     resp.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
256                     e.printStackTrace();
257                 } finally {
258                     proxy.releaseConnection();
259                     httpclient.getConnectionManager().shutdown();
260                 }
261             }
262         } else {
263             intlogger.warn("ProxyServlet: proxy disabled");
264             resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
265         }
266     }
267     private String buildUrl(HttpServletRequest req) {
268         StringBuilder sb = new StringBuilder("https://");
269         sb.append(URLUtilities.getPeerPodName());
270         sb.append(req.getRequestURI());
271         String q = req.getQueryString();
272         if (q != null)
273             sb.append("?").append(q);
274         return sb.toString();
275     }
276     private void copyRequestHeaders(HttpServletRequest from, HttpRequestBase to) {
277         @SuppressWarnings("unchecked")
278         List<String> list = Collections.list(from.getHeaderNames());
279         for (String name : list) {
280             // Proxy code will add this one
281             if (!name.equalsIgnoreCase("Content-Length"))
282                 to.addHeader(name, from.getHeader(name));
283         }
284     }
285     private void copyResponseHeaders(HttpResponse from, HttpServletResponse to) {
286         for (Header hdr : from.getAllHeaders()) {
287             // Don't copy Date: our Jetty will add another Date header
288             if (!hdr.getName().equals("Date"))
289                 to.addHeader(hdr.getName(), hdr.getValue());
290         }
291     }
292
293     public class ProxyHttpRequest extends HttpEntityEnclosingRequestBase {
294         private final String method;
295
296         public ProxyHttpRequest(final String method, final String uri) {
297             super();
298             this.method = method;
299             setURI(URI.create(uri));
300         }
301         @Override
302         public String getMethod() {
303             return method;
304         }
305     }
306 }