alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Play Framework/Scala example source code file (BasicHttpClient.scala)

This example Play Framework source code file (BasicHttpClient.scala) is included in my "Source Code Warehouse" project. The intent of this project is to help you more easily find Play Framework (and Scala) source code examples by using tags.

All credit for the original source code belongs to Play Framework; I'm just trying to make examples easier to find. (For my Scala work, see my Scala examples and tutorials.)

Play Framework tags/keywords

array, basichttpclient, basicrequest, int, map, nil, none, runtimeexception, seq, string

The BasicHttpClient.scala Play Framework example source code

/*
 * Copyright (C) 2009-2013 Typesafe Inc. <http://www.typesafe.com>
 */
package play.it.http

import java.net.Socket
import java.io.{InputStreamReader, BufferedReader, OutputStreamWriter}
import play.api.test.Helpers._
import org.apache.commons.io.IOUtils

object BasicHttpClient {

  /**
   * Very basic HTTP client, for when we want to be very low level about our assertions.
   *
   * Can only work with requests that are entirely ascii, any binary or multi byte characters and it will break.
   *
   * @param port The port to connect to
   * @param checkClosed Whether to check if the channel is closed after receiving the responses
   * @param trickleFeed A timeout to use between sending request body chunks
   * @param requests The requests to make
   * @return The parsed number of responses.  This may be more than the number of requests, if continue headers are sent.
   */
  def makeRequests(port: Int, checkClosed: Boolean = false, trickleFeed: Option[Long] = None)(requests: BasicRequest*): Seq[BasicResponse] = {
    val client = new BasicHttpClient(port)

    try {
      var requestNo = 0
      val responses = requests.flatMap { request =>
        requestNo += 1
        client.sendRequest(request, requestNo.toString, trickleFeed = trickleFeed)
      }

      if (checkClosed) {
        val line = client.reader.readLine()
        if (line != null) {
          throw new RuntimeException("Unexpected data after responses received: " + line)
        }
      }

      responses

    } finally {
      client.close()
    }
  }

  def pipelineRequests(port: Int, requests: BasicRequest*): Seq[BasicResponse] = {
    val client = new BasicHttpClient(port)

    try {
      var requestNo = 0
      requests.foreach { request =>
        requestNo += 1
        client.sendRequest(request, requestNo.toString, waitForResponses = false)
      }
      for (i <- 0 until requests.length) yield {
        client.readResponse(requestNo.toString)
      }
    } finally {
      client.close()
    }
  }
}

class BasicHttpClient(port: Int) {
  val s = new Socket("localhost", port)
  s.setSoTimeout(5000)
  val out = new OutputStreamWriter(s.getOutputStream)
  val reader = new BufferedReader(new InputStreamReader(s.getInputStream))

  /**
   * Send a request
   *
   * @param request The request to send
   * @param waitForResponses Whether we should wait for responses
   * @param trickleFeed Whether bodies should be trickle fed.  Trickle feeding will simulate a more realistic network
   *                    environment.
   * @return The responses (may be more than one if Expect: 100-continue header is present) if requested to wait for
   *         them
   */
  def sendRequest(request: BasicRequest, requestDesc: String, waitForResponses: Boolean = true,
                  trickleFeed: Option[Long] = None): Seq[BasicResponse] = {
    out.write(s"${request.method} ${request.uri} ${request.version}\r\n")
    out.write("Host: localhost\r\n")
    request.headers.foreach { header =>
      out.write(s"${header._1}: ${header._2}\r\n")
    }
    out.write("\r\n")

    def writeBody() = {
      if (request.body.length > 0) {
        trickleFeed match {
          case Some(timeout) =>
            request.body.grouped(8192).foreach { chunk =>
              out.write(chunk)
              out.flush()
              Thread.sleep(timeout)
            }
          case None =>
            out.write(request.body)
        }
      }
      out.flush()
    }

    if (waitForResponses) {
      request.headers.get("Expect").filter(_ == "100-continue").map { _ =>
        out.flush()
        val response = readResponse(requestDesc + " continue")
        if (response.status == 100) {
          writeBody()
          Seq(response, readResponse(requestDesc))
        } else {
          Seq(response)
        }
      } getOrElse {
        writeBody()
        Seq(readResponse(requestDesc))
      }
    } else {
      writeBody()
      Nil
    }
  }

  /**
   * Read a response
   *
   * @param responseDesc Description of the response, for error reporting
   * @return The response
   */
  def readResponse(responseDesc: String) = {
    try {
      // Read status line
      val statusLine = reader.readLine()
      val (version, status, reasonPhrase) = statusLine.split(" ", 3) match {
        case Array(v, s, r) => (v, s.toInt, r)
        case Array(v, s) => (v, s.toInt, "")
        case _ => throw new RuntimeException("Invalid status line for response " + responseDesc + ": " + statusLine)
      }
      // Read headers
      def readHeaders: List[(String, String)] = {
        val header = reader.readLine()
        if (header.length == 0) {
          Nil
        } else {
          val parsed = header.split(":", 2) match {
            case Array(name, value) => (name.trim(), value.trim())
            case Array(name) => (name, "")
          }
          parsed :: readHeaders
        }
      }
      val headers = readHeaders.toMap

      def readCompletely(length: Int): String = {
        if (length == 0) {
          ""
        } else {
          val buf = new Array[Char](length)
          def readFromOffset(offset: Int): Unit = {
            val read = reader.read(buf, offset, length - offset)
            if (read + offset < length) readFromOffset(read + offset) else ()
          }
          readFromOffset(0)
          new String(buf)
        }
      }

      // Read body
      val body = headers.get(TRANSFER_ENCODING).filter(_ == CHUNKED).map { _ =>
        def readChunks: List[String] = {
          val chunkLength = Integer.parseInt(reader.readLine())
          if (chunkLength == 0) {
            Nil
          } else {
            val chunk = readCompletely(chunkLength)
            // Ignore newline after chunk
            reader.readLine()
            chunk :: readChunks
          }
        }
        (readChunks.toSeq, readHeaders.toMap)
      } toRight {
        headers.get(CONTENT_LENGTH).map { length =>
          readCompletely(length.toInt)
        } getOrElse {
          if (status != CONTINUE && status != NOT_MODIFIED && status != NO_CONTENT) {
            IOUtils.toString(reader)
          } else {
            ""
          }
        }
      }

      BasicResponse(version, status, reasonPhrase, headers, body)
    } catch {
      case e: Exception =>
        throw new RuntimeException(
          s"Exception while reading response $responseDesc ${e.getClass.getName}: ${e.getMessage}", e)
    }
  }

  def close() = {
    s.close()
  }
}


/**
 * A basic response
 *
 * @param version The HTTP version
 * @param status The HTTP status code
 * @param reasonPhrase The HTTP reason phrase
 * @param headers The HTTP response headers
 * @param body The body, left is a plain body, right is for chunked bodies, which is a sequence of chunks and a map of
 *             trailers
 */
case class BasicResponse(version: String, status: Int, reasonPhrase: String, headers: Map[String, String],
                         body: Either[String, (Seq[String], Map[String, String])])

/**
 * A basic request
 *
 * @param method The HTTP request method
 * @param uri The URI
 * @param version The HTTP version
 * @param headers The HTTP request headers
 * @param body The body
 */
case class BasicRequest(method: String, uri: String, version: String, headers: Map[String, String], body: String)

Other Play Framework source code examples

Here is a short list of links related to this Play Framework BasicHttpClient.scala source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.