diff --git a/akka-docs-dev/rst/java/code/docs/http/javadsl/server/FormFieldRequestValsExampleTest.java b/akka-docs-dev/rst/java/code/docs/http/javadsl/server/FormFieldRequestValsExampleTest.java new file mode 100644 index 0000000000..d79636ba9a --- /dev/null +++ b/akka-docs-dev/rst/java/code/docs/http/javadsl/server/FormFieldRequestValsExampleTest.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2009-2015 Typesafe Inc. + */ + +package docs.http.javadsl.server; + +import akka.http.javadsl.model.ContentTypes; +import akka.http.javadsl.model.FormData; +import akka.http.javadsl.model.HttpRequest; +import akka.http.javadsl.model.headers.RawHeader; +import akka.http.javadsl.server.Marshallers; +import akka.http.javadsl.server.RequestVal; +import akka.http.javadsl.server.Route; +import akka.http.javadsl.server.values.FormField; +import akka.http.javadsl.server.values.FormFields; +import akka.http.javadsl.server.values.Headers; +import akka.http.javadsl.testkit.JUnitRouteTest; +import akka.japi.Pair; +import docs.http.scaladsl.server.directives.Person; +import org.junit.Ignore; +import org.junit.Test; + +public class FormFieldRequestValsExampleTest extends JUnitRouteTest { + + @Test + public void testFormFieldVals() { + //#simple + FormField name = FormFields.stringValue("name"); + FormField age = FormFields.intValue("age"); + + final Route route = + route( + handleWith2(name, age, (ctx, n, a) -> + ctx.complete(String.format("Name: %s, age: %d", n, a)) + ) + ); + + // tests: + final FormData formData = FormData.create( + Pair.create("name", "Blippy"), + Pair.create("age", "42")); + final HttpRequest request = + HttpRequest + .POST("/") + .withEntity(formData.toEntity()); + testRoute(route).run(request).assertEntity("Name: Blippy, age: 42"); + + //#simple + } + + @Test + public void testFormFieldValsUnmarshaling() { + //#custom-unmarshal + FormField sampleId = FormFields.fromString("id", SampleId.class, s -> new SampleId(Integer.valueOf(s))); + + final Route route = + route( + handleWith1(sampleId, (ctx, sid) -> + ctx.complete(String.format("SampleId: %s", sid.id)) + ) + ); + + // tests: + final FormData formData = FormData.create(Pair.create("id", "1337")); + final HttpRequest request = + HttpRequest + .POST("/") + .withEntity(formData.toEntity()); + testRoute(route).run(request).assertEntity("SampleId: 1337"); + + //#custom-unmarshal + } + + static class SampleId { + public final int id; + + SampleId(int id) { + this.id = id; + } + } + + +} \ No newline at end of file diff --git a/akka-docs-dev/rst/java/code/docs/http/javadsl/server/HeaderRequestValsExampleTest.java b/akka-docs-dev/rst/java/code/docs/http/javadsl/server/HeaderRequestValsExampleTest.java new file mode 100644 index 0000000000..c7872959d6 --- /dev/null +++ b/akka-docs-dev/rst/java/code/docs/http/javadsl/server/HeaderRequestValsExampleTest.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2009-2015 Typesafe Inc. + */ + +package docs.http.javadsl.server; + +import akka.http.javadsl.model.HttpRequest; +import akka.http.javadsl.model.headers.Host; +import akka.http.javadsl.model.headers.RawHeader; +import akka.http.javadsl.server.RequestVal; +import akka.http.javadsl.server.Route; +import akka.http.javadsl.server.values.Headers; +import akka.http.javadsl.testkit.JUnitRouteTest; +import org.junit.Test; + +public class HeaderRequestValsExampleTest extends JUnitRouteTest { + + @Test + public void testHeaderVals() { + //#by-class + // extract the entire header instance: + RequestVal host = Headers.byClass(Host.class).instance(); + + final Route route = + route( + handleWith1(host, (ctx, h) -> + ctx.complete(String.format("Host header was: %s", h.host())) + ) + ); + + // tests: + final HttpRequest request = + HttpRequest + .GET("http://akka.io/") + .addHeader(Host.create("akka.io", 80)); + testRoute(route).run(request).assertEntity("Host header was: akka.io"); + + //#by-class + } + + @Test + public void testHeaderByName() { + //#by-name + // extract the `value` of the header: + final RequestVal XFishName = Headers.byName("X-Fish-Name").value(); + + final Route route = + route( + handleWith1(XFishName, (ctx, xFishName) -> + ctx.complete(String.format("The `X-Fish-Name` header's value was: %s", xFishName)) + ) + ); + + // tests: + final HttpRequest request = + HttpRequest + .GET("/") + .addHeader(RawHeader.create("X-Fish-Name", "Blippy")); + testRoute(route).run(request).assertEntity("The `X-Fish-Name` header's value was: Blippy"); + + //#by-name + } +} \ No newline at end of file diff --git a/akka-docs-dev/rst/java/http/routing-dsl/request-vals/form-field-request-vals.rst b/akka-docs-dev/rst/java/http/routing-dsl/request-vals/form-field-request-vals.rst new file mode 100644 index 0000000000..c72031a706 --- /dev/null +++ b/akka-docs-dev/rst/java/http/routing-dsl/request-vals/form-field-request-vals.rst @@ -0,0 +1,25 @@ +.. _form-field-request-vals-java: + +Request Values: FormFields +========================== + +A collection of pre-defined :ref:`request-vals-java` that can be used to extract header values from incoming requests. + +Description +----------- +``FormField`` request values allow extracting fields submitted as ``application/x-www-form-urlencoded`` values or concrete instances from HTTP requests. + +The ``RequestVal`` builder is made up of 2 steps, initially you need to pick which Header to extract (``byName`` or +``byClass``) and then you need to pick if the header is optionally available or required (i.e. the route should not +match if the header is not present in the request). This is done using one of the below depicted methods:: + +Examples +-------- + +Extracting form fields of a certain primitive type from a request: + +.. includecode:: ../../../code/docs/http/javadsl/server/FormFieldRequestValsExampleTest.java#simple + +Extracting values of custom type from a request by providing a conversion function: + +.. includecode:: ../../../code/docs/http/javadsl/server/FormFieldRequestValsExampleTest.java#custom-unmarshal diff --git a/akka-docs-dev/rst/java/http/routing-dsl/request-vals/header-request-vals.rst b/akka-docs-dev/rst/java/http/routing-dsl/request-vals/header-request-vals.rst new file mode 100644 index 0000000000..f73bf5317e --- /dev/null +++ b/akka-docs-dev/rst/java/http/routing-dsl/request-vals/header-request-vals.rst @@ -0,0 +1,33 @@ +.. _header-request-vals-java: + +Request Values: Headers +======================= + +A collection of pre-defined :ref:`request-vals-java` that can be used to extract header values from incoming requests. + +Description +----------- +Header request values allow extracting ``HttpHeader`` values or concrete instances from HTTP requests. + +The ``RequestVal`` builder is made up of 2 steps, initially you need to pick which Header to extract (``byName`` or +``byClass``) and then you need to pick if the header is optionally available or required (i.e. the route should not +match if the header is not present in the request). This is done using one of the below depicted methods:: + + RequestVal instance() + RequestVal<> optionalInstance() + + RequestVal value() + RequestVal> optionalValue() + +Examples +-------- + +Extracting a header by using a specific ``Header`` class (which are pre-defined in ``akka.http.javadsl.model.headers.*``): + +.. includecode:: ../../../code/docs/http/javadsl/server/HeaderRequestValsExampleTest.java + :include: by-class + +Extracting arbitrary headers by their name, for example custom headers (usually starting with ``X-...``): + +.. includecode:: ../../../code/docs/http/javadsl/server/HeaderRequestValsExampleTest.java + :include: by-name diff --git a/akka-docs-dev/rst/java/http/routing-dsl/request-vals/index.rst b/akka-docs-dev/rst/java/http/routing-dsl/request-vals/index.rst index e808599e77..2a7fb32428 100644 --- a/akka-docs-dev/rst/java/http/routing-dsl/request-vals/index.rst +++ b/akka-docs-dev/rst/java/http/routing-dsl/request-vals/index.rst @@ -25,21 +25,21 @@ Predefined Request values akka-http provides a set of predefined request values for request data commonly accessed in a web service. -These request values are defined: +These request values are defined in the following objects: -RequestVals +:ref:`akka.http.javadsl.server.values.FormFields ` Contains request values for basic data like URI components, request method, peer address, or the entity data. -Cookies +akka.http.javadsl.server.values.FormFieldsCookies Contains request values representing cookies. -FormFields +akka.http.javadsl.server.values.FormFields Contains request values to access form fields unmarshalled to various primitive Java types. -Headers +:ref:`akka.http.javadsl.server.values.Headers ` Contains request values to access request headers or header values. -HttpBasicAuthenticator +akka.http.javadsl.server.values.FormFieldsHttpBasicAuthenticator An abstract class to implement to create a request value representing a HTTP basic authenticated principal. -Parameters +akka.http.javadsl.server.values.FormFieldsParameters Contains request values to access URI paramaters unmarshalled to various primitive Java types. -PathMatchers +akka.http.javadsl.server.values.FormFieldsPathMatchers Contains request values to match and access URI path segments. -CustomRequestVal +akka.http.javadsl.server.values.FormFieldsCustomRequestVal An abstract class to implement arbitrary custom request values. diff --git a/akka-http-core/src/main/java/akka/http/javadsl/model/FormData.java b/akka-http-core/src/main/java/akka/http/javadsl/model/FormData.java new file mode 100644 index 0000000000..1936758d72 --- /dev/null +++ b/akka-http-core/src/main/java/akka/http/javadsl/model/FormData.java @@ -0,0 +1,37 @@ +/** + * Copyright (C) 2009-2014 Typesafe Inc. + */ + +package akka.http.javadsl.model; + +import akka.http.impl.model.parser.CharacterClasses; +import akka.http.impl.util.JavaMapping; +import akka.http.impl.util.StringRendering; +import akka.http.scaladsl.model.Uri.Query; +import akka.http.scaladsl.model.UriRendering; +import akka.japi.Pair; + +/** + * Simple model for `application/x-www-form-urlencoded` form data. + */ +public abstract class FormData { + + public abstract Query fields(); + + public RequestEntity toEntity() { + return toEntity(HttpCharsets.UTF_8); + } + + public RequestEntity toEntity(HttpCharset charset) { + // TODO this logic is duplicated in scaladsl.model.FormData, spent hours trying to DRY it but compiler freaked out in a number of ways... -- ktoso + final akka.http.scaladsl.model.HttpCharset c = (akka.http.scaladsl.model.HttpCharset) charset; + final StringRendering render = (StringRendering) UriRendering.renderQuery(new StringRendering(), this.fields(), c.nioCharset(), CharacterClasses.unreserved()); + return HttpEntities.create(ContentType.create(MediaTypes.APPLICATION_X_WWW_FORM_URLENCODED, charset), render.get()); + } + + @SafeVarargs + public static FormData create(Pair... fields) { + return akka.http.scaladsl.model.FormData.create(fields); + } + +} diff --git a/akka-http-core/src/main/java/akka/http/javadsl/model/headers/Host.java b/akka-http-core/src/main/java/akka/http/javadsl/model/headers/Host.java index fff8b2673d..1ce64adad5 100644 --- a/akka-http-core/src/main/java/akka/http/javadsl/model/headers/Host.java +++ b/akka-http-core/src/main/java/akka/http/javadsl/model/headers/Host.java @@ -7,19 +7,19 @@ package akka.http.javadsl.model.headers; import java.net.InetSocketAddress; public abstract class Host extends akka.http.scaladsl.model.HttpHeader { - + public static Host create(InetSocketAddress address) { return akka.http.scaladsl.model.headers.Host.apply(address); } - + public static Host create(String host) { return akka.http.scaladsl.model.headers.Host.apply(host); } - + public static Host create(String host, int port) { return akka.http.scaladsl.model.headers.Host.apply(host, port); } - + public abstract akka.http.javadsl.model.Host host(); public abstract int port(); } diff --git a/akka-http-core/src/main/scala/akka/http/scaladsl/model/FormData.scala b/akka-http-core/src/main/scala/akka/http/scaladsl/model/FormData.scala index 5f208471b3..6ad16b3d9c 100644 --- a/akka-http-core/src/main/scala/akka/http/scaladsl/model/FormData.scala +++ b/akka-http-core/src/main/scala/akka/http/scaladsl/model/FormData.scala @@ -4,10 +4,25 @@ package akka.http.scaladsl.model +import akka.http.impl.model.parser.CharacterClasses +import akka.http.impl.util.StringRendering +import akka.http.javadsl.{ model ⇒ jm } +import akka.http.scaladsl.model.HttpCharsets._ +import akka.http.scaladsl.model.MediaTypes._ + /** * Simple model for `application/x-www-form-urlencoded` form data. */ -final case class FormData(fields: Uri.Query) +final case class FormData(fields: Uri.Query) extends jm.FormData { + override def toEntity: akka.http.scaladsl.model.RequestEntity = + toEntity(HttpCharsets.`UTF-8`) + + def toEntity(charset: HttpCharset): akka.http.scaladsl.model.RequestEntity = { + // TODO this logic is duplicated in javadsl.model.FormData, spent hours trying to DRY it but compiler freaked out in a number of ways... -- ktoso + val render: StringRendering = UriRendering.renderQuery(new StringRendering, this.fields, charset.nioCharset, CharacterClasses.unreserved) + HttpEntity(ContentType(`application/x-www-form-urlencoded`, `UTF-8`), render.get) + } +} object FormData { val Empty = FormData(Uri.Query.Empty) @@ -17,4 +32,7 @@ object FormData { def apply(fields: (String, String)*): FormData = if (fields.isEmpty) Empty else FormData(Uri.Query(fields: _*)) -} \ No newline at end of file + + def create(fields: Array[akka.japi.Pair[String, String]]): FormData = + if (fields.isEmpty) Empty else FormData(Uri.Query(fields.map(_.toScala): _*)) +} diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/values/FormField.scala b/akka-http/src/main/scala/akka/http/javadsl/server/values/FormField.scala index ffdbda4a6e..beb9448dda 100644 --- a/akka-http/src/main/scala/akka/http/javadsl/server/values/FormField.scala +++ b/akka-http/src/main/scala/akka/http/javadsl/server/values/FormField.scala @@ -6,12 +6,12 @@ package akka.http.javadsl.server package values import java.{ lang ⇒ jl } + +import akka.http.impl.server.{ FormFieldImpl, Util } +import akka.http.scaladsl.unmarshalling._ import akka.japi.function.Function import akka.japi.{ Option ⇒ JOption } -import akka.http.impl.server.{ Util, FormFieldImpl } -import akka.http.scaladsl.unmarshalling._ - import scala.reflect.ClassTag trait FormField[T] extends RequestVal[T] { @@ -36,7 +36,8 @@ object FormFields { def hexIntValue(name: String): FormField[jl.Integer] = FormFieldImpl(name.as(Unmarshaller.HexInt)) def hexLongValue(name: String): FormField[jl.Long] = FormFieldImpl(name.as(Unmarshaller.HexLong)) - def fromString[T](name: String, convert: Function[String, T], clazz: Class[T]): FormField[T] = { + /** Unmarshals the `name` field using the provided `convert` function. */ + def fromString[T](name: String, clazz: Class[T], convert: Function[String, T]): FormField[T] = { implicit val tTag: ClassTag[T] = ClassTag(clazz) FormFieldImpl(name.as(Util.fromStringUnmarshallerFromFunction(convert))) } diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/values/Parameter.scala b/akka-http/src/main/scala/akka/http/javadsl/server/values/Parameter.scala index b1c4865517..093d6e8967 100644 --- a/akka-http/src/main/scala/akka/http/javadsl/server/values/Parameter.scala +++ b/akka-http/src/main/scala/akka/http/javadsl/server/values/Parameter.scala @@ -5,22 +5,17 @@ package akka.http.javadsl.server.values import java.util.AbstractMap.SimpleEntry +import java.util.{ Collection ⇒ JCollection, Map ⇒ JMap } import java.{ lang ⇒ jl } -import java.util.{ Map ⇒ JMap, Collection ⇒ JCollection } - +import akka.http.impl.server.{ ParameterImpl, StandaloneExtractionImpl, Util } +import akka.http.javadsl.server.RequestVal import akka.http.scaladsl.server.directives.ParameterDirectives import akka.http.scaladsl.unmarshalling.Unmarshaller import akka.japi.function.Function - -import scala.reflect.ClassTag - import akka.japi.{ Option ⇒ JOption } -import akka.http.impl.server.{ Util, StandaloneExtractionImpl, ParameterImpl } -import akka.http.javadsl.server.RequestVal -import akka.http.scaladsl.server.Directive1 -import akka.http.scaladsl.server.directives.ParameterDirectives.ParamMagnet +import scala.reflect.ClassTag /** * A RequestVal representing a query parameter of type T. @@ -65,7 +60,8 @@ object Parameters { def asCollection: RequestVal[JCollection[JMap.Entry[String, String]]] = StandaloneExtractionImpl(ParameterDirectives.parameterSeq.map(_.map(e ⇒ new SimpleEntry(e._1, e._2): JMap.Entry[String, String]).asJavaCollection)) - def fromString[T](name: String, convert: Function[String, T], clazz: Class[T]): Parameter[T] = { + /** Unmarshals the `name` field using the provided `convert` function. */ + def fromString[T](name: String, clazz: Class[T], convert: Function[String, T]): Parameter[T] = { implicit val tTag: ClassTag[T] = ClassTag(clazz) ParameterImpl(name.as(Util.fromStringUnmarshallerFromFunction(convert))) } diff --git a/akka-http/src/main/scala/akka/http/scaladsl/marshalling/PredefinedToEntityMarshallers.scala b/akka-http/src/main/scala/akka/http/scaladsl/marshalling/PredefinedToEntityMarshallers.scala index 42ca0cf716..04e1f7aafe 100644 --- a/akka-http/src/main/scala/akka/http/scaladsl/marshalling/PredefinedToEntityMarshallers.scala +++ b/akka-http/src/main/scala/akka/http/scaladsl/marshalling/PredefinedToEntityMarshallers.scala @@ -5,10 +5,9 @@ package akka.http.scaladsl.marshalling import java.nio.CharBuffer -import akka.http.impl.model.parser.CharacterClasses + import akka.http.scaladsl.model.MediaTypes._ import akka.http.scaladsl.model._ -import akka.http.impl.util.StringRendering import akka.util.ByteString trait PredefinedToEntityMarshallers extends MultipartMarshallers { @@ -53,9 +52,7 @@ trait PredefinedToEntityMarshallers extends MultipartMarshallers { implicit val FormDataMarshaller: ToEntityMarshaller[FormData] = Marshaller.withOpenCharset(`application/x-www-form-urlencoded`) { (formData, charset) ⇒ - val query = Uri.Query(formData.fields: _*) - val string = UriRendering.renderQuery(new StringRendering, query, charset.nioCharset, CharacterClasses.unreserved).get - HttpEntity(ContentType(`application/x-www-form-urlencoded`, charset), string) + formData.toEntity(charset) } implicit val MessageEntityMarshaller: ToEntityMarshaller[MessageEntity] = Marshaller strict { value ⇒