+htp: #16651 Add formFieldMap, formFieldMultiMap & formFieldSeq Directives
This commit is contained in:
parent
ecc916abfd
commit
edf0d6cb21
8 changed files with 251 additions and 12 deletions
|
|
@ -0,0 +1,29 @@
|
|||
.. _-formFieldMap-:
|
||||
|
||||
formFieldMap
|
||||
============
|
||||
|
||||
Signature
|
||||
---------
|
||||
|
||||
.. includecode2:: /../../akka-http/src/main/scala/akka/http/scaladsl/server/directives/FormFieldDirectives.scala
|
||||
:snippet: formFieldMap
|
||||
|
||||
Description
|
||||
-----------
|
||||
Extracts all HTTP form fields at once as a ``Map[String, String]`` mapping form field names to form field values.
|
||||
|
||||
If form data contain a field value several times, the map will contain the last one.
|
||||
|
||||
See :ref:`-formFields-` for an in-depth description.
|
||||
|
||||
Warning
|
||||
-------
|
||||
Use of this directive can result in performance degradation or even in ``OutOfMemoryError`` s.
|
||||
See :ref:`-formFieldSeq-` for details.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode2:: ../../../../code/docs/http/scaladsl/server/directives/FormFieldDirectivesExamplesSpec.scala
|
||||
:snippet: formFieldMap
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
.. _-formFieldMultiMap-:
|
||||
|
||||
formFieldMultiMap
|
||||
=================
|
||||
|
||||
Signature
|
||||
---------
|
||||
|
||||
.. includecode2:: /../../akka-http/src/main/scala/akka/http/scaladsl/server/directives/FormFieldDirectives.scala
|
||||
:snippet: formFieldMultiMap
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
Extracts all HTTP form fields at once as a multi-map of type ``Map[String, List[String]`` mapping
|
||||
a form name to a list of all its values.
|
||||
|
||||
This directive can be used if form fields can occur several times.
|
||||
|
||||
The order of values is *not* specified.
|
||||
|
||||
See :ref:`-formFields-` for an in-depth description.
|
||||
|
||||
Warning
|
||||
-------
|
||||
Use of this directive can result in performance degradation or even in ``OutOfMemoryError`` s.
|
||||
See :ref:`-formFieldSeq-` for details.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode2:: ../../../../code/docs/http/scaladsl/server/directives/FormFieldDirectivesExamplesSpec.scala
|
||||
:snippet: formFieldMultiMap
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
.. _-formFieldSeq-:
|
||||
|
||||
formFieldSeq
|
||||
============
|
||||
|
||||
Signature
|
||||
---------
|
||||
|
||||
.. includecode2:: /../../akka-http/src/main/scala/akka/http/scaladsl/server/directives/FormFieldDirectives.scala
|
||||
:snippet: formFieldSeq
|
||||
|
||||
Description
|
||||
-----------
|
||||
Extracts all HTTP form fields at once in the original order as (name, value) tuples of type ``(String, String)``.
|
||||
|
||||
This directive can be used if the exact order of form fields is important or if parameters can occur several times.
|
||||
|
||||
See :ref:`-formFields-` for an in-depth description.
|
||||
|
||||
Warning
|
||||
-------
|
||||
The directive reads all incoming HTT form fields without any configured upper bound.
|
||||
It means, that requests with form fields holding significant amount of data (ie. during a file upload)
|
||||
can cause performance issues or even an ``OutOfMemoryError`` s.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode2:: ../../../../code/docs/http/scaladsl/server/directives/FormFieldDirectivesExamplesSpec.scala
|
||||
:snippet: formFieldSeq
|
||||
|
|
@ -44,5 +44,53 @@ class FormFieldDirectivesExamplesSpec extends RoutingSpec {
|
|||
responseAs[String] shouldEqual "Request is missing required form field 'color'"
|
||||
}
|
||||
}
|
||||
"formFieldMap" in {
|
||||
val route =
|
||||
formFieldMap { fields =>
|
||||
def formFieldString(formField: (String, String)): String =
|
||||
s"""${formField._1} = '${formField._2}'"""
|
||||
complete(s"The form fields are ${fields.map(formFieldString).mkString(", ")}")
|
||||
}
|
||||
|
||||
// tests:
|
||||
Post("/", FormData("color" -> "blue", "count" -> "42")) ~> route ~> check {
|
||||
responseAs[String] shouldEqual "The form fields are color = 'blue', count = '42'"
|
||||
}
|
||||
Post("/", FormData("x" -> "1", "x" -> "5")) ~> route ~> check {
|
||||
responseAs[String] shouldEqual "The form fields are x = '5'"
|
||||
}
|
||||
}
|
||||
"formFieldMultiMap" in {
|
||||
val route =
|
||||
formFieldMultiMap { fields =>
|
||||
complete("There are " +
|
||||
s"form fields ${fields.map(x => x._1 + " -> " + x._2.size).mkString(", ")}")
|
||||
}
|
||||
|
||||
// tests:
|
||||
Post("/", FormData("color" -> "blue", "count" -> "42")) ~> route ~> check {
|
||||
responseAs[String] shouldEqual "There are form fields color -> 1, count -> 1"
|
||||
}
|
||||
Post("/", FormData("x" -> "23", "x" -> "4", "x" -> "89")) ~> route ~> check {
|
||||
responseAs[String] shouldEqual "There are form fields x -> 3"
|
||||
}
|
||||
}
|
||||
"formFieldSeq" in {
|
||||
val route =
|
||||
formFieldSeq { fields =>
|
||||
def formFieldString(formField: (String, String)): String =
|
||||
s"""${formField._1} = '${formField._2}'"""
|
||||
complete(s"The form fields are ${fields.map(formFieldString).mkString(", ")}")
|
||||
}
|
||||
|
||||
// tests:
|
||||
Post("/", FormData("color" -> "blue", "count" -> "42")) ~> route ~> check {
|
||||
responseAs[String] shouldEqual "The form fields are color = 'blue', count = '42'"
|
||||
}
|
||||
Post("/", FormData("x" -> "23", "x" -> "4", "x" -> "89")) ~> route ~> check {
|
||||
responseAs[String] shouldEqual "The form fields are x = '23', x = '4', x = '89'"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,13 @@ Directive Description
|
|||
closest :ref:`-handleExceptions-` directive and its ``ExceptionHandler``
|
||||
:ref:`-fileUpload-` Provides a stream of an uploaded file from a multipart request
|
||||
:ref:`-formField-` Extracts an HTTP form field from the request
|
||||
:ref:`-formFieldMap-` Extracts a number of HTTP form field from the request as
|
||||
a ``Map[String, String]``
|
||||
:ref:`-formFieldMultiMap-` Extracts a number of HTTP form field from the request as
|
||||
a ``Map[String, List[String]``
|
||||
:ref:`-formFields-` Extracts a number of HTTP form field from the request
|
||||
:ref:`-formFieldSeq-` Extracts a number of HTTP form field from the request as
|
||||
a ``Seq[(String, String)]``
|
||||
:ref:`-get-` Rejects all non-GET requests
|
||||
:ref:`-getFromBrowseableDirectories-` Serves the content of the given directories as a file-system browser, i.e.
|
||||
files are sent and directories served as browseable listings
|
||||
|
|
|
|||
|
|
@ -8,3 +8,6 @@ FormFieldDirectives
|
|||
|
||||
formField
|
||||
formFields
|
||||
formFieldSeq
|
||||
formFieldMap
|
||||
formFieldMultiMap
|
||||
|
|
@ -162,4 +162,33 @@ class FormFieldDirectivesSpec extends RoutingSpec {
|
|||
} ~> check { responseAs[String] === "List(3, 10)" }
|
||||
}
|
||||
}
|
||||
|
||||
"The 'formFieldMap' directive" should {
|
||||
"extract fields with different keys" in {
|
||||
Post("/", FormData("age" -> "42", "numberA" -> "3", "numberB" -> "5")) ~> {
|
||||
formFieldMap { echoComplete }
|
||||
} ~> check { responseAs[String] shouldEqual "Map(age -> 42, numberA -> 3, numberB -> 5)" }
|
||||
}
|
||||
}
|
||||
|
||||
"The 'formFieldSeq' directive" should {
|
||||
"extract all fields" in {
|
||||
Post("/", FormData("age" -> "42", "number" -> "3", "number" -> "5")) ~> {
|
||||
formFieldSeq { echoComplete }
|
||||
} ~> check { responseAs[String] shouldEqual "Vector((age,42), (number,3), (number,5))" }
|
||||
}
|
||||
"produce empty Seq when FormData is empty" in {
|
||||
Post("/", FormData.Empty) ~> {
|
||||
formFieldSeq { echoComplete }
|
||||
} ~> check { responseAs[String] shouldEqual "Vector()" }
|
||||
}
|
||||
}
|
||||
|
||||
"The 'formFieldMultiMap' directive" should {
|
||||
"extract fields with different keys (with duplicates)" in {
|
||||
Post("/", FormData("age" -> "42", "number" -> "3", "number" -> "5")) ~> {
|
||||
formFieldMultiMap { echoComplete }
|
||||
} ~> check { responseAs[String] shouldEqual "Map(age -> List(42), number -> List(5, 3))" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,16 +5,35 @@
|
|||
package akka.http.scaladsl.server
|
||||
package directives
|
||||
|
||||
import akka.http.impl.util._
|
||||
import akka.http.scaladsl.common._
|
||||
import akka.http.scaladsl.server.directives.RouteDirectives._
|
||||
import akka.http.scaladsl.unmarshalling.Unmarshaller.UnsupportedContentTypeException
|
||||
import akka.http.scaladsl.util.FastFuture._
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.collection.immutable
|
||||
import scala.concurrent.Future
|
||||
import scala.util.{ Failure, Success }
|
||||
import akka.http.scaladsl.unmarshalling.Unmarshaller.UnsupportedContentTypeException
|
||||
import akka.http.scaladsl.common._
|
||||
import akka.http.impl.util._
|
||||
import akka.http.scaladsl.util.FastFuture._
|
||||
|
||||
trait FormFieldDirectives extends ToNameReceptacleEnhancements {
|
||||
import FormFieldDirectives._
|
||||
|
||||
/**
|
||||
* Extracts HTTP form fields from the request as a ``Map[String, String]``.
|
||||
*/
|
||||
def formFieldMap: Directive1[Map[String, String]] = _formFieldMap
|
||||
|
||||
/**
|
||||
* Extracts HTTP form fields from the request as a ``Map[String, List[String]]``.
|
||||
*/
|
||||
def formFieldMultiMap: Directive1[Map[String, List[String]]] = _formFieldMultiMap
|
||||
|
||||
/**
|
||||
* Extracts HTTP form fields from the request as a ``Seq[(String, String)]``.
|
||||
*/
|
||||
def formFieldSeq: Directive1[immutable.Seq[(String, String)]] = _formFieldSeq
|
||||
|
||||
/**
|
||||
* Extracts an HTTP form field from the request.
|
||||
* Rejects the request if the defined form field matcher(s) don't match.
|
||||
|
|
@ -40,6 +59,50 @@ trait FormFieldDirectives extends ToNameReceptacleEnhancements {
|
|||
}
|
||||
|
||||
object FormFieldDirectives extends FormFieldDirectives {
|
||||
|
||||
private val _formFieldSeq: Directive1[immutable.Seq[(String, String)]] = {
|
||||
import BasicDirectives._
|
||||
import FutureDirectives._
|
||||
import akka.http.scaladsl.unmarshalling._
|
||||
|
||||
extract { ctx ⇒
|
||||
import ctx.{ executionContext, materializer }
|
||||
Unmarshal(ctx.request.entity).to[StrictForm].fast.flatMap { form ⇒
|
||||
val fields = form.fields.collect {
|
||||
case (name, field) if name.nonEmpty ⇒
|
||||
Unmarshal(field).to[String].map(fieldString ⇒ (name, fieldString))
|
||||
}
|
||||
Future.sequence(fields)
|
||||
}
|
||||
}.flatMap { sequenceF ⇒
|
||||
onComplete(sequenceF).flatMap {
|
||||
case Success(x) ⇒ provide(x)
|
||||
case Failure(x: UnsupportedContentTypeException) ⇒ reject(UnsupportedRequestContentTypeRejection(x.supported))
|
||||
case Failure(_) ⇒ reject // TODO Use correct rejections
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val _formFieldMultiMap: Directive1[Map[String, List[String]]] = {
|
||||
@tailrec def append(
|
||||
map: Map[String, List[String]],
|
||||
fields: immutable.Seq[(String, String)]): Map[String, List[String]] = {
|
||||
if (fields.isEmpty) {
|
||||
map
|
||||
} else {
|
||||
val (key, value) = fields.head
|
||||
append(map.updated(key, value :: map.getOrElse(key, Nil)), fields.tail)
|
||||
}
|
||||
}
|
||||
|
||||
_formFieldSeq.map {
|
||||
case seq ⇒
|
||||
append(Map.empty, seq)
|
||||
}
|
||||
}
|
||||
|
||||
private val _formFieldMap: Directive1[Map[String, String]] = _formFieldSeq.map(_.toMap)
|
||||
|
||||
sealed trait FieldMagnet {
|
||||
type Out
|
||||
def apply(): Out
|
||||
|
|
@ -64,10 +127,10 @@ object FormFieldDirectives extends FormFieldDirectives {
|
|||
def apply(value: A) = f(value)
|
||||
}
|
||||
|
||||
import akka.http.scaladsl.unmarshalling.{ FromStrictFormFieldUnmarshaller ⇒ FSFFU, _ }
|
||||
import BasicDirectives._
|
||||
import RouteDirectives._
|
||||
import FutureDirectives._
|
||||
import RouteDirectives._
|
||||
import akka.http.scaladsl.unmarshalling.{ FromStrictFormFieldUnmarshaller ⇒ FSFFU, _ }
|
||||
type SFU = FromEntityUnmarshaller[StrictForm]
|
||||
type FSFFOU[T] = Unmarshaller[Option[StrictForm.Field], T]
|
||||
|
||||
|
|
@ -82,8 +145,7 @@ object FormFieldDirectives extends FormFieldDirectives {
|
|||
//////////////////// "regular" formField extraction ////////////////////
|
||||
|
||||
private def fieldOfForm[T](fieldName: String, fu: Unmarshaller[Option[StrictForm.Field], T])(implicit sfu: SFU): RequestContext ⇒ Future[T] = { ctx ⇒
|
||||
import ctx.executionContext
|
||||
import ctx.materializer
|
||||
import ctx.{ executionContext, materializer }
|
||||
sfu(ctx.request.entity).fast.flatMap(form ⇒ fu(form field fieldName))
|
||||
}
|
||||
private def filter[T](fieldName: String, fu: FSFFOU[T])(implicit sfu: SFU): Directive1[T] =
|
||||
|
|
@ -124,8 +186,7 @@ object FormFieldDirectives extends FormFieldDirectives {
|
|||
|
||||
private def repeatedFilter[T](fieldName: String, fu: FSFFU[T])(implicit sfu: SFU): Directive1[Iterable[T]] =
|
||||
extract { ctx ⇒
|
||||
import ctx.executionContext
|
||||
import ctx.materializer
|
||||
import ctx.{ executionContext, materializer }
|
||||
sfu(ctx.request.entity).fast.flatMap(form ⇒ Future.sequence(form.fields.collect { case (`fieldName`, value) ⇒ fu(value) }))
|
||||
}.flatMap { result ⇒
|
||||
handleFieldResult(fieldName, result)
|
||||
|
|
@ -137,8 +198,8 @@ object FormFieldDirectives extends FormFieldDirectives {
|
|||
|
||||
//////////////////// tuple support ////////////////////
|
||||
|
||||
import akka.http.scaladsl.server.util.TupleOps._
|
||||
import akka.http.scaladsl.server.util.BinaryPolyFunc
|
||||
import akka.http.scaladsl.server.util.TupleOps._
|
||||
|
||||
implicit def forTuple[T](implicit fold: FoldLeft[Directive0, T, ConvertFieldDefAndConcatenate.type]): FieldDefAux[T, fold.Out] =
|
||||
fieldDef[T, fold.Out](fold(pass, _))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue