All Downloads are FREE. Search and download functionalities are using the official Maven repository.

co.cask.http.RequestRouter Maven / Gradle / Ivy

There is a newer version: 1.1.0
Show newest version
/*
 * 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.http;

import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
import org.jboss.netty.handler.codec.http.HttpChunkAggregator;
import org.jboss.netty.handler.codec.http.HttpHeaders;
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.ssl.SslHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.atomic.AtomicBoolean;

/**
 * RequestRouter that uses {@code HttpMethodHandler} to determine the http-handler method Signature of http request. It
 * uses this signature to dynamically configure the Netty Pipeline. Http Handler methods with return-type BodyConsumer
 * will be streamed , while other methods will use HttpChunkAggregator
 */

public class RequestRouter extends SimpleChannelUpstreamHandler {

  private static final Logger LOG = LoggerFactory.getLogger(HttpDispatcher.class);

  private final int chunkMemoryLimit;
  private final HttpResourceHandler httpMethodHandler;
  private final AtomicBoolean exceptionRaised;
  private final boolean enableSSL;

  private HttpMethodInfo methodInfo;

  public RequestRouter(HttpResourceHandler methodHandler, int chunkMemoryLimit, boolean enableSSL) {
    this.httpMethodHandler = methodHandler;
    this.chunkMemoryLimit = chunkMemoryLimit;
    this.exceptionRaised = new AtomicBoolean(false);
    this.enableSSL = enableSSL;
  }

  /**
   * If the HttpRequest is valid and handled it will be sent upstream, if it cannot be invoked
   * the response will be written back immediately.
   * @param ctx
   * @param e
   * @throws Exception
   */
  @Override
  public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
    if (exceptionRaised.get()) {
      return;
    }
    Object message = e.getMessage();
    if (!(message instanceof HttpRequest)) {
      super.messageReceived(ctx, e);
      return;
    }
    HttpRequest request = (HttpRequest) message;
    if (handleRequest(request, ctx.getChannel(), ctx)) {
      ctx.sendUpstream(e);
    }
  }

  @Override
  public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
    if (enableSSL) {
      // Get the SslHandler in the current pipeline.
      final SslHandler sslHandler = ctx.getPipeline().get(SslHandler.class);

      // Get notified when SSL handshake is done.
      ChannelFuture handshakeFuture = sslHandler.handshake();
      handshakeFuture.addListener(new SSLHandshakeListener());
    }
  }

  private class SSLHandshakeListener implements ChannelFutureListener {
    @Override
    public void operationComplete(ChannelFuture channelFuture) throws Exception {
      if (!channelFuture.isSuccess()) {
        channelFuture.addListener(ChannelFutureListener.CLOSE);
      }
    }
  }
  /**
   * If return type of the handler http method is BodyConsumer, remove "HttpChunkAggregator" class if it exits
   * Else add the HttpChunkAggregator to the pipeline if its not present already.
   */
  private boolean handleRequest(HttpRequest httpRequest, Channel channel, ChannelHandlerContext ctx) throws Exception {

    methodInfo = httpMethodHandler.getDestinationMethod(
      httpRequest, new BasicHttpResponder(channel, HttpHeaders.isKeepAlive(httpRequest)));

    if (methodInfo == null) {
      return false;
    }
    ctx.setAttachment(methodInfo);
    if (methodInfo.isStreaming()) {
      if (channel.getPipeline().get("aggregator") != null) {
        channel.getPipeline().remove("aggregator");
      }
    } else {
      if (channel.getPipeline().get("aggregator") == null) {
        channel.getPipeline().addAfter("router", "aggregator", new HttpChunkAggregator(chunkMemoryLimit));
      }
    }
    return true;
  }


  public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {
    String exceptionMessage = "Exception caught in channel processing.";
    Throwable cause = e.getCause();
    if (exceptionRaised.compareAndSet(false, true)) {
      if (methodInfo != null) {
        LOG.error(exceptionMessage, cause);
        methodInfo.sendError(HttpResponseStatus.INTERNAL_SERVER_ERROR, cause);
        methodInfo = null;
      } else {
        ChannelFuture future = Channels.future(ctx.getChannel());
        future.addListener(ChannelFutureListener.CLOSE);
        if (cause instanceof HandlerException) {
          HttpResponse response = ((HandlerException) cause).createFailureResponse();
          // trace logs for user errors, error logs for internal server errors
          if (isUserError(response)) {
            LOG.trace(exceptionMessage, cause);
          } else {
            LOG.error(exceptionMessage, cause);
          }
          Channels.write(ctx, future, response);
        } else {
          LOG.error(exceptionMessage, cause);
          HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1,
                                                          HttpResponseStatus.INTERNAL_SERVER_ERROR);
          Channels.write(ctx, future, response);
        }
      }
    } else {
      LOG.trace(exceptionMessage, cause);
    }
  }

  private boolean isUserError(HttpResponse response) {
    int code = response.getStatus().getCode();
    return code == HttpResponseStatus.BAD_REQUEST.getCode() || code == HttpResponseStatus.NOT_FOUND.getCode() ||
      code == HttpResponseStatus.METHOD_NOT_ALLOWED.getCode();
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy