001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.http;
019
020import java.io.File;
021import java.io.IOException;
022import java.util.regex.Pattern;
023
024import javax.servlet.ServletException;
025import javax.servlet.http.HttpServletRequest;
026import javax.servlet.http.HttpServletResponse;
027
028import org.apache.yetus.audience.InterfaceAudience;
029import org.eclipse.jetty.servlet.DefaultServlet;
030import org.slf4j.Logger;
031import org.slf4j.LoggerFactory;
032
033/**
034 * Servlet to serve files generated by {@link ProfileServlet}
035 */
036@InterfaceAudience.Private
037public class ProfileOutputServlet extends DefaultServlet {
038  private static final long serialVersionUID = 1L;
039  private static final Logger LOG = LoggerFactory.getLogger(ProfileOutputServlet.class);
040  private static final int REFRESH_PERIOD = 2;
041  // Alphanumeric characters, plus percent (url-encoding), equals, ampersand, dot and hyphen
042  private static final Pattern ALPHA_NUMERIC = Pattern.compile("[a-zA-Z0-9%=&.\\-]*");
043
044  @Override
045  protected void doGet(final HttpServletRequest req, final HttpServletResponse resp)
046    throws ServletException, IOException {
047    String absoluteDiskPath = getServletContext().getRealPath(req.getPathInfo());
048    File requestedFile = new File(absoluteDiskPath);
049    // async-profiler version 1.4 writes 'Started [cpu] profiling' to output file when profiler is
050    // running which gets replaced by final output. If final output is not ready yet, the file size
051    // will be <100 bytes (in all modes).
052    if (requestedFile.length() < 100) {
053      LOG.info(requestedFile  + " is incomplete. Sending auto-refresh header.");
054      String refreshUrl = req.getRequestURI();
055      // Rebuild the query string (if we have one)
056      if (req.getQueryString() != null) {
057        refreshUrl += "?" + sanitize(req.getQueryString());
058      }
059      ProfileServlet.setResponseHeader(resp);
060      resp.setHeader("Refresh", REFRESH_PERIOD + ";" + refreshUrl);
061      resp.getWriter().write("This page will be auto-refreshed every " + REFRESH_PERIOD +
062        " seconds until the output file is ready. Redirecting to " + refreshUrl);
063    } else {
064      super.doGet(req, resp);
065    }
066  }
067
068  static String sanitize(String input) {
069    // Basic test to try to avoid any XSS attacks or HTML content showing up.
070    // Duplicates HtmlQuoting a little, but avoid destroying ampersand.
071    if (ALPHA_NUMERIC.matcher(input).matches()) {
072      return input;
073    }
074    throw new RuntimeException("Non-alphanumeric data found in input, aborting.");
075  }
076}