|
Play Framework/Scala example source code file (BasicHttpClient.scala)
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 examplesHere 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 |
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.