16

One of the features of Akka HTTP (formally known as Spray) is its ability to automagically marshal and unmarshal data back and forth from json into case classes, etc. I've had success at getting this to work well.

At the moment, I am trying to make an HTTP client that performs a GET request with query parameters. The code currently looks like this:

val httpResponse: Future[HttpResponse] =
  Http().singleRequest(HttpRequest(
    uri = s"""http://${config.getString("http.serverHost")}:${config.getInt("http.port")}/""" +
          s"query?seq=${seq}" +
          s"&max-mismatches=${maxMismatches}" +
          s"&pam-policy=${pamPolicy}"))

Well, that's not so pretty. It would be nice if I could just pass in a case class containing the query parameters, and have Akka HTTP automagically generate the query parameters, kind of like it does for json. (Also, the server side of Akka HTTP has a somewhat elegant way of parsing GET query parameters, so one would think that it would also have a somewhat elegant way to generate them.)

I'd like to do something like the following:

val httpResponse: Future[HttpResponse] =
  Http().singleRequest(HttpRequest(
    uri = s"""http://${config.getString("http.serverHost")}:${config.getInt("http.port")}/query""",
    entity = QueryParams(seq = seq, maxMismatches = maxMismatches, pamPolicy = pamPolicy)))

Only, the above doesn't actually work.

Is what I want doable somehow with Akka HTTP? Or do I just need to do things the old-fashioned way? I.e, generate the query parameters explicitly, as I do in the first code block above.

(I know that if I were to change this from a GET to a POST, I could probably to get it to work more like I would like it to work, since then I could get the contents of the POST request automagically converted from a case class to json, but I don't really wish to do that here.)

Douglas
  • 2,186
  • 2
  • 19
  • 25

2 Answers2

29

You can leverage the Uri class to do what you want. It offers multiple ways to get a set of params into the query string using the withQuery method. For example, you could do something like this:

val params = Map("foo" -> "bar", "hello" -> "world")
HttpRequest(Uri(hostAndPath).withQuery(params))

Or

HttpRequest(Uri(hostAndPath).withQuery(("foo" -> "bar"), ("hello" -> "world")))
cmbaxter
  • 34,269
  • 4
  • 81
  • 93
  • 3
    The good thing using this method is that escaping will be done automatically using this method. – jrudolph Aug 11 '15 at 19:54
  • 20
    With Akka-http 2.4.8, this seems to need a `Query` wrapping: `.withQuery(Query(params)))` – akauppi Jul 20 '16 at 12:48
  • 1
    This should be in the documentation – coolboyjules May 30 '18 at 18:33
  • @coolboyjules exactly. This answer is great, but pity that one has to search so much around the internet before landing here. This belongs in the documentation. On my todo list - since the code for documentation is open source. – Niks Nov 04 '20 at 16:14
  • And it's now part of the documentation :) https://doc.akka.io/docs/akka-http/10.2/client-side/request-and-response.html which is linked to a concrete example here https://doc.akka.io/docs/akka-http/10.2/common/uri-model.html#query-string-in-uri – Niks Jan 05 '21 at 15:12
0

Obviously this could be done by altering the extending the capability of Akka HTTP, but for what you need (just a tidier way to build the query string), you could do it with some scala fun:

type QueryParams = Map[String, String]

object QueryParams {

  def apply(tuples: (String, String)*): QueryParams = Map(tuples:_*)
}

implicit class QueryParamExtensions(q: QueryParams) {

  def toQueryString = "?"+q.map{
    case (key,value) => s"$key=$value" //Need to do URL escaping here?
  }.mkString("&")
}

implicit class StringQueryExtensions(url: String) {
  def withParams(q: QueryParams) =
    url + q.toQueryString
}

val params = QueryParams(
  "abc" -> "def",
  "xyz" -> "qrs"
)

params.toQueryString // gives ?abc=def&xyz=qrs

"http://www.google.com".withParams(params) // gives http://www.google.com?abc=def&xyz=qrs
mattinbits
  • 9,711
  • 1
  • 22
  • 34