+htp add selectPreferredLanguage directive incl. docs
This commit is contained in:
parent
d036aee09f
commit
8fc02dd799
6 changed files with 132 additions and 3 deletions
|
|
@ -58,6 +58,25 @@ class MiscDirectivesExamplesSpec extends RoutingSpec {
|
||||||
responseAs[String] shouldEqual "request entity empty"
|
responseAs[String] shouldEqual "request entity empty"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"selectPreferredLanguage-example" in {
|
||||||
|
val request = Get() ~> `Accept-Language`(
|
||||||
|
Language("en-US"),
|
||||||
|
Language("en") withQValue 0.7f,
|
||||||
|
LanguageRange.`*` withQValue 0.1f,
|
||||||
|
Language("de") withQValue 0.5f)
|
||||||
|
|
||||||
|
request ~> {
|
||||||
|
selectPreferredLanguage("en", "en-US") { lang ⇒
|
||||||
|
complete(lang.toString)
|
||||||
|
}
|
||||||
|
} ~> check { responseAs[String] shouldEqual "en-US" }
|
||||||
|
|
||||||
|
request ~> {
|
||||||
|
selectPreferredLanguage("de-DE", "hu") { lang ⇒
|
||||||
|
complete(lang.toString)
|
||||||
|
}
|
||||||
|
} ~> check { responseAs[String] shouldEqual "de-DE" }
|
||||||
|
}
|
||||||
"validate-example" in {
|
"validate-example" in {
|
||||||
val route =
|
val route =
|
||||||
extractUri { uri =>
|
extractUri { uri =>
|
||||||
|
|
|
||||||
|
|
@ -197,6 +197,8 @@ Directive Description
|
||||||
:ref:`-responseEncodingAccepted-` Rejects the request with an ``UnacceptedResponseEncodingRejection`` if the
|
:ref:`-responseEncodingAccepted-` Rejects the request with an ``UnacceptedResponseEncodingRejection`` if the
|
||||||
given response encoding is not accepted by the client
|
given response encoding is not accepted by the client
|
||||||
:ref:`-scheme-` Rejects all requests whose URI scheme doesn't match the given one
|
:ref:`-scheme-` Rejects all requests whose URI scheme doesn't match the given one
|
||||||
|
:ref:`-selectPreferredLanguage-` Inspects the request's ``Accept-Language`` header and determines, which of
|
||||||
|
a given set of language alternatives is preferred by the client
|
||||||
:ref:`-setCookie-` Adds a ``Set-Cookie`` response header with the given cookies
|
:ref:`-setCookie-` Adds a ``Set-Cookie`` response header with the given cookies
|
||||||
:ref:`-textract-` Extracts a number of values using a ``RequestContext ⇒ Tuple`` function
|
:ref:`-textract-` Extracts a number of values using a ``RequestContext ⇒ Tuple`` function
|
||||||
:ref:`-tprovide-` Injects a given tuple of values into a directive
|
:ref:`-tprovide-` Injects a given tuple of values into a directive
|
||||||
|
|
|
||||||
|
|
@ -10,4 +10,5 @@ MiscDirectives
|
||||||
rejectEmptyResponse
|
rejectEmptyResponse
|
||||||
requestEntityEmpty
|
requestEntityEmpty
|
||||||
requestEntityPresent
|
requestEntityPresent
|
||||||
|
selectPreferredLanguage
|
||||||
validate
|
validate
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
.. _-selectPreferredLanguage-:
|
||||||
|
|
||||||
|
selectPreferredLanguage
|
||||||
|
=======================
|
||||||
|
|
||||||
|
Signature
|
||||||
|
---------
|
||||||
|
|
||||||
|
.. includecode2:: /../../akka-http/src/main/scala/akka/http/scaladsl/server/directives/MiscDirectives.scala
|
||||||
|
:snippet: selectPreferredLanguage
|
||||||
|
|
||||||
|
Description
|
||||||
|
-----------
|
||||||
|
Inspects the request's ``Accept-Language`` header and determines,
|
||||||
|
which of a given set of language alternatives is preferred by the client according to content negotiation rules
|
||||||
|
defined by http://tools.ietf.org/html/rfc7231#section-5.3.5.
|
||||||
|
|
||||||
|
If there are several best language alternatives that the client has equal preference for
|
||||||
|
(even if this preference is zero!) the order of the arguments is used as a tie breaker (first one wins).
|
||||||
|
|
||||||
|
Example
|
||||||
|
-------
|
||||||
|
|
||||||
|
.. includecode2:: ../../../../code/docs/http/scaladsl/server/directives/MiscDirectivesExamplesSpec.scala
|
||||||
|
:snippet: selectPreferredLanguage-example
|
||||||
|
|
@ -5,11 +5,11 @@
|
||||||
package akka.http.scaladsl.server
|
package akka.http.scaladsl.server
|
||||||
package directives
|
package directives
|
||||||
|
|
||||||
|
import scala.concurrent.{ Await, Promise }
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
import scala.util.Try
|
||||||
import akka.http.scaladsl.model._
|
import akka.http.scaladsl.model._
|
||||||
import headers._
|
import headers._
|
||||||
import HttpMethods._
|
|
||||||
import MediaTypes._
|
|
||||||
import Uri._
|
|
||||||
|
|
||||||
class MiscDirectivesSpec extends RoutingSpec {
|
class MiscDirectivesSpec extends RoutingSpec {
|
||||||
|
|
||||||
|
|
@ -30,4 +30,63 @@ class MiscDirectivesSpec extends RoutingSpec {
|
||||||
} ~> check { responseAs[String] shouldEqual "1.2.3.4" }
|
} ~> check { responseAs[String] shouldEqual "1.2.3.4" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"the selectPreferredLanguage directive" should {
|
||||||
|
"Accept-Language: de, en" test { selectFrom ⇒
|
||||||
|
selectFrom("de", "en") shouldEqual "de"
|
||||||
|
selectFrom("en", "de") shouldEqual "de"
|
||||||
|
}
|
||||||
|
"Accept-Language: en, de" test { selectFrom ⇒
|
||||||
|
selectFrom("de", "en") shouldEqual "en"
|
||||||
|
selectFrom("en", "de") shouldEqual "en"
|
||||||
|
}
|
||||||
|
"Accept-Language: en, de;q=.5" test { selectFrom ⇒
|
||||||
|
selectFrom("de", "en") shouldEqual "en"
|
||||||
|
selectFrom("en", "de") shouldEqual "en"
|
||||||
|
}
|
||||||
|
"Accept-Language: en;q=.5, de" test { selectFrom ⇒
|
||||||
|
selectFrom("de", "en") shouldEqual "de"
|
||||||
|
selectFrom("en", "de") shouldEqual "de"
|
||||||
|
}
|
||||||
|
"Accept-Language: en-US, en;q=.7, *;q=.1, de;q=.5" test { selectFrom ⇒
|
||||||
|
selectFrom("en", "en-US") shouldEqual "en-US"
|
||||||
|
selectFrom("de", "en") shouldEqual "en"
|
||||||
|
selectFrom("de", "hu") shouldEqual "de"
|
||||||
|
selectFrom("de-DE", "hu") shouldEqual "de-DE"
|
||||||
|
selectFrom("hu", "es") shouldEqual "hu"
|
||||||
|
selectFrom("es", "hu") shouldEqual "es"
|
||||||
|
}
|
||||||
|
"Accept-Language: en, *;q=.5, de;q=0" test { selectFrom ⇒
|
||||||
|
selectFrom("es", "de") shouldEqual "es"
|
||||||
|
selectFrom("de", "es") shouldEqual "es"
|
||||||
|
selectFrom("es", "en") shouldEqual "en"
|
||||||
|
}
|
||||||
|
"Accept-Language: en, *;q=0" test { selectFrom ⇒
|
||||||
|
selectFrom("es", "de") shouldEqual "es"
|
||||||
|
selectFrom("de", "es") shouldEqual "de"
|
||||||
|
selectFrom("es", "en") shouldEqual "en"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
implicit class AddStringToIn(acceptLanguageHeaderString: String) {
|
||||||
|
def test(body: ((String*) ⇒ String) ⇒ Unit): Unit =
|
||||||
|
s"properly handle `$acceptLanguageHeaderString`" in {
|
||||||
|
val Array(name, value) = acceptLanguageHeaderString.split(':')
|
||||||
|
val acceptLanguageHeader = HttpHeader.parse(name.trim, value) match {
|
||||||
|
case HttpHeader.ParsingResult.Ok(h: `Accept-Language`, Nil) ⇒ h
|
||||||
|
case result ⇒ fail(result.toString)
|
||||||
|
}
|
||||||
|
body { availableLangs ⇒
|
||||||
|
val selected = Promise[String]()
|
||||||
|
val first = Language(availableLangs.head)
|
||||||
|
val more = availableLangs.tail.map(Language(_))
|
||||||
|
Get() ~> addHeader(acceptLanguageHeader) ~> {
|
||||||
|
selectPreferredLanguage(first, more: _*) { lang ⇒
|
||||||
|
complete(lang.toString)
|
||||||
|
}
|
||||||
|
} ~> check(selected.complete(Try(responseAs[String])))
|
||||||
|
Await.result(selected.future, 1.second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,29 @@ trait MiscDirectives {
|
||||||
* be treated as if the request could not be matched.
|
* be treated as if the request could not be matched.
|
||||||
*/
|
*/
|
||||||
def rejectEmptyResponse: Directive0 = MiscDirectives._rejectEmptyResponse
|
def rejectEmptyResponse: Directive0 = MiscDirectives._rejectEmptyResponse
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inspects the request's `Accept-Language` header and determines,
|
||||||
|
* which of the given language alternatives is preferred by the client.
|
||||||
|
* (See http://tools.ietf.org/html/rfc7231#section-5.3.5 for more details on the
|
||||||
|
* negotiation logic.)
|
||||||
|
* If there are several best language alternatives that the client
|
||||||
|
* has equal preference for (even if this preference is zero!)
|
||||||
|
* the order of the arguments is used as a tie breaker (First one wins).
|
||||||
|
*/
|
||||||
|
def selectPreferredLanguage(first: Language, more: Language*): Directive1[Language] = {
|
||||||
|
val available = first :: List(more: _*) // we use List rather than Seq since element count is likely very small
|
||||||
|
BasicDirectives.extractRequest.map { req ⇒
|
||||||
|
val sortedWithQValues = available.zip(available.map(req.qValueForLanguage(_))).sortBy(-_._2)
|
||||||
|
val firstBest = sortedWithQValues.head
|
||||||
|
val moreBest = sortedWithQValues.tail.takeWhile(_._2 == firstBest._2)
|
||||||
|
if (moreBest.nonEmpty) {
|
||||||
|
// we have several languages that have the same qvalue, so we pick the one the `Accept-Header` lists first
|
||||||
|
val allBest = firstBest :: moreBest
|
||||||
|
req.acceptedLanguageRanges.flatMap(range ⇒ allBest.find(t ⇒ range.matches(t._1))).head._1
|
||||||
|
} else firstBest._1 // we have a single best match, so pick that
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object MiscDirectives extends MiscDirectives {
|
object MiscDirectives extends MiscDirectives {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue