Continued clean up of code smells
[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 static org.onap.dmaap.datarouter.provisioning.utils.HttpServletUtils.sendResponseError;
28
29 import java.io.File;
30 import java.io.FileInputStream;
31 import java.io.FileNotFoundException;
32 import java.io.IOException;
33 import java.io.InputStream;
34 import java.net.URI;
35 import java.security.KeyStore;
36 import java.security.KeyStoreException;
37 import java.util.Collections;
38 import java.util.List;
39 import java.util.Properties;
40 import javax.servlet.ServletConfig;
41 import javax.servlet.ServletException;
42 import javax.servlet.http.HttpServletRequest;
43 import javax.servlet.http.HttpServletResponse;
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 standby to active server.
61  * Its methods perform the proxy function to the active server. If the active server is not reachable, a 503
62  * (SC_SERVICE_UNAVAILABLE) is returned.  Only DELETE/GET/PUT/POST are supported.
63  *
64  * @author Robert Eby
65  * @version $Id: ProxyServlet.java,v 1.3 2014/03/24 18:47:10 eby Exp $
66  */
67 @SuppressWarnings("serial")
68
69 public class ProxyServlet extends BaseServlet {
70
71     private boolean inited = false;
72     private Scheme sch;
73
74     /**
75      * Initialize this servlet, by setting up SSL.
76      */
77     @SuppressWarnings("deprecation")
78     @Override
79     public void init(ServletConfig config) throws ServletException {
80         super.init(config);
81         try {
82             // Set up keystore
83             Properties props = (new DB()).getProperties();
84             String store = props.getProperty(Main.TRUSTSTORE_PATH_PROPERTY);
85             String pass = props.getProperty(Main.TRUSTSTORE_PASS_PROPERTY);
86             if (store == null || store.length() == 0) {
87                 store = Main.DEFAULT_TRUSTSTORE;
88                 pass = "changeit";
89             }
90             KeyStore trustStore = readStore(store, pass, KeyStore.getDefaultType());
91
92             // We are connecting with the node name, but the certificate will have the CNAME
93             // So we need to accept a non-matching certificate name
94             String type = props.getProperty(Main.KEYSTORE_TYPE_PROPERTY, "jks");
95             KeyStore keyStore = readStore(store, pass, type);
96             SSLSocketFactory socketFactory = new SSLSocketFactory(keyStore,
97                     props.getProperty(Main.KEYSTORE_PASS_PROPERTY), trustStore);
98             socketFactory.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
99             sch = new Scheme("https", 443, socketFactory);
100             inited = true;
101         } catch (Exception e) {
102             intlogger.error("ProxyServlet.init: " + e.getMessage(), e);
103         }
104         intlogger.info("ProxyServlet: inited = " + inited);
105     }
106
107     private KeyStore readStore(String store, String pass, String type) throws KeyStoreException {
108         KeyStore ks = KeyStore.getInstance(type);
109         try (FileInputStream instream = new FileInputStream(new File(store))) {
110             ks.load(instream, pass.toCharArray());
111         } catch (FileNotFoundException fileNotFoundException) {
112             intlogger.error("ProxyServlet.readStore: " + fileNotFoundException.getMessage(), fileNotFoundException);
113         } catch (Exception x) {
114             intlogger.error("READING TRUSTSTORE: " + x);
115         }
116         return ks;
117     }
118
119     /**
120      * Return <i>true</i> if the requester has NOT set the <i>noproxy</i> CGI variable. If they have, this indicates
121      * they want to forcibly turn the proxy off.
122      *
123      * @param req the HTTP request
124      * @return true or false
125      */
126     boolean isProxyOK(final HttpServletRequest req) {
127         String str = req.getQueryString();
128         if (str != null) {
129             str = str.replaceAll("&amp;", "&");
130             for (String s : str.split("&")) {
131                 if ("noproxy".equals(s) || s.startsWith("noproxy=")) {
132                     return false;
133                 }
134             }
135         }
136         return true;
137     }
138
139     /**
140      * Is this the standby server?  If it is, the proxy functions can be used. If not, the proxy functions should not be
141      * called, and will send a response of 500 (Internal Server Error).
142      *
143      * @return true if this server is the standby (and hence a proxy server).
144      */
145     boolean isProxyServer() {
146         SynchronizerTask st = SynchronizerTask.getSynchronizer();
147         return st.getPodState() == SynchronizerTask.STANDBY_POD;
148     }
149
150     /**
151      * Issue a proxy DELETE to the active provisioning server.
152      */
153     @Override
154     public void doDelete(HttpServletRequest req, HttpServletResponse resp) {
155         doProxy(req, resp, "DELETE");
156     }
157
158     /**
159      * Issue a proxy GET to the active provisioning server.
160      */
161     @Override
162     public void doGet(HttpServletRequest req, HttpServletResponse resp) {
163         doProxy(req, resp, "GET");
164     }
165
166     /**
167      * Issue a proxy PUT to the active provisioning server.
168      */
169     @Override
170     public void doPut(HttpServletRequest req, HttpServletResponse resp) {
171         doProxy(req, resp, "PUT");
172     }
173
174     /**
175      * Issue a proxy POST to the active provisioning server.
176      */
177     @Override
178     public void doPost(HttpServletRequest req, HttpServletResponse resp) {
179         doProxy(req, resp, "POST");
180     }
181
182     /**
183      * Issue a proxy GET to the active provisioning server.  Unlike doGet() above, this method will allow the caller to
184      * fall back to other code if the remote server is unreachable.
185      *
186      * @return true if the proxy succeeded
187      */
188     boolean doGetWithFallback(HttpServletRequest req, HttpServletResponse resp) {
189         boolean rv = false;
190         if (inited) {
191             String url = buildUrl(req);
192             intlogger.info("ProxyServlet: proxying with fallback GET " + url);
193             try (AbstractHttpClient httpclient = new DefaultHttpClient()) {
194                 HttpRequestBase proxy = new HttpGet(url);
195                 try {
196                     httpclient.getConnectionManager().getSchemeRegistry().register(sch);
197
198                     // Copy request headers and request body
199                     copyRequestHeaders(req, proxy);
200
201                     // Execute the request
202                     HttpResponse pxyResponse = httpclient.execute(proxy);
203
204                     // Get response headers and body
205                     int code = pxyResponse.getStatusLine().getStatusCode();
206                     resp.setStatus(code);
207                     copyResponseHeaders(pxyResponse, resp);
208                     copyEntityContent(pxyResponse, resp);
209                     rv = true;
210
211                 } catch (IOException e) {
212                     intlogger.error("ProxyServlet.doGetWithFallback: " + e.getMessage(), e);
213                 } finally {
214                     proxy.releaseConnection();
215                     httpclient.getConnectionManager().shutdown();
216                 }
217             }
218         } else {
219             intlogger.warn("ProxyServlet: proxy disabled");
220         }
221         return rv;
222     }
223
224     private void doProxy(HttpServletRequest req, HttpServletResponse resp, final String method) {
225         if (inited && isProxyServer()) {
226             String url = buildUrl(req);
227             intlogger.info("ProxyServlet: proxying " + method + " " + url);
228             try (AbstractHttpClient httpclient = new DefaultHttpClient()) {
229                 ProxyHttpRequest proxy = new ProxyHttpRequest(method, url);
230                 try {
231                     httpclient.getConnectionManager().getSchemeRegistry().register(sch);
232
233                     // Copy request headers and request body
234                     copyRequestHeaders(req, proxy);
235
236                     handlePutOrPost(req, method, proxy);
237
238                     // Execute the request
239                     HttpResponse pxyResponse = httpclient.execute(proxy);
240
241                     // Get response headers and body
242                     int code = pxyResponse.getStatusLine().getStatusCode();
243                     resp.setStatus(code);
244                     copyResponseHeaders(pxyResponse, resp);
245                     copyEntityContent(pxyResponse, resp);
246                 } catch (IOException e) {
247                     intlogger.warn("ProxyServlet.doProxy: " + e.getMessage(), e);
248                     sendResponseError(resp, HttpServletResponse.SC_SERVICE_UNAVAILABLE, "", intlogger);
249                 } finally {
250                     proxy.releaseConnection();
251                     httpclient.getConnectionManager().shutdown();
252                 }
253             }
254         } else {
255             intlogger.warn("ProxyServlet: proxy disabled");
256             sendResponseError(resp, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, DB_PROBLEM_MSG, intlogger);
257         }
258     }
259
260     private void handlePutOrPost(HttpServletRequest req, String method, ProxyHttpRequest proxy) throws IOException {
261         if ("POST".equals(method) || "PUT".equals(method)) {
262             BasicHttpEntity body = new BasicHttpEntity();
263             body.setContent(req.getInputStream());
264             body.setContentLength(-1);    // -1 = unknown
265             proxy.setEntity(body);
266         }
267     }
268
269     private String buildUrl(HttpServletRequest req) {
270         StringBuilder sb = new StringBuilder("https://");
271         sb.append(URLUtilities.getPeerPodName());
272         sb.append(req.getRequestURI());
273         String query = req.getQueryString();
274         if (query != null) {
275             sb.append("?").append(query);
276         }
277         return sb.toString();
278     }
279
280     private void copyRequestHeaders(HttpServletRequest from, HttpRequestBase to) {
281         List<String> list = Collections.list(from.getHeaderNames());
282         for (String name : list) {
283             // Proxy code will add this one
284             if (!"Content-Length".equalsIgnoreCase(name)) {
285                 to.addHeader(name, from.getHeader(name));
286             }
287         }
288     }
289
290     void copyResponseHeaders(HttpResponse from, HttpServletResponse to) {
291         for (Header hdr : from.getAllHeaders()) {
292             // Don't copy Date: our Jetty will add another Date header
293             if (!"Date".equals(hdr.getName())) {
294                 to.addHeader(hdr.getName(), hdr.getValue());
295             }
296         }
297     }
298
299     void copyEntityContent(HttpResponse pxyResponse, HttpServletResponse resp) {
300         HttpEntity entity = pxyResponse.getEntity();
301         if (entity != null) {
302             try (InputStream in = entity.getContent()) {
303                 IOUtils.copy(in, resp.getOutputStream());
304             } catch (Exception e) {
305                 intlogger.error("ProxyServlet.copyEntityContent: " + e.getMessage(), e);
306             }
307         }
308     }
309
310     public static class ProxyHttpRequest extends HttpEntityEnclosingRequestBase {
311
312         private final String method;
313
314         ProxyHttpRequest(final String method, final String uri) {
315             super();
316             this.method = method;
317             setURI(URI.create(uri));
318         }
319
320         @Override
321         public String getMethod() {
322             return method;
323         }
324     }
325 }