Package co.cask.cdap.internal.app.runtime.procedure

Source Code of co.cask.cdap.internal.app.runtime.procedure.ProcedureDispatcher

/*
* Copyright © 2014 Cask Data, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/

package co.cask.cdap.internal.app.runtime.procedure;

import co.cask.cdap.api.procedure.ProcedureRequest;
import co.cask.cdap.common.metrics.MetricsCollector;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBufferInputStream;
import org.jboss.netty.buffer.ChannelBufferOutputStream;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;
import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
import org.jboss.netty.handler.codec.http.HttpHeaders;
import org.jboss.netty.handler.codec.http.HttpMethod;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
import org.jboss.netty.handler.codec.http.HttpVersion;
import org.jboss.netty.handler.codec.http.QueryStringDecoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nullable;

/**
* This class dispatch HTTP requests to HandlerMethod. It uses thread local to control
* how many instances of HandlerMethod created, hence it is supposed to be used shared
* around all ChannelPipeline.
*/
final class ProcedureDispatcher extends SimpleChannelHandler {

  private static final Logger LOG = LoggerFactory.getLogger(ProcedureDispatcher.class);
  private static final Type REQUEST_TYPE = new TypeToken<Map<String, String>>() { }.getType();
  private static final Pattern REQUEST_URI_PATTERN = Pattern.compile("apps/(.+)/procedures/(.+)/methods/(.+)$");
  private static final Pattern METHOD_GET_PATTERN = Pattern.compile("^(.*?)[?]");
  private static final Gson GSON = new Gson();
  private static final Type QUERY_PARAMS_TYPE = new TypeToken<Map<String, String>>() { }.getType();
  private static final Maps.EntryTransformer<String, List<String>, String> MULTIMAP_TO_MAP_FUNCTION =
    new Maps.EntryTransformer<String, List<String>, String>() {
      @Override
      public String transformEntry(@Nullable String key, @Nullable List<String> value) {
        if (value == null || value.isEmpty()) {
          return null;
        }
        return value.get(0);
      }
    };

  private final MetricsCollector metrics;
  private final ThreadLocal<HandlerMethod> handlerMethod;

  ProcedureDispatcher(final HandlerMethodFactory handlerMethodFactory, MetricsCollector metrics) {
    this.metrics = metrics;
    handlerMethod = new ThreadLocal<HandlerMethod>() {
      @Override
      protected HandlerMethod initialValue() {
        return handlerMethodFactory.create();
      }
    };
  }

  @Override
  public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
    Object message = e.getMessage();
    if (!(message instanceof HttpRequest)) {
      super.messageReceived(ctx, e);
      return;
    }

    handleRequest((HttpRequest) message, ctx.getChannel());
  }

  @Override
  public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
    LOG.error("Exception caught in channel processing.", e.getCause());
    ctx.getChannel().close();
  }

  /**
   * Sends a error response and close the channel.
   * @param status Status of the response.
   * @param channel Netty channel for output.
   */
  private void errorResponse(HttpResponseStatus status, Channel channel, String content) {
    metrics.increment("query.failed", 1);
    HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, status);
    response.setHeader(HttpHeaders.Names.CONTENT_TYPE, "text/plain; charset=utf-8");
    response.setContent(ChannelBuffers.wrappedBuffer(Charsets.UTF_8.encode(content)));
    Channels.write(channel, response).addListener(ChannelFutureListener.CLOSE);
  }

  private void handleRequest(HttpRequest httpRequest, Channel channel) {
    if (!(HttpMethod.POST.equals(httpRequest.getMethod()) || (HttpMethod.GET.equals(httpRequest.getMethod())))) {
      errorResponse(HttpResponseStatus.METHOD_NOT_ALLOWED, channel, "Only GET and POST methods are supported.");
      return;
    }

    Matcher uriMatcher = REQUEST_URI_PATTERN.matcher(httpRequest.getUri());
    if (!uriMatcher.find()) {
      errorResponse(HttpResponseStatus.BAD_REQUEST, channel, "Invalid request uri.");
      return;
    }

    String requestMethod = uriMatcher.group(3);

    ProcedureRequest request = createProcedureRequest(httpRequest, channel, requestMethod);
    if (request == null) {
      return;
    }

    // Lookup the request handler and handle
    HandlerMethod handler;
    try {
      handler = handlerMethod.get();
    } catch (Throwable t) {
      LOG.error("Fail to get procedure.", t);
      errorResponse(HttpResponseStatus.INTERNAL_SERVER_ERROR, channel, "Fail to get procedure.");
      return;
    }
    handler.handle(request, new HttpProcedureResponder(channel));
  }

  private ProcedureRequest createProcedureRequest(HttpRequest request, Channel channel, String requestMethod) {
    try {
      Map<String, String> args;
      ChannelBuffer content;
      if (HttpMethod.POST.equals(request.getMethod())) {
        content = request.getContent();
      } else {
        //GET method - Get key/value pairs from the URI
        Map<String, List<String>> queryParams = new QueryStringDecoder(request.getUri()).getParameters();
        content = ChannelBuffers.EMPTY_BUFFER;

        if (!queryParams.isEmpty()) {
          content = jsonEncode(Maps.transformEntries(queryParams, MULTIMAP_TO_MAP_FUNCTION), QUERY_PARAMS_TYPE,
                               ChannelBuffers.dynamicBuffer(request.getUri().length()));
        }
      }

      if (content == null || !content.readable()) {
        args = ImmutableMap.of();
      } else {
        args = GSON.fromJson(new InputStreamReader(new ChannelBufferInputStream(content), Charsets.UTF_8),
                             REQUEST_TYPE);
      }

      //Extract the GET method name
      Matcher methodMatcher = METHOD_GET_PATTERN.matcher(requestMethod);
      if (methodMatcher.find()) {
        requestMethod = methodMatcher.group(1);
      }

      return new DefaultProcedureRequest(requestMethod, args);

    } catch (Exception ex) {
      errorResponse(HttpResponseStatus.BAD_REQUEST, channel, "Only json map<string,string> is supported.");
    }
    return null;
  }

  private <T> ChannelBuffer jsonEncode(T obj, Type type, ChannelBuffer buffer) throws IOException {
    Writer writer = new OutputStreamWriter(new ChannelBufferOutputStream(buffer), Charsets.UTF_8);
    try {
      GSON.toJson(obj, type, writer);
    } finally {
      writer.close();
    }
    return buffer;
  }

}
TOP

Related Classes of co.cask.cdap.internal.app.runtime.procedure.ProcedureDispatcher

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.