org.springframework.cloud.netflix.hystrix.dashboard.HystrixDashboardConfiguration.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.cloud.netflix.hystrix.dashboard.HystrixDashboardConfiguration.java

Source

/*
 * Copyright 2013-2017 the original author or authors.
 *
 * Licensed 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.springframework.cloud.netflix.hystrix.dashboard;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.cloud.client.actuator.HasFeatures;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.ui.freemarker.SpringTemplateLoader;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;

/**
 * @author Dave Syer
 * @author Roy Clarkson
 * @author Fahim Farook
 */
@Configuration
@EnableConfigurationProperties(HystrixDashboardProperties.class)
public class HystrixDashboardConfiguration {

    private static final String DEFAULT_TEMPLATE_LOADER_PATH = "classpath:/templates/";

    private static final String DEFAULT_CHARSET = "UTF-8";

    @Autowired
    private HystrixDashboardProperties dashboardProperties;

    @Bean
    public HasFeatures hystrixDashboardFeature() {
        return HasFeatures.namedFeature("Hystrix Dashboard", HystrixDashboardConfiguration.class);
    }

    /**
     * Overrides Spring Boot's {@link FreeMarkerAutoConfiguration} to prefer using a
     * {@link SpringTemplateLoader} instead of the file system. This corrects an issue
     * where Spring Boot may use an empty 'templates' file resource to resolve templates
     * instead of the packaged Hystrix classpath templates.
     * @return FreeMarker configuration
     */
    @Bean
    public FreeMarkerConfigurer freeMarkerConfigurer() {
        FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
        configurer.setTemplateLoaderPaths(DEFAULT_TEMPLATE_LOADER_PATH);
        configurer.setDefaultEncoding(DEFAULT_CHARSET);
        configurer.setPreferFileSystemAccess(false);
        return configurer;
    }

    @Bean
    public ServletRegistrationBean proxyStreamServlet() {
        final ProxyStreamServlet proxyStreamServlet = new ProxyStreamServlet();
        proxyStreamServlet.setEnableIgnoreConnectionCloseHeader(
                this.dashboardProperties.isEnableIgnoreConnectionCloseHeader());
        final ServletRegistrationBean registration = new ServletRegistrationBean(proxyStreamServlet,
                "/proxy.stream");
        registration.setInitParameters(this.dashboardProperties.getInitParameters());
        return registration;
    }

    @Bean
    public HystrixDashboardController hsytrixDashboardController() {
        return new HystrixDashboardController();
    }

    /**
     * Proxy an EventStream request (data.stream via proxy.stream) since EventStream does
     * not yet support CORS (https://bugs.webkit.org/show_bug.cgi?id=61862) so that a UI
     * can request a stream from a different server.
     */
    public static class ProxyStreamServlet extends HttpServlet {

        private static final Log log = LogFactory.getLog(ProxyStreamServlet.class);

        private static final long serialVersionUID = 1L;

        private static final String CONNECTION_CLOSE_VALUE = "close";

        private boolean enableIgnoreConnectionCloseHeader = false;

        public void setEnableIgnoreConnectionCloseHeader(boolean enableIgnoreConnectionCloseHeader) {
            this.enableIgnoreConnectionCloseHeader = enableIgnoreConnectionCloseHeader;
        }

        public ProxyStreamServlet() {
            super();
        }

        /**
         * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest
         * request, javax.servlet.http.HttpServletResponse response)
         */
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            String origin = request.getParameter("origin");
            if (origin == null) {
                response.setStatus(500);
                response.getWriter().println("Required parameter 'origin' missing. Example: 107.20.175.135:7001");
                return;
            }
            origin = origin.trim();

            HttpGet httpget = null;
            InputStream is = null;
            boolean hasFirstParameter = false;
            StringBuilder url = new StringBuilder();
            if (!origin.startsWith("http")) {
                url.append("http://");
            }
            url.append(origin);
            if (origin.contains("?")) {
                hasFirstParameter = true;
            }
            Map<String, String[]> params = request.getParameterMap();
            for (String key : params.keySet()) {
                if (!key.equals("origin")) {
                    String[] values = params.get(key);
                    String value = values[0].trim();
                    if (hasFirstParameter) {
                        url.append("&");
                    } else {
                        url.append("?");
                        hasFirstParameter = true;
                    }
                    url.append(key).append("=").append(value);
                }
            }
            String proxyUrl = url.toString();
            log.info("\n\nProxy opening connection to: " + proxyUrl + "\n\n");
            try {
                httpget = new HttpGet(proxyUrl);
                HttpClient client = ProxyConnectionManager.httpClient;
                HttpResponse httpResponse = client.execute(httpget);
                int statusCode = httpResponse.getStatusLine().getStatusCode();
                if (statusCode == HttpStatus.SC_OK) {
                    // writeTo swallows exceptions and never quits even if outputstream is
                    // throwing IOExceptions (such as broken pipe) ... since the
                    // inputstream is infinite
                    // httpResponse.getEntity().writeTo(new
                    // OutputStreamWrapper(response.getOutputStream()));
                    // so I copy it manually ...
                    is = httpResponse.getEntity().getContent();

                    // set headers
                    copyHeadersToServletResponse(httpResponse.getAllHeaders(), response);

                    // copy data from source to response
                    OutputStream os = response.getOutputStream();
                    int b = -1;
                    while ((b = is.read()) != -1) {
                        try {
                            os.write(b);
                            if (b == 10 /** flush buffer on line feed */
                            ) {
                                os.flush();
                            }
                        } catch (Exception ex) {
                            if (ex.getClass().getSimpleName().equalsIgnoreCase("ClientAbortException")) {
                                // don't throw an exception as this means the user closed
                                // the connection
                                log.debug("Connection closed by client. Will stop proxying ...");
                                // break out of the while loop
                                break;
                            } else {
                                // received unknown error while writing so throw an
                                // exception
                                throw new RuntimeException(ex);
                            }
                        }
                    }
                } else {
                    log.warn("Failed opening connection to " + proxyUrl + " : " + statusCode + " : "
                            + httpResponse.getStatusLine());
                }
            } catch (Exception ex) {
                log.error("Error proxying request: " + url, ex);
            } finally {
                if (httpget != null) {
                    try {
                        httpget.abort();
                    } catch (Exception ex) {
                        log.error("failed aborting proxy connection.", ex);
                    }
                }

                // httpget.abort() MUST be called first otherwise is.close() hangs
                // (because data is still streaming?)
                if (is != null) {
                    // this should already be closed by httpget.abort() above
                    try {
                        is.close();
                    } catch (Exception ex) {
                        // ignore errors on close
                    }
                }
            }

        }

        private void copyHeadersToServletResponse(Header[] headers, HttpServletResponse response) {
            for (Header header : headers) {
                // Some versions of Cloud Foundry (HAProxy) are
                // incorrectly setting a "Connection: close" header
                // causing the Hystrix dashboard to close the connection
                // to the stream
                // https://github.com/cloudfoundry/gorouter/issues/71
                if (this.enableIgnoreConnectionCloseHeader
                        && HttpHeaders.CONNECTION.equalsIgnoreCase(header.getName())
                        && CONNECTION_CLOSE_VALUE.equalsIgnoreCase(header.getValue())) {
                    log.warn("Ignoring 'Connection: close' header from stream response");
                } else if (!HttpHeaders.TRANSFER_ENCODING.equalsIgnoreCase(header.getName())) {
                    response.addHeader(header.getName(), header.getValue());
                }
            }
        }

        @SuppressWarnings("deprecation")
        private static class ProxyConnectionManager {

            private final static PoolingClientConnectionManager threadSafeConnectionManager = new PoolingClientConnectionManager();

            private final static HttpClient httpClient = new DefaultHttpClient(threadSafeConnectionManager);

            static {
                log.debug("Initialize ProxyConnectionManager");
                /* common settings */
                HttpParams httpParams = httpClient.getParams();
                HttpConnectionParams.setConnectionTimeout(httpParams, 5000);
                HttpConnectionParams.setSoTimeout(httpParams, 10000);

                /* number of connections to allow */
                threadSafeConnectionManager.setDefaultMaxPerRoute(400);
                threadSafeConnectionManager.setMaxTotal(400);
            }

        }

    }
}