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

com.twitter.finagle.http.codec.HttpServerDispatcher.scala Maven / Gradle / Ivy

package com.twitter.finagle.http.codec

import com.twitter.finagle.Service
import com.twitter.finagle.http._
import com.twitter.finagle.http.exp.{GenSerialServerDispatcher, StreamTransport}
import com.twitter.finagle.stats.{StatsReceiver, RollupStatsReceiver}
import com.twitter.logging.Logger
import com.twitter.util.{Future, Promise, Throwables}

private[http] object HttpServerDispatcher {
  val handleHttp10: PartialFunction[Throwable, Response] = {
    case _ => Response(Version.Http10, Status.InternalServerError)
  }

  val handleHttp11: PartialFunction[Throwable, Response] = {
    case _ => Response(Version.Http11, Status.InternalServerError)
  }
}

private[finagle] class HttpServerDispatcher(
    trans: StreamTransport[Response, Request],
    service: Service[Request, Response],
    stats: StatsReceiver)
  extends GenSerialServerDispatcher[Request, Response, Response, Request](trans) {
  import HttpServerDispatcher._

  private[this] val failureReceiver =
    new RollupStatsReceiver(stats.scope("stream")).scope("failures")

  trans.onClose.ensure {
    service.close()
  }

  protected def dispatch(m: Request): Future[Response] = m match {
    case badReq: BadReq =>
      val resp = badReq match {
        case _: ContentTooLong =>
          Response(badReq.version, Status.RequestEntityTooLarge)
        case _: UriTooLong =>
          Response(badReq.version, Status.RequestURITooLong)
        case _: HeaderFieldsTooLarge =>
          Response(badReq.version, Status.RequestHeaderFieldsTooLarge)
        case _ =>
          Response(badReq.version, Status.BadRequest)
      }
      // The connection is unusable so we close it here.
      // Note that state != Idle while inside dispatch
      // so state will be set to Closed but trans.close
      // will not be called. Instead, isClosing will be
      // set to true, keep-alive headers set correctly
      // in handle, and trans.close will be called in
      // the respond statement of loop().
      close()
      Future.value(resp)

    case req: Request =>
      val handleFn = req.version match {
        case Version.Http10 => handleHttp10
        case _ => handleHttp11
      }
      service(req).handle(handleFn)

    case invalid =>
      Future.exception(new IllegalArgumentException("Invalid message "+invalid))
  }

  protected def handle(rep: Response): Future[Unit] = {
    setKeepAlive(rep, !isClosing)
    if (rep.isChunked) {
      // We remove content length here in case the content is later
      // compressed. This is a pretty bad violation of modularity;
      // this is likely an issue with the Netty content
      // compressors, which (should?) adjust headers regardless of
      // transfer encoding.
      rep.headerMap.remove(Fields.ContentLength)
      rep.headerMap.set(Fields.TransferEncoding, "chunked")

      val p = new Promise[Unit]
      val f = trans.write(rep)
      f.proxyTo(p)
      // This awkwardness is unfortunate but necessary for now as you may be
      // interrupted in the middle of a write, or when there otherwise isn’t
      // an outstanding read (e.g. read-write race).
      f.onFailure { t =>
        Logger.get(this.getClass.getName).debug(t, "Failed mid-stream. Terminating stream, closing connection")
        failureReceiver.counter(Throwables.mkString(t): _*).incr()
        rep.reader.discard()
      }
      p.setInterruptHandler { case intr =>
        rep.reader.discard()
        f.raise(intr)
      }
      p
    } else {
      // Ensure Content-Length is set if not chunked
      if (!rep.contentLength.isDefined)
        rep.contentLength = rep.content.length

      trans.write(rep)
    }
  }

  protected def setKeepAlive(rep: Response, keepAlive: Boolean): Unit = {
    rep.version match {
      case Version.Http10 =>
        if (keepAlive) {
          rep.headers.set(Fields.Connection, "keep-alive")
        } else {
          rep.headers.remove(Fields.Connection)
        }
      case Version.Http11 =>
        if (keepAlive) {
          rep.headers.remove(Fields.Connection)
        } else {
          rep.headers.set(Fields.Connection, "close")
        }
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy