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

com.twitter.finagle.buoyant.h2.Message.scala Maven / Gradle / Ivy

There is a newer version: 1.7.5
Show newest version
package com.twitter.finagle.buoyant.h2

import com.twitter.util.{Future, Promise}

/**
 * A generic HTTP2 message.
 *
 * The message itself includes the Headers that initiated the message
 * (comprising all HEADERS frames sent to initiate the message).
 *
 * Furthermore, messages have an (optional) Stream of data and trailer
 * frames.
 *
 * These types are only intended to satisfy session layer concerned
 * and are not intended to provide a rish programming interface.
 * However, it is hoped that other types may be built upon / adapated
 * to satisfy a broader set of use cases.
 */
sealed trait Message {
  def headers: Headers
  def stream: Stream

  /** Create a deep copy of the Message with a new copy of headers (but the same stream). */
  def dup(): Message
}

trait Headers {
  def toSeq: Seq[(String, String)]
  def contains(k: String): Boolean
  def get(k: String): Option[String]
  def getAll(k: String): Seq[String]
  def add(k: String, v: String): Unit
  def set(k: String, v: String): Unit
  def remove(key: String): Seq[String]

  /** Create a deep copy. */
  def dup(): Headers
}

object Headers {
  val Authority = ":authority"
  val Method = ":method"
  val Path = ":path"
  val Scheme = ":scheme"
  val Status = ":status"

  def apply(pairs: Seq[(String, String)]): Headers =
    new Impl(pairs)

  def apply(hd: (String, String), tl: (String, String)*): Headers =
    apply(hd +: tl)

  val empty: Headers = apply(Nil)

  /**
   * Typically, headers wrap underlying netty types; but since we
   * don't want to bind user code to any netty APIs, we provide
   * another header implementation that can be translated by
   * transports.
   */
  private[h2] class Impl(orig: Seq[(String, String)]) extends Headers {
    private[this] var current: Seq[(String, String)] = orig
    def toSeq = synchronized(current)
    override def toString = s"""Headers(${toSeq.mkString(", ")})"""
    def contains(key: String) = toSeq.exists { case (k, _) => key == k }
    def get(key: String) = toSeq.find(_._1 == key).map(_._2)
    def getAll(key: String) = toSeq.collect { case (k, v) if key == k => v }
    def add(key: String, value: String) = synchronized {
      current = current :+ (key -> value)
    }
    def set(key: String, v: String) = synchronized {
      remove(key)
      current = current :+ (key -> v)
    }
    def remove(key: String) = synchronized {
      val isMatch: ((String, String)) => Boolean = { case (k, _) => key == k }
      val (removed, updated) = current.partition(isMatch)
      current = updated
      removed.map(toVal)
    }
    private[this] val toVal: ((String, String)) => String = { case (_, v) => v }
    def dup() = new Impl(synchronized(current))
  }
}

/**
 * Request messages include helpers for accessing pseudo-headers.
 */
trait Request extends Message {
  def scheme: String
  def method: Method
  def authority: String
  def path: String
  override def dup(): Request
}

object Request {
  def apply(
    scheme: String,
    method: Method,
    authority: String,
    path: String,
    stream: Stream
  ): Request = Impl(
    Headers(
      Headers.Scheme -> scheme,
      Headers.Method -> method.toString,
      Headers.Authority -> authority,
      Headers.Path -> path
    ),
    stream
  )

  def apply(headers: Headers, stream: Stream): Request =
    Impl(headers, stream)

  private case class Impl(headers: Headers, stream: Stream) extends Request {
    override def toString = s"Request($scheme, $method, $authority, $path, $stream)"
    override def dup() = copy(headers = headers.dup())
    override def scheme = headers.get(Headers.Scheme).getOrElse("")
    override def method = headers.get(Headers.Method) match {
      case Some(name) => Method(name)
      case None => throw new IllegalArgumentException("missing :method header")
    }
    override def authority = headers.get(Headers.Authority).getOrElse("")
    override def path = headers.get(Headers.Path).getOrElse("/")
  }
}

trait Response extends Message {
  def status: Status
  override def dup(): Response
}

object Response {

  def apply(status: Status, stream: Stream): Response =
    Impl(Headers(Headers.Status -> status.code.toString), stream)

  def apply(headers: Headers, stream: Stream): Response =
    Impl(headers, stream)

  def unapply(arg: Response): Option[Status] =
    Some(arg.status)

  private case class Impl(
    headers: Headers,
    stream: Stream
  ) extends Response {
    override def toString = s"Response($status, $stream)"
    override def dup() = copy(headers = headers.dup())
    override def status = headers.get(Headers.Status).headOption match {
      case Some(code) => Status.fromCode(code.toInt)
      case None => throw new IllegalArgumentException(s"missing ${Headers.Status} header")
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy