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}