Adding headers to an Akka HTTP HttpRequest - scala

I have an existing Akka HTTP HttpRequest and I want to add two headers to it.
val req: HttpRequest = ???
val hs: Seq[HttpHeader] = Seq(RawHeader("a", "b"))
req.addHeaders(hs)
Expected:
a new HttpRequest object with the additional headers
Actual:
.addHeaders expects a java.lang.Iterable and does not compile.
What is the recommended way of doing this in Scala?
There is a workaround, but it's a bit cludgy:
req.withHeaders(req.headers ++ hs)
Running Scala 2.12.8 and Akka HTTP 10.1.7.

Another workaround that is maybe a tiny sliver less cludgy. This is approximately how addHeaders is defined in the source. I unfortunately have no clue why addHeaders is not exposed in the scala api.
req.mapHeaders(_ ++ hs)

One alternative is to use foldLeft and addHeader:
val req: HttpRequest = ???
val hs: Seq[HttpHeader] = Seq(RawHeader("a", "b"))
hs.foldLeft(req)((r, h) => r.addHeader(h))

You can copy the existing HttpRequest to a new HttpRequest with headers
val req: HttpRequest = ???
val hs: Seq[HttpHeader] = Seq(RawHeader("a", "b"))
val reqWithHeaders: HttpRequest = req.copy(headers=hs)

Related

Scala and Akka HTTP: How to get the entity content as string

I am very new to Akka HTTP so please accept my apologies beforehand for the very elementary question.
In the following piece of code, I want to retrieve the entity from the HTTP request (entity will be plain text), get the text out of the entity, and return it as a response.
implicit val system = ActorSystem("ActorSystem")
implicit val materializer = ActorMaterializer
import system.dispatcher
val requestHandler: Flow[HttpRequest, HttpResponse, _] = Flow[HttpRequest].map {
case HttpRequest(HttpMethods.POST, Uri.Path("/api"), _, entity, _) =>
val entityAsText = ... // <- get entity content as text
HttpResponse(
StatusCodes.OK,
entity = HttpEntity(
ContentTypes.`text/plain(UTF-8)`,
entityAsText
)
)
}
Http().bindAndHandle(requestHandler, "localhost", 8080)
How can I get the string content of the entity?
Many thanks in advance!
One approach is to call toStrict on the RequestEntity, which loads the entity into memory, and mapAsync on the Flow:
import scala.concurrent.duration._
val requestHandler: Flow[HttpRequest, HttpResponse, _] = Flow[HttpRequest].mapAsync(1) {
case HttpRequest(HttpMethods.GET, Uri.Path("/api"), _, entity, _) =>
val entityAsText: Future[String] = entity.toStrict(1 second).map(_.data.utf8String)
entityAsText.map { text =>
HttpResponse(
StatusCodes.OK,
entity = HttpEntity(
ContentTypes.`text/plain(UTF-8)`,
text
)
)
}
}
Adjust the timeout on the former and the parallelism level on the latter as needed.
Another approach would be to use the already Unmarshaller which is in scope (most likely it will be the akka.http.scaladsl.unmarshalling.PredefinedFromEntityUnmarshallers#stringUnmarshaller):
val entityAsText: Future[String] = Unmarshal(entity).to[String]
This approach ensures the consistent usage of the provided Unmarshaller for String and you wouldn't have to cope with timeouts.

How to implement Akka HTTP Request Post Client Headers Authorization

I'am trying to implement Akka http client for post request where Authorization will be given in header from postman. I am not able to authorize, following is my code
def main(args: Array[String]) {
implicit val system: ActorSystem = ActorSystem()
implicit val materializer: ActorMaterializer = ActorMaterializer()
implicit val executionContext: ExecutionContextExecutor = system.dispatcher
val requestHeader = scala.collection.immutable.Seq(RawHeader("Authorization", "admin"))
val requestHandler: HttpRequest => HttpResponse = {
case HttpRequest(POST, Uri.Path("/GetTrackerData"), requestHeader, entity, _) =>
val chunk = Unmarshal(entity).to[DeviceLocationData]
val deviceLocationData = Await.result(chunk, 1.second)
val responseArray = "Authorized"
HttpResponse(entity = HttpEntity(ContentTypes.`application/json`, responseArray)
)
case r: HttpRequest =>
println(r.uri.toString())
r.discardEntityBytes() // important to drain incoming HTTP Entity stream
HttpResponse(404, entity = "Unknown resource!")
}
val bindingFuture = Http().bindAndHandleSync(requestHandler, "0.0.0.0", 7070)
println(s"iot engine api live at 0.0.0.0:7070")
sys.addShutdownHook({
println("ShutdownHook called")
bindingFuture
.flatMap(_.unbind()) // trigger unbinding from the port
.onComplete(_ => system.terminate()) // and shutdown when done
})
}
Whatever value I give from postman. It serves the request. What I am skipping ?
My use case is that result showed be displayed only after authorization
You are pattern matching on HttpRequest.
The requestHeader you use there is not the one you specified earlier but will be the headers from the HttpRequest itself.
One way to resolve it could be checking for the values in the headers:
case HttpRequest(HttpMethods.POST, Uri.Path("/GetTrackerData"), headers, entity, _)
if (headers.exists(h => h.name == "Authorization" && h.value == "admin")) =>
Here is one problem:
case HttpRequest(POST, Uri.Path("/GetTrackerData"), requestHeader, entity, _) =>
This does not match the current value of requestHeader, it creates a new value requestHeader containing the current headers of the HttpRequest. So this match will not check the Authorization field of the header. You will have to do this manually.
More generally, it would be worth looking at the Route support in Akka HTTP as a cleaner and more powerful way of implementing a server.

Akka Stream - How to Stream from multiple SQS Sources

This is a subsequent post of Akka Stream - Select Sink based on Element in Flow.
Assume I have multiple SQS queues I'd like to stream from. I'm using the AWS SQS Connector of Alpakka to create Source.
implicit val sqsClient: AmazonSQSAsync = ???
val queueUrls: List[String] = ???
val sources: List[Source[Message, NotUsed]] = queueUrls.map(url => SqsSource(url))
Now, I'd like to combine the sources to merge them. However, the Source.combine method doesn't support passing a list as parameter, but only support varargs.
def combine[T, U](first: Source[T, _], second: Source[T, _], rest: Source[T, _]*)(strategy: Int ⇒ Graph[UniformFanInShape[T, U], NotUsed])
Of course, I can finger type all sources parameters. But, the parameter will get pretty long if I have 10 source queues.
Is there a way to combine sources from a list of sources?
[Supplement]
As Ramon J Romero y Vigil pointed out, it's a better practice to keep stream "a thin veneer". In this particular case, however, I use single sqsClient for all the SqsSource initialization.
You could use foldLeft to concatenate or merge the sources:
val sources: List[Source[Message, NotUsed]] = ???
val concatenated: Source[Message, NotUsed] = sources.foldLeft(Source.empty[Message])(_ ++ _)
// the same as sources.foldLeft(Source.empty[Message])(_ concat _)
val merged: Source[Message, NotUsed] = sources.foldLeft(Source.empty[Message])(_ merge _)
Alternatively, you could use Source.zipN with flatMapConcat:
val combined: Source[Message, NotUsed] = Source.zipN(sources).flatMapConcat(Source.apply)

How to make htttp request with akka stream for 10K request

I build server with akka-http and akka-stream but it lost some request in 2K+ request. What I miss something or my understand for akka is wrong.
this is my code
implicit val actorRef = ActorSystem("system", testConf)
implicit val materializer = ActorMaterializer(ActorMaterializerSettings(actorRef).withInputBuffer(initialSize = 4, maxSize = 16))
implicit val requestTimeout = Timeout(8 seconds)
def response(req: HttpRequest): Future[Response] = {
val connectionFlow: Flow[HttpRequest, HttpResponse, Future[Http.OutgoingConnection]] =
Http().outgoingConnection(host = "url").async
for {
res <- Source.single(req.copy(uri = s"${req.uri.path}?${req.uri.rawQueryString.get}")).via(connectionFlow).runWith(Sink.head)
data <- res.entity.toStrict(5 second)
} yield (data.getData().decodeString("UTF-8"), res.status.intValue())
}
Thank you.
Most likely there were either timeout or server-side errors in first part of your for-comprehension therefore you got only successful responses in res.
I recommend to create flow/graph that processes your requests with withSupervisionStrategy in your materializer so you can see what exactly went wrong. Details of implementation depend on your business logic.

Spray Unzip HttpResponse

I'm using Spray API(spray-client) to hit an external URL and I'm getting gzipped HttpResponse. How do I unzip this HttpResponse to get its entity(json, in my case)?
val future: Future[HttpResponse] = (IO(Http) ? Get(uri)).mapTo[HttpResponse]
val response = Await.result(future, Duration.inf)
val json = response.entity
Here, json is gzipped. How do I unzip it?
You need to use pipelining and the decode directive. Like in this example.
Modifying that example your code would look something like this:
val pipeline: HttpRequest => Future[String] = (
sendReceive
~> decode(Gzip)
~> unmarshal[String]
)
val response: Future[String] =
pipeline(Get(uri))
You can then do Await on the response if you don't want the benefits of Futures.
On a side note you can use spray-json and create an object for your response and then unmarshal the http response directly into a case class without having to deal with the json.