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    }