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.hdfs.tools.offlineImageViewer;
019
020import java.io.FileNotFoundException;
021import java.io.IOException;
022import java.util.List;
023import java.util.Map;
024
025import org.apache.commons.logging.Log;
026import org.apache.commons.logging.LogFactory;
027import org.jboss.netty.channel.ChannelFutureListener;
028import org.jboss.netty.channel.ChannelHandlerContext;
029import org.jboss.netty.channel.MessageEvent;
030import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
031import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
032import org.jboss.netty.handler.codec.http.HttpHeaders;
033import org.jboss.netty.handler.codec.http.HttpMethod;
034import org.jboss.netty.handler.codec.http.HttpRequest;
035import org.jboss.netty.handler.codec.http.HttpResponse;
036import org.jboss.netty.handler.codec.http.HttpResponseStatus;
037import org.jboss.netty.handler.codec.http.HttpVersion;
038import org.jboss.netty.handler.codec.http.QueryStringDecoder;
039
040/**
041 * Implement the read-only WebHDFS API for fsimage.
042 */
043public class FSImageHandler extends SimpleChannelUpstreamHandler {
044  public static final Log LOG = LogFactory.getLog(FSImageHandler.class);
045  private final FSImageLoader loader;
046
047  public FSImageHandler(FSImageLoader loader) throws IOException {
048    this.loader = loader;
049  }
050
051  @Override
052  public void messageReceived(
053      ChannelHandlerContext ctx, MessageEvent e) throws Exception {
054    String op = getOp(e);
055    try {
056      String path = getPath(e);
057      handleOperation(op, path, e);
058    } catch (Exception ex) {
059      notFoundResponse(e);
060      LOG.warn(ex.getMessage());
061    } finally {
062      e.getFuture().addListener(ChannelFutureListener.CLOSE);
063    }
064  }
065
066  /** return the op parameter in upper case */
067  private String getOp(MessageEvent e) {
068    Map<String, List<String>> parameters = getDecoder(e).getParameters();
069    if (parameters.containsKey("op")) {
070      return parameters.get("op").get(0).toUpperCase();
071    } else {
072      // return "" to avoid NPE
073      return "";
074    }
075  }
076
077  private String getPath(MessageEvent e) throws FileNotFoundException {
078    String path = getDecoder(e).getPath();
079    // trim "/webhdfs/v1" to keep compatibility with WebHDFS API
080    if (path.startsWith("/webhdfs/v1/")) {
081      return path.replaceFirst("/webhdfs/v1", "");
082    } else {
083      throw new FileNotFoundException("Path: " + path + " should " +
084          "start with \"/webhdfs/v1/\"");
085    }
086  }
087
088  private QueryStringDecoder getDecoder(MessageEvent e) {
089    HttpRequest request = (HttpRequest) e.getMessage();
090    return new QueryStringDecoder(request.getUri());
091  }
092
093  private void handleOperation(String op, String path, MessageEvent e)
094      throws IOException {
095    HttpRequest request = (HttpRequest) e.getMessage();
096    HttpResponse response = new DefaultHttpResponse(
097        HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
098    response.setHeader(HttpHeaders.Names.CONTENT_TYPE,
099        "application/json");
100    String content = null;
101
102    if (request.getMethod() == HttpMethod.GET){
103      if (op.equals("GETFILESTATUS")) {
104        content = loader.getFileStatus(path);
105      } else if (op.equals("LISTSTATUS")) {
106        content = loader.listStatus(path);
107      } else if (op.equals("GETACLSTATUS")) {
108        content = loader.getAclStatus(path);
109      } else {
110        response.setStatus(HttpResponseStatus.BAD_REQUEST);
111      }
112    } else {
113      // only HTTP GET is allowed since fsimage is read-only.
114      response.setStatus(HttpResponseStatus.METHOD_NOT_ALLOWED);
115    }
116
117    if (content != null) {
118      HttpHeaders.setContentLength(response, content.length());
119    }
120    e.getChannel().write(response);
121
122    if (content != null) {
123      e.getChannel().write(content);
124    }
125
126    LOG.info(response.getStatus().getCode() + " method="
127        + request.getMethod().getName() + " op=" + op + " target=" + path);
128  }
129
130  private void notFoundResponse(MessageEvent e) {
131    HttpResponse response = new DefaultHttpResponse(
132        HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND);
133    e.getChannel().write(response);
134  }
135}