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

com.twitter.finagle.netty4.http.handler.BadRequestHandler.scala Maven / Gradle / Ivy

The newest version!
package com.twitter.finagle.netty4.http.handler

import com.twitter.finagle.http.headers.Rfc7230HeaderValidation
import com.twitter.finagle.http.BadRequestResponse
import com.twitter.finagle.http.Response
import com.twitter.finagle.netty4.http.Bijections
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.logging.Logger
import io.netty.channel.ChannelFutureListener
import io.netty.channel.ChannelHandler.Sharable
import io.netty.channel.ChannelHandlerContext
import io.netty.channel.ChannelInboundHandlerAdapter
import io.netty.handler.codec.TooLongFrameException
import io.netty.handler.codec.http.HttpMessage
import io.netty.handler.codec.http.HttpObject
import io.netty.util.ReferenceCountUtil

object BadRequestHandler {
  val log = Logger()
  val HandlerName: String = "badRequestHandler"
}

/**
 * A `ChannelHandler` for turning http decoding errors into meaningful responses.
 *
 * The only errors that we explicitly handle are from the  HttpObjectDecoder, specifically the
 * request line and header lengths. A message chunk aggregator, if it exists, handles its own
 * errors. When an error is observed, we generate an appropriate response and close the pipeline
 * after the write operation is complete.
 *
 * @see [[ClientExceptionMapper]] for a client-side implementation of this handler
 */
@Sharable
private[netty4] class BadRequestHandler(stats: StatsReceiver) extends ChannelInboundHandlerAdapter {

  private[this] val invalidHeaderNames = stats.counter("rejected_invalid_header_names")
  private[this] val invalidHeaderValues = stats.counter("rejected_invalid_header_values")

  import BadRequestHandler._

  override def channelRead(ctx: ChannelHandlerContext, msg: Any): Unit = msg match {
    // NOTE: HttpObjectDecoder sets the DecoderResult on an inbound HTTP message when either
    // headers or initial line exceed a desired limit.
    case obj: HttpObject if obj.decoderResult.isFailure =>
      ReferenceCountUtil.release(obj)
      handleDecodeFailure(ctx, obj)

    case _ =>
      ctx.fireChannelRead(msg)
  }

  private[this] def handleDecodeFailure(ctx: ChannelHandlerContext, obj: HttpObject): Unit =
    obj match {
      case _: HttpMessage =>
        // We detected a decode failure in the message's initial line. We can reply back safely.
        val failure = obj.decoderResult.cause
        val resp = exceptionToResponse(failure)
        val nettyResp = Bijections.finagle.fullResponseToNetty(resp)

        failure match {
          case _: Rfc7230HeaderValidation.NameValidationException =>
            invalidHeaderNames.incr()
          case _: Rfc7230HeaderValidation.ValueValidationException =>
            invalidHeaderValues.incr()
          case _ =>
        }

        // We must close the connection on these types of failures since
        // the inbound TCP stream is in an undefined state
        ctx
          .writeAndFlush(nettyResp)
          .addListener(ChannelFutureListener.CLOSE)
      case _ =>
        // A decode failure must be from validating trailers in the last chunk. We can't really
        // reply anything as we're not sure if application-logic already replied. The only
        // meaningful thing we can do is to hang up.

        log.debug(
          "Detected invalid trailing headers in the HTTP stream. Tearing down the connection.")

        ctx.close()
    }

  private[this] def exceptionToResponse(ex: Throwable): Response = ex match {
    case ex: TooLongFrameException =>
      // Chunk aggregation is already handled by the HttpObjectAggregator, so we only need
      // to consider the request line and headers
      if (ex.getMessage.startsWith("An HTTP line is larger than "))
        BadRequestResponse.uriTooLong()
      else
        BadRequestResponse.headerTooLong()
    case _ =>
      BadRequestResponse()
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy