You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openwhisk.apache.org by cs...@apache.org on 2017/08/16 16:50:55 UTC
[incubator-openwhisk] branch master updated: Support json response
in HTTP web actions without base64 encoding. (#2609)
This is an automated email from the ASF dual-hosted git repository.
csantanapr pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-openwhisk.git
The following commit(s) were added to refs/heads/master by this push:
new de8340a Support json response in HTTP web actions without base64 encoding. (#2609)
de8340a is described below
commit de8340a8c5351d0f1935f0734684334c79e37073
Author: rodric rabbah <ro...@gmail.com>
AuthorDate: Wed Aug 16 12:50:52 2017 -0400
Support json response in HTTP web actions without base64 encoding. (#2609)
---
.../scala/whisk/core/controller/WebActions.scala | 33 +++++++++++---
docs/webactions.md | 6 ++-
.../core/controller/test/WebActionsApiTests.scala | 52 ++++++++++++++++++++--
3 files changed, 79 insertions(+), 12 deletions(-)
diff --git a/core/controller/src/main/scala/whisk/core/controller/WebActions.scala b/core/controller/src/main/scala/whisk/core/controller/WebActions.scala
index 1c9ec8e..c26d32b 100644
--- a/core/controller/src/main/scala/whisk/core/controller/WebActions.scala
+++ b/core/controller/src/main/scala/whisk/core/controller/WebActions.scala
@@ -42,7 +42,7 @@ import akka.http.scaladsl.model.headers.`Content-Type`
import akka.http.scaladsl.model.ContentType
import akka.http.scaladsl.model.ContentTypes
import akka.http.scaladsl.model.FormData
-import akka.http.scaladsl.model.HttpMethods.{ OPTIONS, GET, DELETE, POST, PUT, HEAD, PATCH}
+import akka.http.scaladsl.model.HttpMethods.{ OPTIONS, GET, DELETE, POST, PUT, HEAD, PATCH }
import akka.http.scaladsl.model.HttpCharsets
import spray.json._
@@ -241,7 +241,7 @@ protected[core] object WhiskWebActionsApi extends Directives {
fields.get("body") map {
case JsString(str) => interpretHttpResponse(code, headers, str, transid)
- case _ => terminate(BadRequest, Messages.httpContentTypeError)(transid)
+ case js => interpretHttpResponseAsJson(code, headers, js, transid)
} getOrElse {
respondWithHeaders(headers) {
// note that if header defined a content-type, it will be ignored
@@ -256,7 +256,7 @@ protected[core] object WhiskWebActionsApi extends Directives {
}
}
- private def headersFromJson(k: String, v: JsValue) : Seq[RawHeader] = v match {
+ private def headersFromJson(k: String, v: JsValue): Seq[RawHeader] = v match {
case JsString(v) => Seq(RawHeader(k, v))
case JsBoolean(v) => Seq(RawHeader(k, v.toString))
case JsNumber(v) => Seq(RawHeader(k, v.toString))
@@ -264,8 +264,12 @@ protected[core] object WhiskWebActionsApi extends Directives {
case _ => throw new Throwable("Invalid header")
}
- private def interpretHttpResponse(code: StatusCode, headers: List[RawHeader], str: String, transid: TransactionId) = {
- val parsedHeader: Try[MediaType] = headers.find(_.lowercaseName == `Content-Type`.lowercaseName) match {
+ /**
+ * Finds the content-type in the header list and maps it to a known media type. If it is not
+ * recognized, construct a failure with appropriate message.
+ */
+ private def findContentTypeInHeader(headers: List[RawHeader], transid: TransactionId, defaultType: MediaType): Try[MediaType] = {
+ headers.find(_.lowercaseName == `Content-Type`.lowercaseName) match {
case Some(header) =>
MediaType.parse(header.value) match {
case Right(mediaType: MediaType) =>
@@ -276,10 +280,25 @@ protected[core] object WhiskWebActionsApi extends Directives {
}
case _ => Failure(RejectRequest(BadRequest, Messages.httpUnknownContentType)(transid))
}
- case None => Success(`text/html`)
+ case None => Success(defaultType)
}
+ }
+
+ private def interpretHttpResponseAsJson(code: StatusCode, headers: List[RawHeader], js: JsValue, transid: TransactionId) = {
+ findContentTypeInHeader(headers, transid, `application/json`) match {
+ case Success(mediaType) if (mediaType == `application/json`) =>
+ respondWithHeaders(headers) {
+ complete(code, js)
+ }
- parsedHeader.flatMap { mediaType =>
+ case _ =>
+ terminate(BadRequest, Messages.httpContentTypeError)(transid)
+ }
+ }
+
+ private def interpretHttpResponse(code: StatusCode, headers: List[RawHeader], str: String, transid: TransactionId) = {
+ findContentTypeInHeader(headers, transid, `text/html`).flatMap { mediaType =>
+ // base64 encoded json response supported for legacy reasons
if (mediaType.binary || mediaType == `application/json`) {
Try(new String(Base64.getDecoder().decode(str), StandardCharsets.UTF_8)).map((mediaType, _))
} else {
diff --git a/docs/webactions.md b/docs/webactions.md
index c597a98..4be9ef5 100644
--- a/docs/webactions.md
+++ b/docs/webactions.md
@@ -102,11 +102,13 @@ function main(params) {
return {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
- body: new Buffer(JSON.stringify(params)).toString('base64'),
+ body: params
};
}
```
+The default content-type for an HTTP response is `application/json` and the body may be any allowed JSON value. The default content-type may be omitted from the headers.
+
It is important to be aware of the [response size limit](reference.md) for actions since a response that exceeds the predefined system limits will fail. Large objects should not be sent inline through OpenWhisk, but instead deferred to an object store, for example.
## Handling HTTP requests with actions
@@ -309,7 +311,7 @@ $ curl https://${APIHOST}/api/v1/web/guest/demo/hello.json?name=Jane -X POST -H
}
```
-Notice in this case the JSON content is base64 encoded because it is treated as a binary value. The action must base64 decode and JSON parse this value to recover the JSON object. OpenWhisk uses the [Spray](https://github.com/spray/spray) framework to [determine](https://github.com/spray/spray/blob/master/spray-http/src/main/scala/spray/http/MediaType.scala#L282) which content types are binary and which are plain text.
+OpenWhisk uses the [Akka Http](http://doc.akka.io/docs/akka-http/current/scala/http/) framework to [determine](http://doc.akka.io/api/akka-http/10.0.4/akka/http/scaladsl/model/MediaTypes$.html) which content types are binary and which are plain text.
### Enabling raw HTTP handling
diff --git a/tests/src/test/scala/whisk/core/controller/test/WebActionsApiTests.scala b/tests/src/test/scala/whisk/core/controller/test/WebActionsApiTests.scala
index d0b4f0a..6cd92e4 100644
--- a/tests/src/test/scala/whisk/core/controller/test/WebActionsApiTests.scala
+++ b/tests/src/test/scala/whisk/core/controller/test/WebActionsApiTests.scala
@@ -771,7 +771,7 @@ trait WebActionsApiTests extends ControllerTestCommon with BeforeAndAfterEach wi
}
}
- it should s"handle http web action with base64 encoded response (auth? ${creds.isDefined})" in {
+ it should s"handle http web action with base64 encoded JSON response (auth? ${creds.isDefined})" in {
implicit val tid = transid()
Seq(s"$systemId/proxy/export_c.http").
@@ -795,6 +795,53 @@ trait WebActionsApiTests extends ControllerTestCommon with BeforeAndAfterEach wi
}
}
+ it should s"handle http web action without base64 encoded JSON response (auth? ${creds.isDefined})" in {
+ implicit val tid = transid()
+
+ Seq((JsObject("content-type" -> "application/json".toJson), OK),
+ (JsObject(), OK),
+ (JsObject("content-type" -> "text/html".toJson), BadRequest)).foreach {
+ case (headers, expectedCode) =>
+ Seq(s"$systemId/proxy/export_c.http").
+ foreach { path =>
+ allowedMethods.foreach { m =>
+ invocationsAllowed += 1
+ actionResult = Some(JsObject(
+ "headers" -> headers,
+ webApiDirectives.statusCode -> OK.intValue.toJson,
+ "body" -> JsObject("field" -> "value".toJson)))
+
+ m(s"$testRoutePath/$path") ~> Route.seal(routes(creds)) ~> check {
+ status should be(expectedCode)
+
+ if (expectedCode == OK) {
+ header("content-type").map(_.toString shouldBe "content-type: application/json")
+ responseAs[JsObject] shouldBe JsObject("field" -> "value".toJson)
+ } else {
+ confirmErrorWithTid(responseAs[JsObject], Some(Messages.httpContentTypeError))
+ }
+ }
+ }
+ }
+ }
+
+ Seq(s"$systemId/proxy/export_c.http").
+ foreach { path =>
+ allowedMethods.foreach { m =>
+ invocationsAllowed += 1
+ actionResult = Some(JsObject(
+ webApiDirectives.statusCode -> OK.intValue.toJson,
+ "body" -> JsNumber(3)))
+
+ m(s"$testRoutePath/$path") ~> Route.seal(routes(creds)) ~> check {
+ status should be(OK)
+ header("content-type").map(_.toString shouldBe "content-type: application/json")
+ responseAs[String].toInt shouldBe 3
+ }
+ }
+ }
+ }
+
it should s"handle http web action with html/text response (auth? ${creds.isDefined})" in {
implicit val tid = transid()
@@ -1149,8 +1196,7 @@ trait WebActionsApiTests extends ControllerTestCommon with BeforeAndAfterEach wi
Options(s"$testRoutePath/$path") ~> Route.seal(routes(creds)) ~> check {
headers should contain allOf (
RawHeader("Set-Cookie", "a=b"),
- RawHeader("Set-Cookie", "c=d; Path = /")
- )
+ RawHeader("Set-Cookie", "c=d; Path = /"))
}
}
}
--
To stop receiving notification emails like this one, please contact
['"commits@openwhisk.apache.org" <co...@openwhisk.apache.org>'].