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;
023import javax.servlet.ServletException;
024import javax.servlet.http.HttpServletRequest;
025import javax.servlet.http.HttpServletResponse;
026import org.apache.yetus.audience.InterfaceAudience;
027import org.slf4j.Logger;
028import org.slf4j.LoggerFactory;
029
030import org.apache.hbase.thirdparty.org.eclipse.jetty.servlet.DefaultServlet;
031
032/**
033 * Servlet to serve files generated by {@link ProfileServlet}
034 */
035@InterfaceAudience.Private
036public class ProfileOutputServlet extends DefaultServlet {
037  private static final long serialVersionUID = 1L;
038  private static final Logger LOG = LoggerFactory.getLogger(ProfileOutputServlet.class);
039  private static final int REFRESH_PERIOD = 2;
040  // Alphanumeric characters, plus percent (url-encoding), equals, ampersand, dot and hyphen
041  private static final Pattern ALPHA_NUMERIC = Pattern.compile("[a-zA-Z0-9%=&.\\-]*");
042
043  @Override
044  protected void doGet(final HttpServletRequest req, final HttpServletResponse resp)
045    throws ServletException, IOException {
046    String absoluteDiskPath = getServletContext().getRealPath(req.getPathInfo());
047    File requestedFile = new File(absoluteDiskPath);
048    // async-profiler version 1.4 writes 'Started [cpu] profiling' to output file when profiler is
049    // running which gets replaced by final output. If final output is not ready yet, the file size
050    // will be <100 bytes (in all modes).
051    if (requestedFile.length() < 100) {
052      LOG.info(requestedFile + " is incomplete. Sending auto-refresh header.");
053      String refreshUrl = req.getRequestURI();
054      // Rebuild the query string (if we have one)
055      if (req.getQueryString() != null) {
056        refreshUrl += "?" + sanitize(req.getQueryString());
057      }
058      ProfileServlet.setResponseHeader(resp);
059      resp.setHeader("Refresh", REFRESH_PERIOD + ";" + refreshUrl);
060      resp.getWriter().write("This page will be auto-refreshed every " + REFRESH_PERIOD
061        + " seconds until the output file is ready. Redirecting to " + refreshUrl);
062    } else {
063      super.doGet(req, resp);
064    }
065  }
066
067  static String sanitize(String input) {
068    // Basic test to try to avoid any XSS attacks or HTML content showing up.
069    // Duplicates HtmlQuoting a little, but avoid destroying ampersand.
070    if (ALPHA_NUMERIC.matcher(input).matches()) {
071      return input;
072    }
073    throw new RuntimeException("Non-alphanumeric data found in input, aborting.");
074  }
075}