如何从 Akka HTTP POST 请求中读取 JSON 正文并将最终响应作为 JSON 数组发送
How to read JSON body from Akka HTTP POST request and send the final response as JSON array
我是 akka http 的新手。我创建了一个程序来使用 http 发出 post 请求,如下所示 -
object MainController {
def main(args: Array[String]) {
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
implicit val executionContext = system.dispatcher
val serverSource = Http().bind(interface = "localhost", port = 9000)
val requestHandler: HttpRequest => HttpResponse = {
case HttpRequest(GET, Uri.Path("/welcome"), _, _, _) =>
HttpResponse(entity = HttpEntity(
ContentTypes.`text/html(UTF-8)`,
"<html><body>Welcome to API Application</body></html>"))
case HttpRequest(POST, Uri.Path("/parseData"), _, entity: HttpEntity, _) =>
// Here Need to read request body which is in json format
println("1 " + new String(entity.getDataBytes()))
println("2 " + entity.getDataBytes())
// here need to do some calculations and again construct array of json response and send as HttpResponse
HttpResponse(entity = "PONG!")
case r: HttpRequest =>
r.discardEntityBytes() // important to drain incoming HTTP Entity stream
HttpResponse(404, entity = "Unknown resource!")
}
val bindingFuture = Http().bindAndHandleSync(requestHandler, "localhost", 9000)
println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
StdIn.readLine() // let it run until user presses return
bindingFuture
.flatMap(_.unbind()) // trigger unbinding from the port
.onComplete(_ => system.terminate()) // and shutdown when done
}
}
正如我在上面的代码中提到的 "Post" 请求,我需要读取 json 数组的请求正文数据并进行一些计算,最后发送处理后的 json 到 HTTPResponse。甚至高级 API 也尝试过,但它再次陷入编组。谁能解释一下或帮助我。
我尝试了另一种方法如下-
object MainController {
// needed to run the route
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
implicit val executionContext = system.dispatcher
final case class hexRecord(hexstring: String)
final case class DeviceData(hexData: List[hexRecord])
// formats for unmarshalling and marshalling
implicit val contentFormat = jsonFormat1(hexRecord)
implicit val dataFormat = jsonFormat1(DeviceData)
def main(args: Array[String]) {
implicit val formats = org.json4s.DefaultFormats
val requestBody = List.empty[Map[String, Any]]
val route: Route =
concat(
get {
path("welcome"){
complete("Welcome to Parsing Application")}
},
post {
path("parseDeviceData") {
entity(as[DeviceData]) { data => {
val result = data.hexData.map(row => {
val parseData = ParserManager(Hex.decodeHex(row.hexstring.replaceAll("\s", "").toCharArray))
val jsonString = Serialization.writePretty(parseData)
jsonString
}).toArray
complete(result)
}
}
}
}
)
val bindingFuture = Http().bindAndHandle(route, "localhost", 9000)
println(s"Server online at http://localhost:9000/")
StdIn.readLine() // let it run until user presses return
bindingFuture
.flatMap(_.unbind()) // trigger unbinding from the port
.onComplete(_ => system.terminate()) // and shutdown when done
}
}
这里的结果很好,但我在输出中得到了转义字符 -
[
" {\n \"totalsize\" : 128,\n \"devicetypeuno\" : \"2\"} ",
" {\n \"totalsize\" : 128,\n \"devicetypeuno\" : \"2\"} "
]
这是一个示例程序,它读取一个 json 值数组并 return 返回一个新的 json 值。
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.server.Directives._
import akka.stream.ActorMaterializer
import akka.util.Timeout
import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport
import io.circe.generic.semiauto
import io.circe.{Decoder, Encoder}
import scala.concurrent.duration._
import scala.io.StdIn
object WebServer3 extends FailFastCirceSupport {
def main(args: Array[String]) {
implicit val system = ActorSystem("my-system")
implicit val materializer = ActorMaterializer()
implicit val executionContext = system.dispatcher
implicit val timeout: Timeout = 2.seconds
case class Zoo(foo: String, bar: String)
case class DoneZoo(message: String)
implicit val zooDecoder: Decoder[Zoo] = semiauto.deriveDecoder[Zoo]
implicit val zooEncoder: Encoder[Zoo] = semiauto.deriveEncoder[Zoo]
implicit val doneDecoder: Decoder[DoneZoo] = semiauto.deriveDecoder[DoneZoo]
implicit val doneEnecoder: Encoder[DoneZoo] =
semiauto.deriveEncoder[DoneZoo]
val route = path("parseData") {
entity(as[List[Zoo]]) { zoo =>
zoo.foreach(println)
complete(DoneZoo(zoo.foldLeft("") {
case (agg, el) => agg + el.bar + el.foo + ";"
}))
}
}
val bindingFuture = Http().bindAndHandle(route, "localhost", 8080)
println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
StdIn.readLine()
val _ = bindingFuture.flatMap(_.unbind())
}
}
json 是在案例 类 和 circe semi auto derivation 的帮助下定义的。这将为 json 转换创建 Encoder/Decoder 类。您必须将这些类型转换为用于实体编组的 Akka 特定类型,这是在 de.heikoseeberger.akkahttpcirce.FailFastCirceSupport 的帮助下隐式完成的。
在此之后,您可以使用 akka route dsl 来定义您的 http 路由。 entity(as[List[Zoo]])
会将 http 正文读取为 json 并 return 返回 List[Zoo]
您可以在 curl
的帮助下测试此应用
curl -v -X POST -H 'Content-type: application/json' --data '[{"foo": "Foo", "bar": "Bar"}]' http://localhost:8080/parseData
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying ::1...
* TCP_NODELAY set
* Connection failed
* connect to ::1 port 8080 failed: Connection refused
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> POST /parseData HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
> Content-type: application/json
> Content-Length: 30
>
* upload completely sent off: 30 out of 30 bytes
< HTTP/1.1 200 OK
< Server: akka-http/10.1.7
< Date: Tue, 15 Oct 2019 08:46:56 GMT
< Content-Type: application/json
< Content-Length: 18
<
* Connection #0 to host localhost left intact
{"message":"DONE"}* Closing connection 0
编辑:
Json 序列化必须留给 akka 在指令中处理 entity(as[])
请求和 complete
响应。不要手动创建 JSON。
你可以试试这个。
import spray.json.DefaultJsonProtocol
import spray.httpx.unmarshalling._
import spray.httpx.marshalling._
import spray.http._
import HttpCharsets._
import MediaTypes._
case class Person(name: String, firstName: String, age: Int)
object MyJsonProtocol extends DefaultJsonProtocol {
implicit val PersonFormat = jsonFormat3(Person)
}
import MyJsonProtocol._
import spray.httpx.SprayJsonSupport._
import spray.util._
val bob = Person("Bob", "Parr", 32)
val body = HttpEntity(
contentType = ContentType(`application/json`, `UTF-8`),
string =
"""|{
| "name": "Bob",
| "firstName": "Parr",
| "age": 32
|}""".stripMarginWithNewline("\n")
)
marshal(bob) === Right(body)
body.as[Person] === Right(bob)
就像 jsonFormat1[DeviceData]
和 as
组合帮助您将 JSON 字符串转换为 DeviceData 对象一样,您可以在另一个方向使用相同的技术将结果转换为 JSON,无需手动编组输出。
implicit val parseDataJsonFormat = jsonFormatn[ParserManager]
// You should replace jsonFormatn with appropriate number based on the number of fields in ParserManager.
val result = data.hexData.map(row => {
ParserManager(Hex.decodeHex(row.hexstring.replaceAll("\s", "").toCharArray))
}
)
我使用以下方法做到了 -
case HttpRequest(POST, Uri.Path("/decodedata"), _, entity, _) =>
val chunk = Unmarshal(entity).to[List[DataChunk]]
val deviceData = Await.result(chunk, 1.second)
implicit val formats = org.json4s.DefaultFormats
val resp = deviceData
.map(data =>
Serialization.write(ParserManager(Hex.decodeHex(data.rawData.replaceAll("\s", "").toCharArray)))
).mkString
HttpResponse(entity = HttpEntity(ContentTypes.`application/json`, resp))
case class DataChunk(rawData: String)
object DataChunk {
implicit val dataChunkJsonFormat: RootJsonFormat[DataChunk] = jsonFormat1(DataChunk.apply)
}
我是 akka http 的新手。我创建了一个程序来使用 http 发出 post 请求,如下所示 -
object MainController {
def main(args: Array[String]) {
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
implicit val executionContext = system.dispatcher
val serverSource = Http().bind(interface = "localhost", port = 9000)
val requestHandler: HttpRequest => HttpResponse = {
case HttpRequest(GET, Uri.Path("/welcome"), _, _, _) =>
HttpResponse(entity = HttpEntity(
ContentTypes.`text/html(UTF-8)`,
"<html><body>Welcome to API Application</body></html>"))
case HttpRequest(POST, Uri.Path("/parseData"), _, entity: HttpEntity, _) =>
// Here Need to read request body which is in json format
println("1 " + new String(entity.getDataBytes()))
println("2 " + entity.getDataBytes())
// here need to do some calculations and again construct array of json response and send as HttpResponse
HttpResponse(entity = "PONG!")
case r: HttpRequest =>
r.discardEntityBytes() // important to drain incoming HTTP Entity stream
HttpResponse(404, entity = "Unknown resource!")
}
val bindingFuture = Http().bindAndHandleSync(requestHandler, "localhost", 9000)
println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
StdIn.readLine() // let it run until user presses return
bindingFuture
.flatMap(_.unbind()) // trigger unbinding from the port
.onComplete(_ => system.terminate()) // and shutdown when done
}
}
正如我在上面的代码中提到的 "Post" 请求,我需要读取 json 数组的请求正文数据并进行一些计算,最后发送处理后的 json 到 HTTPResponse。甚至高级 API 也尝试过,但它再次陷入编组。谁能解释一下或帮助我。
我尝试了另一种方法如下-
object MainController {
// needed to run the route
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
implicit val executionContext = system.dispatcher
final case class hexRecord(hexstring: String)
final case class DeviceData(hexData: List[hexRecord])
// formats for unmarshalling and marshalling
implicit val contentFormat = jsonFormat1(hexRecord)
implicit val dataFormat = jsonFormat1(DeviceData)
def main(args: Array[String]) {
implicit val formats = org.json4s.DefaultFormats
val requestBody = List.empty[Map[String, Any]]
val route: Route =
concat(
get {
path("welcome"){
complete("Welcome to Parsing Application")}
},
post {
path("parseDeviceData") {
entity(as[DeviceData]) { data => {
val result = data.hexData.map(row => {
val parseData = ParserManager(Hex.decodeHex(row.hexstring.replaceAll("\s", "").toCharArray))
val jsonString = Serialization.writePretty(parseData)
jsonString
}).toArray
complete(result)
}
}
}
}
)
val bindingFuture = Http().bindAndHandle(route, "localhost", 9000)
println(s"Server online at http://localhost:9000/")
StdIn.readLine() // let it run until user presses return
bindingFuture
.flatMap(_.unbind()) // trigger unbinding from the port
.onComplete(_ => system.terminate()) // and shutdown when done
}
}
这里的结果很好,但我在输出中得到了转义字符 -
[
" {\n \"totalsize\" : 128,\n \"devicetypeuno\" : \"2\"} ",
" {\n \"totalsize\" : 128,\n \"devicetypeuno\" : \"2\"} "
]
这是一个示例程序,它读取一个 json 值数组并 return 返回一个新的 json 值。
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.server.Directives._
import akka.stream.ActorMaterializer
import akka.util.Timeout
import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport
import io.circe.generic.semiauto
import io.circe.{Decoder, Encoder}
import scala.concurrent.duration._
import scala.io.StdIn
object WebServer3 extends FailFastCirceSupport {
def main(args: Array[String]) {
implicit val system = ActorSystem("my-system")
implicit val materializer = ActorMaterializer()
implicit val executionContext = system.dispatcher
implicit val timeout: Timeout = 2.seconds
case class Zoo(foo: String, bar: String)
case class DoneZoo(message: String)
implicit val zooDecoder: Decoder[Zoo] = semiauto.deriveDecoder[Zoo]
implicit val zooEncoder: Encoder[Zoo] = semiauto.deriveEncoder[Zoo]
implicit val doneDecoder: Decoder[DoneZoo] = semiauto.deriveDecoder[DoneZoo]
implicit val doneEnecoder: Encoder[DoneZoo] =
semiauto.deriveEncoder[DoneZoo]
val route = path("parseData") {
entity(as[List[Zoo]]) { zoo =>
zoo.foreach(println)
complete(DoneZoo(zoo.foldLeft("") {
case (agg, el) => agg + el.bar + el.foo + ";"
}))
}
}
val bindingFuture = Http().bindAndHandle(route, "localhost", 8080)
println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
StdIn.readLine()
val _ = bindingFuture.flatMap(_.unbind())
}
}
json 是在案例 类 和 circe semi auto derivation 的帮助下定义的。这将为 json 转换创建 Encoder/Decoder 类。您必须将这些类型转换为用于实体编组的 Akka 特定类型,这是在 de.heikoseeberger.akkahttpcirce.FailFastCirceSupport 的帮助下隐式完成的。
在此之后,您可以使用 akka route dsl 来定义您的 http 路由。 entity(as[List[Zoo]])
会将 http 正文读取为 json 并 return 返回 List[Zoo]
您可以在 curl
curl -v -X POST -H 'Content-type: application/json' --data '[{"foo": "Foo", "bar": "Bar"}]' http://localhost:8080/parseData
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying ::1...
* TCP_NODELAY set
* Connection failed
* connect to ::1 port 8080 failed: Connection refused
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> POST /parseData HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
> Content-type: application/json
> Content-Length: 30
>
* upload completely sent off: 30 out of 30 bytes
< HTTP/1.1 200 OK
< Server: akka-http/10.1.7
< Date: Tue, 15 Oct 2019 08:46:56 GMT
< Content-Type: application/json
< Content-Length: 18
<
* Connection #0 to host localhost left intact
{"message":"DONE"}* Closing connection 0
编辑:
Json 序列化必须留给 akka 在指令中处理 entity(as[])
请求和 complete
响应。不要手动创建 JSON。
你可以试试这个。
import spray.json.DefaultJsonProtocol
import spray.httpx.unmarshalling._
import spray.httpx.marshalling._
import spray.http._
import HttpCharsets._
import MediaTypes._
case class Person(name: String, firstName: String, age: Int)
object MyJsonProtocol extends DefaultJsonProtocol {
implicit val PersonFormat = jsonFormat3(Person)
}
import MyJsonProtocol._
import spray.httpx.SprayJsonSupport._
import spray.util._
val bob = Person("Bob", "Parr", 32)
val body = HttpEntity(
contentType = ContentType(`application/json`, `UTF-8`),
string =
"""|{
| "name": "Bob",
| "firstName": "Parr",
| "age": 32
|}""".stripMarginWithNewline("\n")
)
marshal(bob) === Right(body)
body.as[Person] === Right(bob)
就像 jsonFormat1[DeviceData]
和 as
组合帮助您将 JSON 字符串转换为 DeviceData 对象一样,您可以在另一个方向使用相同的技术将结果转换为 JSON,无需手动编组输出。
implicit val parseDataJsonFormat = jsonFormatn[ParserManager]
// You should replace jsonFormatn with appropriate number based on the number of fields in ParserManager.
val result = data.hexData.map(row => {
ParserManager(Hex.decodeHex(row.hexstring.replaceAll("\s", "").toCharArray))
}
)
我使用以下方法做到了 -
case HttpRequest(POST, Uri.Path("/decodedata"), _, entity, _) =>
val chunk = Unmarshal(entity).to[List[DataChunk]]
val deviceData = Await.result(chunk, 1.second)
implicit val formats = org.json4s.DefaultFormats
val resp = deviceData
.map(data =>
Serialization.write(ParserManager(Hex.decodeHex(data.rawData.replaceAll("\s", "").toCharArray)))
).mkString
HttpResponse(entity = HttpEntity(ContentTypes.`application/json`, resp))
case class DataChunk(rawData: String)
object DataChunk {
implicit val dataChunkJsonFormat: RootJsonFormat[DataChunk] = jsonFormat1(DataChunk.apply)
}