Skip to content

Commit

Permalink
Added bodyAsBytes to ApiHttpResponse, fixed #1
Browse files Browse the repository at this point in the history
  • Loading branch information
viktor-podzigun committed Apr 4, 2020
1 parent eedfa7f commit e1a34a7
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 43 deletions.
23 changes: 19 additions & 4 deletions core/src/main/scala/scommons/api/http/ApiHttpResponse.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
package scommons.api.http

case class ApiHttpResponse(url: String,
status: Int,
headers: Map[String, Seq[String]],
body: String)
class ApiHttpResponse(val url: String,
val status: Int,
val headers: Map[String, Seq[String]],
getBody: => String,
getBodyAsBytes: => Seq[Byte]) {

private lazy val _body: String = getBody
private lazy val _bodyAsBytes: Seq[Byte] = getBodyAsBytes

def body: String = _body
def bodyAsBytes: Seq[Byte] = _bodyAsBytes
}

object ApiHttpResponse {

def apply(url: String, status: Int, headers: Map[String, Seq[String]], body: String): ApiHttpResponse = {
new ApiHttpResponse(url, status, headers, body, body.getBytes)
}
}
14 changes: 4 additions & 10 deletions core/src/test/scala/scommons/api/http/ApiHttpClientSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -178,14 +178,11 @@ class ApiHttpClientSpec extends AsyncFlatSpec
}

//then
inside(ex) { case ApiHttpStatusException(error, ApiHttpResponse(resUrl, status, resHeaders, body)) =>
inside(ex) { case ApiHttpStatusException(error, resp) =>
error shouldBe {
"Fail to parse http response, error: List((/name,List(JsonValidationError(List(error.path.missing),WrappedArray()))))"
}
resUrl shouldBe url
status shouldBe statusCode
resHeaders shouldBe Map.empty
body shouldBe data
resp shouldBe response
}

val message = ex.getMessage
Expand All @@ -208,12 +205,9 @@ class ApiHttpClientSpec extends AsyncFlatSpec
}

//then
inside(ex) { case ApiHttpStatusException(error, ApiHttpResponse(resUrl, status, resHeaders, body)) =>
inside(ex) { case ApiHttpStatusException(error, resp) =>
error shouldBe "Received error response"
resUrl shouldBe url
status shouldBe statusCode
resHeaders shouldBe Map.empty
body shouldBe data
resp shouldBe response
}

val message = ex.getMessage
Expand Down
22 changes: 16 additions & 6 deletions dom/src/main/scala/scommons/api/http/dom/DomApiHttpClient.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import scala.concurrent.duration._
import scala.concurrent.{Future, Promise}
import scala.scalajs.concurrent.JSExecutionContext.Implicits.queue
import scala.scalajs.js
import scala.scalajs.js.typedarray._

class DomApiHttpClient(baseUrl: String, defaultTimeout: FiniteDuration = 30.seconds)
extends ApiHttpClient(baseUrl, defaultTimeout) {
Expand Down Expand Up @@ -38,11 +39,12 @@ class DomApiHttpClient(baseUrl: String, defaultTimeout: FiniteDuration = 30.seco

execute(req, body).map {
case res if res.status == 0 => None //timeout
case res => Some(ApiHttpResponse(
url = targetUrl,
status = res.status,
headers = parseResponseHeaders(res.getAllResponseHeaders()),
body = res.responseText
case res => Some(new ApiHttpResponse(
targetUrl,
res.status,
parseResponseHeaders(res.getAllResponseHeaders()),
res.responseText,
getBodyAsBytes(res.response)
))
}
}
Expand Down Expand Up @@ -72,13 +74,21 @@ object DomApiHttpClient {
private val headersLineRegex = """[\r\n]+""".r
private val headersValueRegex = """: """.r

private[dom] def parseResponseHeaders(headers: String): Map[String, Seq[String]] = {
private def parseResponseHeaders(headers: String): Map[String, Seq[String]] = {
headersLineRegex.split(headers.trim).map { line =>
val parts = headersValueRegex.pattern.split(line, 2)
(parts.head, parts.lastOption.toList)
}.toMap
}

private[dom] def getBodyAsBytes(response: js.Any): Seq[Byte] = {
if (response == null || js.isUndefined(response)) Nil
else {
//TODO: handle Blob response as well
new Int8Array(response.asInstanceOf[ArrayBuffer]).toArray
}
}

private[dom] def getFullUrl(url: String, params: List[(String, String)]): String = {

def enc(p: String) = js.URIUtils.encodeURIComponent(p)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,13 @@ class XMLHttpRequest extends js.Object {
* MDN
*/
def responseText: String = js.native

/**
* The response entity body according to responseType, as an ArrayBuffer, Blob,
* Document, JavaScript object (for "json"), or string. This is null if the request is
* not complete or was not successful.
*
* MDN
*/
def response: js.Any = js.native
}
58 changes: 44 additions & 14 deletions dom/src/test/scala/scommons/api/http/dom/DomApiHttpClientSpec.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package scommons.api.http.dom

import org.scalamock.scalatest.AsyncMockFactory
import org.scalatest.{AsyncFlatSpec, Matchers}
import org.scalatest.{Assertion, AsyncFlatSpec, Inside, Matchers}
import scommons.api.http.ApiHttpData.{StringData, UrlEncodedFormData}
import scommons.api.http.dom.DomApiHttpClient.getFullUrl
import scommons.api.http.dom.DomApiHttpClient._
import scommons.api.http.dom.DomApiHttpClientSpec.MockXMLHttpRequest
import scommons.api.http.{ApiHttpData, ApiHttpResponse}

Expand All @@ -12,9 +12,11 @@ import scala.concurrent.duration._
import scala.scalajs.concurrent.JSExecutionContext
import scala.scalajs.js
import scala.scalajs.js.annotation.JSExportAll
import scala.scalajs.js.typedarray._

class DomApiHttpClientSpec extends AsyncFlatSpec
with Matchers
with Inside
with AsyncMockFactory {

implicit override def executionContext: ExecutionContext = JSExecutionContext.queue
Expand Down Expand Up @@ -51,9 +53,10 @@ class DomApiHttpClientSpec extends AsyncFlatSpec
(req.status _).when().returns(expectedResult.status)
(req.getAllResponseHeaders _).when().returns("test_header: test header value\r\n")
(req.responseText _).when().returns(expectedResult.body)
(req.response _).when().returns(expectedResult.bodyAsBytes.toArray.toTypedArray.buffer)

//when
client.execute("GET", targetUrl, params, headers, body, timeout).map { result =>
client.execute("GET", targetUrl, params, headers, body, timeout).map(inside(_) { case Some(result) =>
//then
(req.open _).verify("GET", getFullUrl(targetUrl, params))
(req.timeout_= _).verify(timeout.toMillis)
Expand All @@ -62,8 +65,8 @@ class DomApiHttpClientSpec extends AsyncFlatSpec
}
(req.send _).verify(().asInstanceOf[js.Any])

result shouldBe Some(expectedResult)
}
assertApiHttpResponse(result, expectedResult)
})
}

it should "execute request with plain text body" in {
Expand All @@ -88,9 +91,10 @@ class DomApiHttpClientSpec extends AsyncFlatSpec
(req.status _).when().returns(expectedResult.status)
(req.getAllResponseHeaders _).when().returns("test_header: test header value\r\n")
(req.responseText _).when().returns(expectedResult.body)
(req.response _).when().returns(expectedResult.bodyAsBytes.toArray.toTypedArray.buffer)

//when
client.execute("POST", targetUrl, params, headers, body, timeout).map { result =>
client.execute("POST", targetUrl, params, headers, body, timeout).map(inside(_) { case Some(result) =>
//then
(req.open _).verify("POST", getFullUrl(targetUrl, params))
(req.timeout_= _).verify(timeout.toMillis)
Expand All @@ -99,8 +103,8 @@ class DomApiHttpClientSpec extends AsyncFlatSpec
}
(req.send _).verify(data.asInstanceOf[js.Any])

result shouldBe Some(expectedResult)
}
assertApiHttpResponse(result, expectedResult)
})
}

it should "execute request with json body" in {
Expand All @@ -125,9 +129,10 @@ class DomApiHttpClientSpec extends AsyncFlatSpec
(req.status _).when().returns(expectedResult.status)
(req.getAllResponseHeaders _).when().returns("test_header: test header value\r\n")
(req.responseText _).when().returns(expectedResult.body)
(req.response _).when().returns(expectedResult.bodyAsBytes.toArray.toTypedArray.buffer)

//when
client.execute("POST", targetUrl, params, headers, body, timeout).map { result =>
client.execute("POST", targetUrl, params, headers, body, timeout).map(inside(_) { case Some(result) =>
//then
(req.open _).verify("POST", getFullUrl(targetUrl, params))
(req.timeout_= _).verify(timeout.toMillis)
Expand All @@ -136,8 +141,8 @@ class DomApiHttpClientSpec extends AsyncFlatSpec
}
(req.send _).verify(data.asInstanceOf[js.Any])

result shouldBe Some(expectedResult)
}
assertApiHttpResponse(result, expectedResult)
})
}

it should "execute request with form body" in {
Expand All @@ -164,9 +169,10 @@ class DomApiHttpClientSpec extends AsyncFlatSpec
(req.status _).when().returns(expectedResult.status)
(req.getAllResponseHeaders _).when().returns("test_header: test header value\r\n")
(req.responseText _).when().returns(expectedResult.body)
(req.response _).when().returns(expectedResult.bodyAsBytes.toArray.toTypedArray.buffer)

//when
client.execute("POST", targetUrl, params, headers, body, timeout).map { result =>
client.execute("POST", targetUrl, params, headers, body, timeout).map(inside(_) { case Some(result) =>
//then
(req.open _).verify("POST", getFullUrl(targetUrl, params))
(req.timeout_= _).verify(timeout.toMillis)
Expand All @@ -175,8 +181,8 @@ class DomApiHttpClientSpec extends AsyncFlatSpec
}
(req.send _).verify("param1=value1&param1=value2&param2=value3".asInstanceOf[js.Any])

result shouldBe Some(expectedResult)
}
assertApiHttpResponse(result, expectedResult)
})
}

it should "return None if timed out when execute request" in {
Expand Down Expand Up @@ -223,6 +229,28 @@ class DomApiHttpClientSpec extends AsyncFlatSpec
getFullUrl("/test", List("p1" -> "1", "p2" -> "1 2")) shouldBe "/test?p1=1&p2=1%202"
getFullUrl("/test", List("p1" -> "1", "p2" -> "1&2")) shouldBe "/test?p1=1&p2=1%262"
}

it should "return Nil if response is null or undefined when getBodyAsBytes" in {
//when & then
getBodyAsBytes(null) shouldBe Nil
getBodyAsBytes(js.undefined) shouldBe Nil
}

it should "return bytes if response is ArrayBuffer when getBodyAsBytes" in {
//given
val response: ArrayBuffer = "test data".getBytes.toTypedArray.buffer

//when & then
getBodyAsBytes(response) shouldBe "test data".getBytes
}

private def assertApiHttpResponse(result: ApiHttpResponse, expected: ApiHttpResponse): Assertion = {
result.url shouldBe expected.url
result.status shouldBe expected.status
result.headers shouldBe expected.headers
result.body shouldBe expected.body
result.bodyAsBytes shouldBe expected.bodyAsBytes
}
}

object DomApiHttpClientSpec {
Expand All @@ -247,5 +275,7 @@ object DomApiHttpClientSpec {
def getAllResponseHeaders(): String

def responseText: String

def response: js.Any
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class WsApiHttpClient(ws: StandaloneAhcWSClient,
.withHttpHeaders(headers: _*)
.withRequestTimeout(timeout)
).map { resp =>
Some(ApiHttpResponse(targetUrl, resp.status, resp.headers, resp.body))
Some(new ApiHttpResponse(targetUrl, resp.status, resp.headers, resp.body, resp.bodyAsBytes))
}.recover {
case _: TimeoutException => None
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import java.util.concurrent.TimeoutException

import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import akka.util.ByteString
import org.mockito.ArgumentCaptor
import org.mockito.Matchers.any
import org.mockito.Mockito._
Expand Down Expand Up @@ -75,18 +76,20 @@ class WsApiHttpClientSpec extends FlatSpec
when(response.status).thenReturn(expectedResult.status)
when(response.headers).thenReturn(respHeaders)
when(response.body).thenReturn(expectedResult.body)
when(response.bodyAsBytes).thenReturn(ByteString.apply(expectedResult.body))

//when
val result = client.execute("GET", targetUrl, params, headers, body, timeout).futureValue
val Some(result) = client.execute("GET", targetUrl, params, headers, body, timeout).futureValue

//then
result shouldBe Some(expectedResult)
assertApiHttpResponse(result, expectedResult)

assertRequest("GET", targetUrl, params, headers, body, timeout)

verify(response).status
verify(response).headers
verify(response).body
verify(response).bodyAsBytes
verifyNoMoreInteractions(response)
}

Expand All @@ -99,18 +102,20 @@ class WsApiHttpClientSpec extends FlatSpec
when(response.status).thenReturn(expectedResult.status)
when(response.headers).thenReturn(respHeaders)
when(response.body).thenReturn(expectedResult.body)
when(response.bodyAsBytes).thenReturn(ByteString.apply(expectedResult.body))

//when
val result = client.execute("POST", targetUrl, params, headers, body, timeout).futureValue
val Some(result) = client.execute("POST", targetUrl, params, headers, body, timeout).futureValue

//then
result shouldBe Some(expectedResult)
assertApiHttpResponse(result, expectedResult)

assertRequest("POST", targetUrl, params, headers, body, timeout)

verify(response).status
verify(response).headers
verify(response).body
verify(response).bodyAsBytes
verifyNoMoreInteractions(response)
}

Expand All @@ -123,18 +128,20 @@ class WsApiHttpClientSpec extends FlatSpec
when(response.status).thenReturn(expectedResult.status)
when(response.headers).thenReturn(respHeaders)
when(response.body).thenReturn(expectedResult.body)
when(response.bodyAsBytes).thenReturn(ByteString.apply(expectedResult.body))

//when
val result = client.execute("POST", targetUrl, params, headers, body, timeout).futureValue
val Some(result) = client.execute("POST", targetUrl, params, headers, body, timeout).futureValue

//then
result shouldBe Some(expectedResult)
assertApiHttpResponse(result, expectedResult)

assertRequest("POST", targetUrl, params, headers, body, timeout)

verify(response).status
verify(response).headers
verify(response).body
verify(response).bodyAsBytes
verifyNoMoreInteractions(response)
}

Expand All @@ -150,18 +157,20 @@ class WsApiHttpClientSpec extends FlatSpec
when(response.status).thenReturn(expectedResult.status)
when(response.headers).thenReturn(respHeaders)
when(response.body).thenReturn(expectedResult.body)
when(response.bodyAsBytes).thenReturn(ByteString.apply(expectedResult.body))

//when
val result = client.execute("POST", targetUrl, params, headers, body, timeout).futureValue
val Some(result) = client.execute("POST", targetUrl, params, headers, body, timeout).futureValue

//then
result shouldBe Some(expectedResult)
assertApiHttpResponse(result, expectedResult)

assertRequest("POST", targetUrl, params, headers, body, timeout)

verify(response).status
verify(response).headers
verify(response).body
verify(response).bodyAsBytes
verifyNoMoreInteractions(response)
}

Expand All @@ -182,6 +191,14 @@ class WsApiHttpClientSpec extends FlatSpec

verifyZeroInteractions(response)
}

private def assertApiHttpResponse(result: ApiHttpResponse, expected: ApiHttpResponse): Unit = {
result.url shouldBe expected.url
result.status shouldBe expected.status
result.headers shouldBe expected.headers
result.body shouldBe expected.body
result.bodyAsBytes shouldBe expected.bodyAsBytes
}

private def assertRequest(method: String,
targetUrl: String,
Expand Down

0 comments on commit e1a34a7

Please sign in to comment.