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

com.twitter.finagle.http.Request.scala Maven / Gradle / Ivy

package com.twitter.finagle.http

import com.twitter.collection.RecordSchema
import com.twitter.finagle.http.exp.Multipart
import com.twitter.finagle.http.netty.Bijections
import com.twitter.io.{Charsets, Reader}
import java.net.{InetAddress, InetSocketAddress}
import java.util.{AbstractMap, List => JList, Map => JMap, Set => JSet}
import org.jboss.netty.buffer.{ChannelBuffer, ChannelBuffers}
import org.jboss.netty.channel.Channel
import org.jboss.netty.handler.codec.embedder.{DecoderEmbedder, EncoderEmbedder}
import org.jboss.netty.handler.codec.http._
import scala.beans.BeanProperty
import scala.annotation.varargs
import scala.collection.JavaConverters._

import Bijections._

/**
 * Rich HttpRequest.
 *
 * Use RequestProxy to create an even richer subclass.
 */
abstract class Request extends Message {

  /**
   * Arbitrary user-defined context associated with this request object.
   * [[com.twitter.collection.RecordSchema.Record RecordSchema.Record]] is
   * used here, rather than [[com.twitter.finagle.context.Context Context]] or similar
   * out-of-band mechanisms, to make the connection between the request and its
   * associated context explicit.
   */
  def ctx: Request.Schema.Record = _ctx
  private[this] val _ctx = Request.Schema.newRecord()

  def isRequest = true

  /**
   * Returns a [[ParamMap]] instance, which maintains query string and url-encoded
   * params associated with this request.
   */
  def params: ParamMap = _params
  private[this] lazy val _params: ParamMap = new RequestParamMap(this)

  /**
   * Returns an _optional_ [[Multipart]] instance, which maintains the
   * `multipart/form-data` content of this non-chunked, POST request. If
   * this requests is either streaming or non-POST, this method returns
   * `None`.
   *
   * Note: This method is a part of an experimental API for handling
   * multipart HTTP data and it will likely be changed in future in order
   * to support streaming requests.
   */
  def multipart: Option[Multipart] = _multipart
  private[this] lazy val _multipart: Option[Multipart] =
    if (!isChunked && method == Method.Post)
      Some(Multipart.decodeNonChunked(this))
    else None

  /**
   * Returns the HTTP method of this request.
   */
  def method: Method = from(getMethod())

  /**
   * Sets the HTTP method of this request to the given `method`.
   */
  def method_=(method: Method) = setMethod(from(method))

  /**
   * Returns the URI of this request.
   */
  def uri: String = getUri()

  /**
   * Set the URI of this request to the given `uri`.
   */
  def uri_=(uri: String) = setUri(uri)

  /** Path from URI. */
  @BeanProperty
  def path: String = {
    val u = uri
    u.indexOf('?') match {
      case -1 => u
      case n  => u.substring(0, n)
    }
  }

  /** File extension.  Empty string if none. */
  @BeanProperty
  def fileExtension: String = {
    val p = path
    val leaf = p.lastIndexOf('/') match {
      case -1 => p
      case n  => p.substring(n + 1)
    }
    leaf.lastIndexOf('.') match {
      case -1 => ""
      case n  => leaf.substring(n + 1).toLowerCase
    }
  }

  /**
   * The InetSocketAddress of the client or a place-holder
   * ephemeral address for requests that have yet to be dispatched.
   */
  @BeanProperty
  def remoteSocketAddress: InetSocketAddress

  /** Remote host - a dotted quad */
  @BeanProperty
  def remoteHost: String =
    remoteAddress.getHostAddress

  /** Remote InetAddress */
  @BeanProperty
  def remoteAddress: InetAddress =
    remoteSocketAddress.getAddress

  /** Remote port */
  @BeanProperty
  def remotePort: Int =
    remoteSocketAddress.getPort

  // The get*Param methods below are for Java compatibility.  Note Scala default
  // arguments aren't compatible with Java, so we need two versions of each.

  /** Get parameter value.  Returns value or null. */
  def getParam(name: String): String =
    params.get(name).orNull

  /** Get parameter value.  Returns value or default. */
  def getParam(name: String, default: String): String =
    params.get(name).getOrElse(default)

  /** Get Short param.  Returns value or 0. */
  def getShortParam(name: String): Short =
    params.getShortOrElse(name, 0)

  /** Get Short param.  Returns value or default. */
  def getShortParam(name: String, default: Short): Short =
    params.getShortOrElse(name, default)

  /** Get Int param.  Returns value or 0. */
  def getIntParam(name: String): Int =
    params.getIntOrElse(name, 0)

  /** Get Int param.  Returns value or default. */
  def getIntParam(name: String, default: Int): Int =
    params.getIntOrElse(name, default)

  /** Get Long param.  Returns value or 0. */
  def getLongParam(name: String): Long =
    params.getLongOrElse(name, 0L)

  /** Get Long param.  Returns value or default. */
  def getLongParam(name: String, default: Long=0L): Long =
    params.getLongOrElse(name, default)

  /** Get Boolean param.  Returns value or false. */
  def getBooleanParam(name: String): Boolean =
    params.getBooleanOrElse(name, false)

  /** Get Boolean param.  Returns value or default. */
  def getBooleanParam(name: String, default: Boolean): Boolean =
    params.getBooleanOrElse(name, default)

  /** Get all values of parameter.  Returns list of values. */
  def getParams(name: String): JList[String] =
    params.getAll(name).toList.asJava

  /** Get all parameters. */
  def getParams(): JList[JMap.Entry[String, String]] =
    (params.toList.map { case (k, v) =>
      // cast to appease asJava
      (new AbstractMap.SimpleImmutableEntry(k, v)).asInstanceOf[JMap.Entry[String, String]]
    }).asJava

  /** Check if parameter exists. */
  def containsParam(name: String): Boolean =
    params.contains(name)

  /** Get parameters names. */
  def getParamNames(): JSet[String] =
    params.keySet.asJava

  /** Response associated with request */
  lazy val response: Response = Response(this)

  /** Get response associated with request. */
  def getResponse(): Response = response

  /** Encode an HTTP message to String */
  def encodeString(): String = {
    new String(encodeBytes(), "UTF-8")
  }

  /** Encode an HTTP message to Array[Byte] */
  def encodeBytes(): Array[Byte] = {
    val encoder = new EncoderEmbedder[ChannelBuffer](new HttpRequestEncoder)
    encoder.offer(from[Request, HttpRequest](this))
    val buffer = encoder.poll()
    val bytes = new Array[Byte](buffer.readableBytes())
    buffer.readBytes(bytes)
    bytes
  }

  override def toString: String =
    "Request(\"" + method + " " + uri + "\", from " + remoteSocketAddress + ")"

  protected[finagle] def httpRequest: HttpRequest
  protected[finagle] def getHttpRequest(): HttpRequest = httpRequest
  protected[finagle] def httpMessage: HttpMessage = httpRequest

  protected[finagle] def getMethod(): HttpMethod = httpRequest.getMethod
  protected[finagle] def setMethod(method: HttpMethod) { httpRequest.setMethod(method) }
  protected[finagle] def getUri(): String = httpRequest.getUri()
  protected[finagle] def setUri(uri: String) { httpRequest.setUri(uri) }
}


object Request {

  /**
   * [[com.twitter.collection.RecordSchema RecordSchema]] declaration, used
   * to generate [[com.twitter.collection.RecordSchema.Record Record]] instances
   * for Request.ctx.
   */
  val Schema: RecordSchema = new RecordSchema

  /** Decode a Request from a String */
  def decodeString(s: String): Request = {
    decodeBytes(s.getBytes(Charsets.Utf8))
  }

  /** Decode a Request from Array[Byte] */
  def decodeBytes(b: Array[Byte]): Request = {
    val decoder = new DecoderEmbedder(
      new HttpRequestDecoder(Int.MaxValue, Int.MaxValue, Int.MaxValue))
    decoder.offer(ChannelBuffers.wrappedBuffer(b))
    val req = decoder.poll().asInstanceOf[HttpRequest]
    assert(req ne null)
    new Request {
      val httpRequest = req
      lazy val remoteSocketAddress = new InetSocketAddress(0)
    }
  }

  /**
   * Create an HTTP/1.1 GET Request from query string parameters.
   *
   * @param params a list of key-value pairs representing the query string.
   */
  @varargs
  def apply(params: Tuple2[String, String]*): Request =
    apply("/", params:_*)

  /**
   * Create an HTTP/1.1 GET Request from URI and query string parameters.
   *
   * @param params a list of key-value pairs representing the query string.
   */
  def apply(uri: String, params: Tuple2[String, String]*): Request = {
    val encoder = new QueryStringEncoder(uri)
    params.foreach { case (key, value) =>
      encoder.addParam(key, value)
    }
    apply(Method.Get, encoder.toString)
  }

  /**
   * Create an HTTP/1.1 GET Request from URI string.
   * */
  def apply(uri: String): Request =
    apply(Method.Get, uri)

  /**
   * Create an HTTP/1.1 GET Request from method and URI string.
   */
  def apply(method: Method, uri: String): Request =
    apply(Version.Http11, method, uri)

  /**
   * Create an HTTP/1.1 GET Request from version, method, and URI string.
   */
  def apply(version: Version, method: Method, uri: String): Request = {
    val reqIn = new DefaultHttpRequest(from(version), from(method), uri)
    new Request {
      val httpRequest = reqIn
      lazy val remoteSocketAddress = new InetSocketAddress(0)
    }
  }

  /**
   * Create an HTTP/1.1 GET Request from Version, Method, URI, and Reader.
   *
   * A [[com.twitter.io.Reader]] is a stream of bytes serialized to HTTP chunks.
   * `Reader`s are useful for representing streaming data in the body of the
   * request (e.g. a large file, or long lived computation that produces results
   * incrementally).
   *
   * {{{
   * val data = Reader.fromStream(File.open("data.txt"))
   * val post = Request(Http11, Post, "/upload", data)
   *
   * client(post) onSuccess {
   *   case r if r.status == Ok => println("Success!")
   *   case _                   => println("Something went wrong...")
   * }
   * }}}
   */
  def apply(
    version: Version,
    method: Method,
    uri: String,
    reader: Reader
  ): Request = {
    val httpReq = new DefaultHttpRequest(from(version), from(method), uri)
    httpReq.setChunked(true)
    apply(httpReq, reader, new InetSocketAddress(0))
  }

  private[finagle] def apply(
    version: Version,
    method: Method,
    uri: String,
    reader: Reader,
    remoteAddr: InetSocketAddress
  ): Request = {
    val httpReq = new DefaultHttpRequest(from(version), from(method), uri)
    httpReq.setChunked(true)
    apply(httpReq, reader, remoteAddr)
  }

  private[http] def apply(
    reqIn: HttpRequest,
    readerIn: Reader,
    remoteAddr: InetSocketAddress
  ): Request = new Request {
    override val reader = readerIn
    val httpRequest = reqIn
    lazy val remoteSocketAddress = remoteAddr
  }

  /** Create Request from HttpRequest and Channel.  Used by Codec. */
  private[finagle] def apply(httpRequestArg: HttpRequest, channel: Channel): Request =
    new Request {
      val httpRequest = httpRequestArg
      lazy val remoteSocketAddress = channel.getRemoteAddress.asInstanceOf[InetSocketAddress]
    }

  /** Create a query string from URI and parameters. */
  def queryString(uri: String, params: Tuple2[String, String]*): String = {
    val encoder = new QueryStringEncoder(uri)
    params.foreach { case (key, value) =>
      encoder.addParam(key, value)
    }
    encoder.toString
  }

  /**
   * Create a query string from parameters.  The results begins with "?" only if
   * params is non-empty.
   */
  def queryString(params: Tuple2[String, String]*): String =
    queryString("", params: _*)

  /** Create a query string from URI and parameters. */
  def queryString(uri: String, params: Map[String, String]): String =
    queryString(uri, params.toSeq: _*)

  /**
   * Create a query string from parameters.  The results begins with "?" only if
   * params is non-empty.
   */
  def queryString(params: Map[String, String]): String =
    queryString("", params.toSeq: _*)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy