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.log; 019 020import java.io.BufferedReader; 021import java.io.IOException; 022import java.io.InputStream; 023import java.io.InputStreamReader; 024import java.net.HttpURLConnection; 025import java.nio.charset.StandardCharsets; 026import java.util.regex.Matcher; 027import java.util.regex.Pattern; 028import java.util.stream.Collectors; 029import org.apache.yetus.audience.InterfaceAudience; 030 031/** 032 * HTTP utility class to help propagate server side exception in log level servlet to the client 033 * over HTTP (HTML payload) It parses HTTP client connections and recreates the exception. 034 */ 035@InterfaceAudience.Private 036public class LogLevelExceptionUtils { 037 038 private static void throwEx(Throwable ex) { 039 LogLevelExceptionUtils.<RuntimeException> throwException(ex); 040 } 041 042 @SuppressWarnings("unchecked") 043 private static <E extends Throwable> void throwException(Throwable ex) throws E { 044 throw (E) ex; 045 } 046 047 /** 048 * Validates the status of an <code>HttpURLConnection</code> against an expected HTTP status code. 049 * If the current status code is not the expected one it throws an exception with a detail message 050 * using Server side error messages if available. 051 * <p> 052 * <b>NOTE: This is an adapted version of the original method in HttpServerUtil.java of Hadoop, 053 * but we handle for HTML response. 054 * @param conn the <code>HttpURLConnection</code>. 055 * @param expectedStatus the expected HTTP status code. 056 * @throws IOException thrown if the current status code does not match the expected one. 057 */ 058 @SuppressWarnings("unchecked") 059 public static void validateResponse(HttpURLConnection conn, int expectedStatus) 060 throws IOException { 061 if (conn.getResponseCode() != expectedStatus) { 062 Exception toThrow = null; 063 064 try (InputStream es = conn.getErrorStream()) { 065 if (es != null) { 066 try (InputStreamReader isr = new InputStreamReader(es, StandardCharsets.UTF_8); 067 BufferedReader reader = new BufferedReader(isr)) { 068 final String errorAsHtml = reader.lines().collect(Collectors.joining("\n")); 069 070 final String status = extractValue(errorAsHtml, "<th>STATUS:</th><td>(\\d+)</td>"); 071 final String message = extractValue(errorAsHtml, "<th>MESSAGE:</th><td>([^<]+)</td>"); 072 final String uri = extractValue(errorAsHtml, "<th>URI:</th><td>([^<]+)</td>"); 073 final String exception = extractValue(errorAsHtml, "<title>([^<]+)</title>"); 074 075 toThrow = new IOException( 076 String.format("HTTP status [%s], message [%s], URL [%s], exception [%s]", status, 077 message, uri, exception)); 078 } 079 } 080 } catch (Exception ex) { 081 toThrow = 082 new IOException(String.format("HTTP status [%d], message [%s], URL [%s], exception [%s]", 083 conn.getResponseCode(), conn.getResponseMessage(), conn.getURL(), ex), ex); 084 } 085 if (toThrow != null) { 086 throwEx(toThrow); 087 } 088 } 089 } 090 091 private static String extractValue(String html, String regex) { 092 Pattern pattern = Pattern.compile(regex); 093 Matcher matcher = pattern.matcher(html); 094 if (matcher.find()) { 095 return matcher.group(1); 096 } 097 return null; 098 } 099 100}