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 */ 018 package org.apache.hadoop.fs.http.client; 019 020 import org.apache.hadoop.classification.InterfaceAudience; 021 import org.apache.hadoop.fs.Path; 022 import org.json.simple.JSONObject; 023 import org.json.simple.parser.JSONParser; 024 import org.json.simple.parser.ParseException; 025 026 import java.io.IOException; 027 import java.io.InputStreamReader; 028 import java.lang.reflect.Constructor; 029 import java.net.HttpURLConnection; 030 import java.net.URI; 031 import java.net.URL; 032 import java.net.URLEncoder; 033 import java.text.MessageFormat; 034 import java.util.Map; 035 036 /** 037 * Utility methods used by HttpFS classes. 038 */ 039 @InterfaceAudience.Private 040 public class HttpFSUtils { 041 042 public static final String SERVICE_NAME = "/webhdfs"; 043 044 public static final String SERVICE_VERSION = "/v1"; 045 046 private static final String SERVICE_PATH = SERVICE_NAME + SERVICE_VERSION; 047 048 /** 049 * Convenience method that creates an HTTP <code>URL</code> for the 050 * HttpFSServer file system operations. 051 * <p/> 052 * 053 * @param path the file path. 054 * @param params the query string parameters. 055 * 056 * @return a <code>URL</code> for the HttpFSServer server, 057 * 058 * @throws IOException thrown if an IO error occurs. 059 */ 060 static URL createURL(Path path, Map<String, String> params) 061 throws IOException { 062 URI uri = path.toUri(); 063 String realScheme; 064 if (uri.getScheme().equalsIgnoreCase(HttpFSFileSystem.SCHEME)) { 065 realScheme = "http"; 066 } else if (uri.getScheme().equalsIgnoreCase(HttpsFSFileSystem.SCHEME)) { 067 realScheme = "https"; 068 069 } else { 070 throw new IllegalArgumentException(MessageFormat.format( 071 "Invalid scheme [{0}] it should be '" + HttpFSFileSystem.SCHEME + "' " + 072 "or '" + HttpsFSFileSystem.SCHEME + "'", uri)); 073 } 074 StringBuilder sb = new StringBuilder(); 075 sb.append(realScheme).append("://").append(uri.getAuthority()). 076 append(SERVICE_PATH).append(uri.getPath()); 077 078 String separator = "?"; 079 for (Map.Entry<String, String> entry : params.entrySet()) { 080 sb.append(separator).append(entry.getKey()).append("="). 081 append(URLEncoder.encode(entry.getValue(), "UTF8")); 082 separator = "&"; 083 } 084 return new URL(sb.toString()); 085 } 086 087 /** 088 * Validates the status of an <code>HttpURLConnection</code> against an 089 * expected HTTP status code. If the current status code is not the expected 090 * one it throws an exception with a detail message using Server side error 091 * messages if available. 092 * 093 * @param conn the <code>HttpURLConnection</code>. 094 * @param expected the expected HTTP status code. 095 * 096 * @throws IOException thrown if the current status code does not match the 097 * expected one. 098 */ 099 @SuppressWarnings({"unchecked", "deprecation"}) 100 static void validateResponse(HttpURLConnection conn, int expected) 101 throws IOException { 102 int status = conn.getResponseCode(); 103 if (status != expected) { 104 try { 105 JSONObject json = (JSONObject) HttpFSUtils.jsonParse(conn); 106 json = (JSONObject) json.get(HttpFSFileSystem.ERROR_JSON); 107 String message = (String) json.get(HttpFSFileSystem.ERROR_MESSAGE_JSON); 108 String exception = (String) 109 json.get(HttpFSFileSystem.ERROR_EXCEPTION_JSON); 110 String className = (String) 111 json.get(HttpFSFileSystem.ERROR_CLASSNAME_JSON); 112 113 try { 114 ClassLoader cl = HttpFSFileSystem.class.getClassLoader(); 115 Class klass = cl.loadClass(className); 116 Constructor constr = klass.getConstructor(String.class); 117 throw (IOException) constr.newInstance(message); 118 } catch (IOException ex) { 119 throw ex; 120 } catch (Exception ex) { 121 throw new IOException(MessageFormat.format("{0} - {1}", exception, 122 message)); 123 } 124 } catch (IOException ex) { 125 if (ex.getCause() instanceof IOException) { 126 throw (IOException) ex.getCause(); 127 } 128 throw new IOException( 129 MessageFormat.format("HTTP status [{0}], {1}", 130 status, conn.getResponseMessage())); 131 } 132 } 133 } 134 135 /** 136 * Convenience method that JSON Parses the <code>InputStream</code> of a 137 * <code>HttpURLConnection</code>. 138 * 139 * @param conn the <code>HttpURLConnection</code>. 140 * 141 * @return the parsed JSON object. 142 * 143 * @throws IOException thrown if the <code>InputStream</code> could not be 144 * JSON parsed. 145 */ 146 static Object jsonParse(HttpURLConnection conn) throws IOException { 147 try { 148 JSONParser parser = new JSONParser(); 149 return parser.parse(new InputStreamReader(conn.getInputStream())); 150 } catch (ParseException ex) { 151 throw new IOException("JSON parser error, " + ex.getMessage(), ex); 152 } 153 } 154 }