Skip to content

Commit acc7dd7

Browse files
Added url and headers to ApiHttpResponse
1 parent d962100 commit acc7dd7

File tree

8 files changed

+81
-30
lines changed

8 files changed

+81
-30
lines changed

core/src/main/scala/scommons/api/http/ApiHttpClient.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ object ApiHttpClient {
9191
Json.parse(body).validate[R] match {
9292
case JsSuccess(data, _) => data
9393
case JsError(error) =>
94-
throw ApiHttpStatusException(s"Fail to parse http response, error: $error", url, res.status, body)
94+
throw ApiHttpStatusException(s"Fail to parse http response, error: $error", res)
9595
}
9696

9797
case Some(other) =>
@@ -107,7 +107,7 @@ object ApiHttpClient {
107107

108108
maybeData match {
109109
case Some(data) => data
110-
case None => throw ApiHttpStatusException("Received error response", url, other.status, body)
110+
case None => throw ApiHttpStatusException("Received error response", other)
111111
}
112112
}
113113

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
package scommons.api.http
22

3-
case class ApiHttpResponse(status: Int, body: String)
3+
case class ApiHttpResponse(url: String,
4+
status: Int,
5+
headers: Map[String, Seq[String]],
6+
body: String)

core/src/main/scala/scommons/api/http/ApiHttpStatusException.scala

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,8 @@ package scommons.api.http
33
import scommons.api.http.ApiHttpStatusException._
44

55
case class ApiHttpStatusException(error: String,
6-
url: String,
7-
status: Int,
8-
body: String
9-
) extends RuntimeException(buildMessage(error, url, status, body))
6+
resp: ApiHttpResponse
7+
) extends RuntimeException(buildMessage(error, resp.url, resp.status, resp.body))
108

119
object ApiHttpStatusException {
1210

@@ -15,9 +13,15 @@ object ApiHttpStatusException {
1513
status: Int,
1614
body: String): String = {
1715

16+
def printBody: String = {
17+
val maxLen = 1024
18+
if (body.length > maxLen) s"${body.take(maxLen)}..."
19+
else body
20+
}
21+
1822
s"""$error
1923
| url: $url
2024
| status: $status
21-
| body: $body""".stripMargin
25+
| body: $printBody""".stripMargin
2226
}
2327
}

core/src/test/scala/scommons/api/http/ApiHttpClientSpec.scala

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class ApiHttpClientSpec extends AsyncFlatSpec
4747
//given
4848
val url = s"/api/get/url"
4949
val expectedResult = List(TestRespData(1, "test"))
50-
val expectedResponse = ApiHttpResponse(200, stringify(toJson(expectedResult)))
50+
val expectedResponse = ApiHttpResponse(url, 200, Map.empty, stringify(toJson(expectedResult)))
5151
val execute = stubExec
5252
val client = new TestHttpClient(execute)
5353

@@ -66,7 +66,7 @@ class ApiHttpClientSpec extends AsyncFlatSpec
6666
//given
6767
val url = s"/api/get/url"
6868
val expectedResult = List(TestRespData(1, "test"))
69-
val expectedResponse = ApiHttpResponse(200, stringify(toJson(expectedResult)))
69+
val expectedResponse = ApiHttpResponse(url, 200, Map.empty, stringify(toJson(expectedResult)))
7070
val execute = stubExec
7171
val client = new TestHttpClient(execute)
7272

@@ -86,7 +86,7 @@ class ApiHttpClientSpec extends AsyncFlatSpec
8686
val url = s"/api/post/url"
8787
val data = TestReqData(1)
8888
val expectedResult = List(TestRespData(2, "test"))
89-
val expectedResponse = ApiHttpResponse(200, stringify(toJson(expectedResult)))
89+
val expectedResponse = ApiHttpResponse(url, 200, Map.empty, stringify(toJson(expectedResult)))
9090
val execute = stubExec
9191
val client = new TestHttpClient(execute)
9292

@@ -106,7 +106,7 @@ class ApiHttpClientSpec extends AsyncFlatSpec
106106
val url = s"/api/put/url"
107107
val data = TestReqData(1)
108108
val expectedResult = List(TestRespData(2, "test"))
109-
val expectedResponse = ApiHttpResponse(200, stringify(toJson(expectedResult)))
109+
val expectedResponse = ApiHttpResponse(url, 200, Map.empty, stringify(toJson(expectedResult)))
110110
val execute = stubExec
111111
val client = new TestHttpClient(execute)
112112

@@ -126,7 +126,7 @@ class ApiHttpClientSpec extends AsyncFlatSpec
126126
val url = s"/api/delete/url"
127127
val data = TestReqData(1)
128128
val expectedResult = List(TestRespData(2, "test"))
129-
val expectedResponse = ApiHttpResponse(200, stringify(toJson(expectedResult)))
129+
val expectedResponse = ApiHttpResponse(url, 200, Map.empty, stringify(toJson(expectedResult)))
130130
val execute = stubExec
131131
val client = new TestHttpClient(execute)
132132

@@ -165,20 +165,21 @@ class ApiHttpClientSpec extends AsyncFlatSpec
165165
val url = s"/some/url"
166166
val statusCode = 200
167167
val data = """{"id": 1, "missing": "name"}"""
168-
val response = ApiHttpResponse(statusCode, data)
168+
val response = ApiHttpResponse(url, statusCode, Map.empty, data)
169169

170170
//when
171171
val ex = the[ApiHttpStatusException] thrownBy {
172172
ApiHttpClient.parseResponse[TestRespData](url, Some(response))
173173
}
174174

175175
//then
176-
inside(ex) { case ApiHttpStatusException(error, resUrl, status, body) =>
176+
inside(ex) { case ApiHttpStatusException(error, ApiHttpResponse(resUrl, status, resHeaders, body)) =>
177177
error shouldBe {
178178
"Fail to parse http response, error: List((/name,List(JsonValidationError(List(error.path.missing),WrappedArray()))))"
179179
}
180180
resUrl shouldBe url
181181
status shouldBe statusCode
182+
resHeaders shouldBe Map.empty
182183
body shouldBe data
183184
}
184185

@@ -194,18 +195,19 @@ class ApiHttpClientSpec extends AsyncFlatSpec
194195
val url = s"/some/url"
195196
val statusCode = 400
196197
val data = "testData"
197-
val response = ApiHttpResponse(statusCode, data)
198+
val response = ApiHttpResponse(url, statusCode, Map.empty, data)
198199

199200
//when
200201
val ex = the[ApiHttpStatusException] thrownBy {
201202
ApiHttpClient.parseResponse[List[TestRespData]](url, Some(response))
202203
}
203204

204205
//then
205-
inside(ex) { case ApiHttpStatusException(error, resUrl, status, body) =>
206+
inside(ex) { case ApiHttpStatusException(error, ApiHttpResponse(resUrl, status, resHeaders, body)) =>
206207
error shouldBe "Received error response"
207208
resUrl shouldBe url
208209
status shouldBe statusCode
210+
resHeaders shouldBe Map.empty
209211
body shouldBe data
210212
}
211213

@@ -217,23 +219,25 @@ class ApiHttpClientSpec extends AsyncFlatSpec
217219

218220
it should "parse json for HTTP success response when parseResponse" in {
219221
//given
222+
val url = "/api/url"
220223
val respData = List(TestRespData(1, "test"))
221-
val response = ApiHttpResponse(200, stringify(toJson(respData)))
224+
val response = ApiHttpResponse(url, 200, Map.empty, stringify(toJson(respData)))
222225

223226
//when
224-
val result = ApiHttpClient.parseResponse[List[TestRespData]](s"/api/url", Some(response))
227+
val result = ApiHttpClient.parseResponse[List[TestRespData]](url, Some(response))
225228

226229
//then
227230
result shouldBe respData
228231
}
229232

230233
it should "parse json for HTTP failure json response when parseResponse" in {
231234
//given
235+
val url = "/api/url"
232236
val respData = TestRespData(1, "test")
233-
val response = ApiHttpResponse(500, stringify(toJson(respData)))
237+
val response = ApiHttpResponse(url, 500, Map.empty, stringify(toJson(respData)))
234238

235239
//when
236-
val result = ApiHttpClient.parseResponse[TestRespData](s"/api/url", Some(response))
240+
val result = ApiHttpClient.parseResponse[TestRespData](url, Some(response))
237241

238242
//then
239243
result shouldBe respData

dom/src/main/scala/scommons/api/http/dom/DomApiHttpClient.scala

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package scommons.api.http.dom
22

33
import org.scalajs.dom
4+
import scommons.api.http.dom.DomApiHttpClient._
45
import scommons.api.http.{ApiHttpClient, ApiHttpResponse}
56

67
import scala.concurrent.duration._
@@ -19,7 +20,7 @@ class DomApiHttpClient(baseUrl: String, defaultTimeout: FiniteDuration = 30.seco
1920
timeout: FiniteDuration): Future[Option[ApiHttpResponse]] = {
2021

2122
val req = createRequest()
22-
req.open(method, DomApiHttpClient.getFullUrl(targetUrl, params))
23+
req.open(method, getFullUrl(targetUrl, params))
2324
req.timeout = timeout.toMillis.toInt
2425

2526
val allHeaders = {
@@ -33,7 +34,12 @@ class DomApiHttpClient(baseUrl: String, defaultTimeout: FiniteDuration = 30.seco
3334

3435
execute(req, jsonBody).map {
3536
case res if res.status == 0 => None //timeout
36-
case res => Some(ApiHttpResponse(res.status, res.responseText))
37+
case res => Some(ApiHttpResponse(
38+
url = targetUrl,
39+
status = res.status,
40+
headers = parseResponseHeaders(res.getAllResponseHeaders()),
41+
body = res.responseText
42+
))
3743
}
3844
}
3945

@@ -59,6 +65,16 @@ class DomApiHttpClient(baseUrl: String, defaultTimeout: FiniteDuration = 30.seco
5965

6066
object DomApiHttpClient {
6167

68+
private val headersLineRegex = """[\r\n]+""".r
69+
private val headersValueRegex = """: """.r
70+
71+
private[dom] def parseResponseHeaders(headers: String): Map[String, Seq[String]] = {
72+
headersLineRegex.split(headers.trim).map { line =>
73+
val parts = headersValueRegex.pattern.split(line, 2)
74+
(parts.head, parts.lastOption.toList)
75+
}.toMap
76+
}
77+
6278
private[dom] def getFullUrl(url: String, params: List[(String, String)]): String = {
6379

6480
def enc(p: String) = js.URIUtils.encodeURIComponent(p)

dom/src/test/scala/scommons/api/http/dom/DomApiHttpClientSpec.scala

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class DomApiHttpClientSpec extends AsyncFlatSpec
3737
//given
3838
val targetUrl = s"$baseUrl/api/get/url"
3939
val body: Option[String] = None
40-
val expectedResult = ApiHttpResponse(200, "some resp body")
40+
val expectedResult = ApiHttpResponse(targetUrl, 200, Map("test" -> Seq("test value")), "some resp body")
4141
val req = stub[MockXMLHttpRequest]
4242
val resp = stub[MockXMLHttpRequest]
4343
val client = new TestDomClient(req, resp)
@@ -46,6 +46,7 @@ class DomApiHttpClientSpec extends AsyncFlatSpec
4646
(req.timeout_= _).when(*).returns(())
4747
(req.setRequestHeader _).when(*, *).returns(())
4848
(resp.status _).when().returns(expectedResult.status)
49+
(resp.getAllResponseHeaders _).when().returns("test: test value\r\n")
4950
(resp.responseText _).when().returns(expectedResult.body)
5051

5152
//when
@@ -65,7 +66,7 @@ class DomApiHttpClientSpec extends AsyncFlatSpec
6566
//given
6667
val targetUrl = s"$baseUrl/api/post/url"
6768
val body = Some("some req data")
68-
val expectedResult = ApiHttpResponse(200, "some resp body")
69+
val expectedResult = ApiHttpResponse(targetUrl, 200, Map("test" -> Seq("test value")), "some resp body")
6970
val req = stub[MockXMLHttpRequest]
7071
val resp = stub[MockXMLHttpRequest]
7172
val client = new TestDomClient(req, resp)
@@ -74,6 +75,7 @@ class DomApiHttpClientSpec extends AsyncFlatSpec
7475
(req.timeout_= _).when(*).returns(())
7576
(req.setRequestHeader _).when(*, *).returns(())
7677
(resp.status _).when().returns(expectedResult.status)
78+
(resp.getAllResponseHeaders _).when().returns("test: test value\r\n")
7779
(resp.responseText _).when().returns(expectedResult.body)
7880

7981
//when
@@ -144,6 +146,8 @@ object DomApiHttpClientSpec {
144146

145147
def status: Int
146148

149+
def getAllResponseHeaders(): String
150+
147151
def responseText: String
148152
}
149153
}

play-ws/src/main/scala/scommons/api/http/ws/WsApiHttpClient.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package scommons.api.http.ws
33
import java.util.concurrent.TimeoutException
44

55
import akka.actor.ActorSystem
6-
import akka.stream.ActorMaterializer
6+
import akka.stream.{ActorMaterializer, Materializer}
77
import akka.util.ByteString
88
import play.api.libs.ws.ahc.StandaloneAhcWSClient
99
import play.api.libs.ws.{BodyWritable, InMemoryBody, StandaloneWSRequest, StandaloneWSResponse}
@@ -18,7 +18,7 @@ class WsApiHttpClient(baseUrl: String,
1818
extends ApiHttpClient(baseUrl, defaultTimeout)(system.dispatcher) {
1919

2020
private implicit val ec: ExecutionContext = system.dispatcher
21-
private implicit val materializer = ActorMaterializer()
21+
private implicit val materializer: Materializer = ActorMaterializer()
2222

2323
private[ws] val ws = StandaloneAhcWSClient()
2424

@@ -45,7 +45,7 @@ class WsApiHttpClient(baseUrl: String,
4545
.withHttpHeaders(headers: _*)
4646
.withRequestTimeout(timeout)
4747
).map { resp =>
48-
Some(ApiHttpResponse(resp.status, resp.body))
48+
Some(ApiHttpResponse(targetUrl, resp.status, resp.headers, resp.body))
4949
}.recover {
5050
case _: TimeoutException => None
5151
}

play-ws/src/test/scala/scommons/api/http/ws/WsApiHttpClientSpec.scala

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ class WsApiHttpClientSpec extends FlatSpec
5555
reset(client, response)
5656
}
5757

58+
override protected def afterEach(): Unit = {
59+
verifyNoMoreInteractions(response)
60+
}
61+
5862
override protected def afterAll(): Unit = {
5963
reset(client.ws)
6064

@@ -67,8 +71,10 @@ class WsApiHttpClientSpec extends FlatSpec
6771
//given
6872
val targetUrl = s"$baseUrl/api/get/url"
6973
val body: Option[String] = None
70-
val expectedResult = ApiHttpResponse(200, "some resp body")
74+
val respHeaders = Map("test" -> Seq("test value"))
75+
val expectedResult = ApiHttpResponse(targetUrl, 200, respHeaders, "some resp body")
7176
when(response.status).thenReturn(expectedResult.status)
77+
when(response.headers).thenReturn(respHeaders)
7278
when(response.body).thenReturn(expectedResult.body)
7379

7480
//when
@@ -78,14 +84,21 @@ class WsApiHttpClientSpec extends FlatSpec
7884
result shouldBe Some(expectedResult)
7985

8086
assertRequest("GET", targetUrl, params, headers, body, timeout)
87+
88+
verify(response).status
89+
verify(response).headers
90+
verify(response).body
91+
verifyNoMoreInteractions(response)
8192
}
8293

8394
it should "execute request with body" in {
8495
//given
8596
val targetUrl = s"$baseUrl/api/post/url"
8697
val body = Some("some req data")
87-
val expectedResult = ApiHttpResponse(200, "some resp body")
98+
val respHeaders = Map("test" -> Seq("test value"))
99+
val expectedResult = ApiHttpResponse(targetUrl, 200, respHeaders, "some resp body")
88100
when(response.status).thenReturn(expectedResult.status)
101+
when(response.headers).thenReturn(respHeaders)
89102
when(response.body).thenReturn(expectedResult.body)
90103

91104
//when
@@ -95,6 +108,11 @@ class WsApiHttpClientSpec extends FlatSpec
95108
result shouldBe Some(expectedResult)
96109

97110
assertRequest("POST", targetUrl, params, headers, body, timeout)
111+
112+
verify(response).status
113+
verify(response).headers
114+
verify(response).body
115+
verifyNoMoreInteractions(response)
98116
}
99117

100118
it should "return None if timed out when execute request" in {
@@ -111,6 +129,8 @@ class WsApiHttpClientSpec extends FlatSpec
111129
result shouldBe None
112130

113131
assertRequest("GET", targetUrl, params, headers, None, timeout)
132+
133+
verifyZeroInteractions(response)
114134
}
115135

116136
private def assertRequest(method: String,

0 commit comments

Comments
 (0)