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

io.cdap.http.internal.HttpMethodInfo Maven / Gradle / Ivy

There is a newer version: 1.7.0
Show newest version
/*
 * Copyright © 2017-2019 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 io.cdap.http.internal;

import io.cdap.http.BodyConsumer;
import io.cdap.http.ExceptionHandler;
import io.cdap.http.HttpHandler;
import io.cdap.http.HttpResponder;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.LastHttpContent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.channels.ClosedChannelException;

/**
 * HttpMethodInfo is a helper class having state information about the http handler method to be invoked, the handler
 * and arguments required for invocation by the Dispatcher. RequestRouter populates this class and stores in its
 * context as attachment.
 */
class HttpMethodInfo {

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

  private final Method method;
  private final HttpHandler handler;
  private final HttpResponder responder;
  private final Object[] args;
  private final boolean isStreaming;
  private final ExceptionHandler exceptionHandler;

  private HttpRequest request;
  private BodyConsumer bodyConsumer;

  HttpMethodInfo(Method method, HttpHandler handler,
                 HttpResponder responder, Object[] args, ExceptionHandler exceptionHandler) {
    this.method = method;
    this.handler = handler;
    this.isStreaming = BodyConsumer.class.isAssignableFrom(method.getReturnType());
    this.responder = responder;
    this.exceptionHandler = exceptionHandler;

    // The actual arguments list to invoke handler method
    this.args = new Object[args.length + 2];
    // The actual HttpRequest object will be provided to the invoke method, since
    // the HttpObjectAggregator may create a different instance
    this.args[0] = null;
    this.args[1] = responder;
    System.arraycopy(args, 0, this.args, 2, args.length);
  }

  /**
   * Calls the httpHandler method.
   */
  void invoke(HttpRequest request) throws Exception {
    bodyConsumer = null;
    Object invokeResult;
    try {
      args[0] = this.request = request;
      invokeResult = method.invoke(handler, args);
    } catch (InvocationTargetException e) {
      exceptionHandler.handle(e.getTargetException(), request, responder);
      return;
    } catch (Throwable t) {
      exceptionHandler.handle(t, request, responder);
      return;
    }

    if (isStreaming) {
      // Casting guarantee to be succeeded.
      bodyConsumer = (BodyConsumer) invokeResult;
    }
  }

  void chunk(HttpContent chunk) throws Exception {
    if (bodyConsumer == null) {
      // If the handler method doesn't want to handle chunk request, the bodyConsumer will be null.
      // It applies to case when the handler method inspects the request and decides to decline it.
      // Usually the handler also closes the connection after declining the request.
      // However, depending on the closing time and the request,
      // there may be some chunk of data already sent by the client.
      return;
    }

    if (chunk.content().isReadable()) {
      bodyConsumerChunk(chunk.content());
    }

    if (chunk instanceof LastHttpContent) {
      bodyConsumerFinish();
    }
  }

  void disconnected() {
    if (bodyConsumer != null) {
      bodyConsumerError(new ClosedChannelException());
    }
  }

  /**
   * Calls the {@link BodyConsumer#chunk(ByteBuf, HttpResponder)} method. If the chunk method call
   * throws exception, the {@link BodyConsumer#handleError(Throwable)} will be called and this method will
   * throw {@link HandlerException}.
   */
  private void bodyConsumerChunk(ByteBuf buffer) throws HandlerException {
    try {
      bodyConsumer.chunk(buffer, responder);
    } catch (Throwable t) {
      try {
        bodyConsumerError(t);
      } catch (Throwable t2) {
        exceptionHandler.handle(t2, request, responder);
        // log original throwable since we'll lose it otherwise
        LOG.debug("Handled exception thrown from handleError. original exception from chunk call was:", t);
        return;
      }
      exceptionHandler.handle(t, request, responder);
    }
  }

  /**
   * Calls {@link BodyConsumer#finished(HttpResponder)} method. The current bodyConsumer will be set to {@code null}
   * after the call.
   */
  private void bodyConsumerFinish() {
    BodyConsumer consumer = bodyConsumer;
    bodyConsumer = null;
    try {
      consumer.finished(responder);
    } catch (Throwable t) {
      exceptionHandler.handle(t, request, responder);
    }
  }

  /**
   * Calls {@link BodyConsumer#handleError(Throwable)}. The current
   * bodyConsumer will be set to {@code null} after the call.
   */
  private void bodyConsumerError(Throwable cause) {
    BodyConsumer consumer = bodyConsumer;
    bodyConsumer = null;
    consumer.handleError(cause);
  }

  /**
   * Sends the error to responder.
   */
  void sendError(HttpResponseStatus status, Throwable ex) {
    String msg;

    if (ex instanceof InvocationTargetException) {
      msg = String.format("Exception Encountered while processing request : %s", ex.getCause().getMessage());
    } else {
      msg = String.format("Exception Encountered while processing request: %s", ex.getMessage());
    }

    // Send the status and message, followed by closing of the connection.
    responder.sendString(status, msg, new DefaultHttpHeaders().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE));
    if (bodyConsumer != null) {
      bodyConsumerError(ex);
    }
  }

  /**
   * Returns true if the handler method's return type is BodyConsumer.
   */
  boolean isStreaming() {
    return isStreaming;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy