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