From 31742b15ef8d3a9ab3296b1e36ce36a5b0d014c4 Mon Sep 17 00:00:00 2001 From: Johannes Rudolph Date: Fri, 12 Jun 2015 16:41:14 +0200 Subject: [PATCH 1/5] +htc some convenience methods for javadsl.model.HttpCookie --- .../javadsl/model/headers/HttpCookie.java | 43 +++++++++++++++++++ .../scaladsl/model/headers/HttpCookie.scala | 15 +++++++ 2 files changed, 58 insertions(+) diff --git a/akka-http-core/src/main/java/akka/http/javadsl/model/headers/HttpCookie.java b/akka-http-core/src/main/java/akka/http/javadsl/model/headers/HttpCookie.java index 3faf0e266c..b7d1f2cc00 100644 --- a/akka-http-core/src/main/java/akka/http/javadsl/model/headers/HttpCookie.java +++ b/akka-http-core/src/main/java/akka/http/javadsl/model/headers/HttpCookie.java @@ -28,6 +28,14 @@ public abstract class HttpCookie { false, false, Util.scalaNone()); } + public static HttpCookie create(String name, String value, Option domain, Option path) { + return new akka.http.scaladsl.model.headers.HttpCookie( + name, value, + Util.scalaNone(), Util.scalaNone(), + domain.asScala(), path.asScala(), + false, false, + Util.scalaNone()); + } @SuppressWarnings("unchecked") public static HttpCookie create( String name, @@ -49,4 +57,39 @@ public abstract class HttpCookie { httpOnly, extension.asScala()); } + + /** + * Returns a copy of this HttpCookie instance with the given expiration set. + */ + public abstract HttpCookie withExpires(DateTime dateTime); + + /** + * Returns a copy of this HttpCookie instance with the given max age set. + */ + public abstract HttpCookie withMaxAge(long maxAge); + + /** + * Returns a copy of this HttpCookie instance with the given domain set. + */ + public abstract HttpCookie withDomain(String domain); + + /** + * Returns a copy of this HttpCookie instance with the given path set. + */ + public abstract HttpCookie withPath(String path); + + /** + * Returns a copy of this HttpCookie instance with the given secure flag set. + */ + public abstract HttpCookie withSecure(boolean secure); + + /** + * Returns a copy of this HttpCookie instance with the given http-only flag set. + */ + public abstract HttpCookie withHttpOnly(boolean httpOnly); + + /** + * Returns a copy of this HttpCookie instance with the given extension set. + */ + public abstract HttpCookie withExtension(String extension); } diff --git a/akka-http-core/src/main/scala/akka/http/scaladsl/model/headers/HttpCookie.scala b/akka-http-core/src/main/scala/akka/http/scaladsl/model/headers/HttpCookie.scala index 717b3c1feb..4c246544e6 100644 --- a/akka-http-core/src/main/scala/akka/http/scaladsl/model/headers/HttpCookie.scala +++ b/akka-http-core/src/main/scala/akka/http/scaladsl/model/headers/HttpCookie.scala @@ -4,6 +4,7 @@ package akka.http.scaladsl.model.headers +import akka.http.javadsl.model.headers import akka.parboiled2.CharPredicate import akka.japi.{ Option ⇒ JOption } import akka.http.scaladsl.model.DateTime @@ -77,6 +78,20 @@ final case class HttpCookie( def getMaxAge: JOption[java.lang.Long] = maxAge.asJava /** Java API */ def getExpires: JOption[jm.DateTime] = expires.asJava + /** Java API */ + def withExpires(dateTime: jm.DateTime): headers.HttpCookie = copy(expires = Some(dateTime.asScala)) + /** Java API */ + def withDomain(domain: String): headers.HttpCookie = copy(domain = Some(domain)) + /** Java API */ + def withPath(path: String): headers.HttpCookie = copy(path = Some(path)) + /** Java API */ + def withMaxAge(maxAge: Long): headers.HttpCookie = copy(maxAge = Some(maxAge)) + /** Java API */ + def withSecure(secure: Boolean): headers.HttpCookie = copy(secure = secure) + /** Java API */ + def withHttpOnly(httpOnly: Boolean): headers.HttpCookie = copy(httpOnly = httpOnly) + /** Java API */ + def withExtension(extension: String): headers.HttpCookie = copy(extension = Some(extension)) } object HttpCookie { From 90918204991c7683ca80d7cc6dfd2ec222d1f7b3 Mon Sep 17 00:00:00 2001 From: Johannes Rudolph Date: Tue, 12 May 2015 15:51:15 +0200 Subject: [PATCH 2/5] +htc add missing constructor for javadsl.model.headers.RawHeader --- .../main/java/akka/http/javadsl/model/headers/RawHeader.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/akka-http-core/src/main/java/akka/http/javadsl/model/headers/RawHeader.java b/akka-http-core/src/main/java/akka/http/javadsl/model/headers/RawHeader.java index e444d4d3bc..aec58b09f7 100644 --- a/akka-http-core/src/main/java/akka/http/javadsl/model/headers/RawHeader.java +++ b/akka-http-core/src/main/java/akka/http/javadsl/model/headers/RawHeader.java @@ -7,4 +7,8 @@ package akka.http.javadsl.model.headers; public abstract class RawHeader extends akka.http.scaladsl.model.HttpHeader { public abstract String name(); public abstract String value(); + + public static RawHeader create(String name, String value) { + return new akka.http.scaladsl.model.headers.RawHeader(name, value); + } } From 1d5332a3114354b3e3da4812b4e3b141ff9791d7 Mon Sep 17 00:00:00 2001 From: Johannes Rudolph Date: Wed, 17 Jun 2015 09:41:27 +0200 Subject: [PATCH 3/5] =htp add more tests for PathDirectives and fix test descriptions --- .../directives/PathDirectivesSpec.scala | 57 +++++++++++++------ .../http/scaladsl/server/PathMatcher.scala | 1 + 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/akka-http-tests/src/test/scala/akka/http/scaladsl/server/directives/PathDirectivesSpec.scala b/akka-http-tests/src/test/scala/akka/http/scaladsl/server/directives/PathDirectivesSpec.scala index 812167e6d9..e12a694146 100644 --- a/akka-http-tests/src/test/scala/akka/http/scaladsl/server/directives/PathDirectivesSpec.scala +++ b/akka-http-tests/src/test/scala/akka/http/scaladsl/server/directives/PathDirectivesSpec.scala @@ -53,16 +53,16 @@ class PathDirectivesSpec extends RoutingSpec with Inside { val test = testFor(pathPrefix("ab[cd]+".r) { echoCaptureAndUnmatchedPath }) "reject [/bar]" in test() "reject [/ab/cd]" in test() - "reject [/abcdef]" in test("abcd:ef") - "reject [/abcdd/ef]" in test("abcdd:/ef") + "accept [/abcdef]" in test("abcd:ef") + "accept [/abcdd/ef]" in test("abcdd:/ef") } """pathPrefix("ab(cd)".r)""" should { val test = testFor(pathPrefix("ab(cd)+".r) { echoCaptureAndUnmatchedPath }) "reject [/bar]" in test() "reject [/ab/cd]" in test() - "reject [/abcdef]" in test("cd:ef") - "reject [/abcde/fg]" in test("cd:e/fg") + "accept [/abcdef]" in test("cd:ef") + "accept [/abcde/fg]" in test("cd:e/fg") } "pathPrefix(regex)" should { @@ -132,40 +132,40 @@ class PathDirectivesSpec extends RoutingSpec with Inside { val test = testFor(pathPrefix(separateOnSlashes("a/b")) { echoUnmatchedPath }) "accept [/a/b]" in test("") "accept [/a/b/]" in test("/") - "accept [/a/c]" in test() + "reject [/a/c]" in test() } """pathPrefix(separateOnSlashes("abc"))""" should { val test = testFor(pathPrefix(separateOnSlashes("abc")) { echoUnmatchedPath }) "accept [/abc]" in test("") "accept [/abcdef]" in test("def") - "accept [/ab]" in test() + "reject [/ab]" in test() } """pathPrefixTest("a" / Segment ~ Slash)""" should { val test = testFor(pathPrefixTest("a" / Segment ~ Slash) { echoCaptureAndUnmatchedPath }) "accept [/a/bc/]" in test("bc:/a/bc/") - "accept [/a/bc]" in test() - "accept [/a/]" in test() + "reject [/a/bc]" in test() + "reject [/a/]" in test() } """pathSuffix("edit" / Segment)""" should { val test = testFor(pathSuffix("edit" / Segment) { echoCaptureAndUnmatchedPath }) "accept [/orders/123/edit]" in test("123:/orders/") - "accept [/orders/123/ed]" in test() - "accept [/edit]" in test() + "reject [/orders/123/ed]" in test() + "reject [/edit]" in test() } """pathSuffix("foo" / "bar" ~ "baz")""" should { val test = testFor(pathSuffix("foo" / "bar" ~ "baz") { echoUnmatchedPath }) "accept [/orders/barbaz/foo]" in test("/orders/") - "accept [/orders/bazbar/foo]" in test() + "reject [/orders/bazbar/foo]" in test() } "pathSuffixTest(Slash)" should { val test = testFor(pathSuffixTest(Slash) { echoUnmatchedPath }) "accept [/]" in test("/") "accept [/foo/]" in test("/foo/") - "accept [/foo]" in test() + "reject [/foo]" in test() } """pathPrefix("foo" | "bar")""" should { @@ -242,6 +242,20 @@ class PathDirectivesSpec extends RoutingSpec with Inside { } } + """rawPathPrefix(Slash ~ "a" / Segment ~ Slash)""" should { + val test = testFor(rawPathPrefix(Slash ~ "a" / Segment ~ Slash) { echoCaptureAndUnmatchedPath }) + "accept [/a/bc/]" in test("bc:") + "reject [/a/bc]" in test() + "reject [/ab/]" in test() + } + + """rawPathPrefixTest(Slash ~ "a" / Segment ~ Slash)""" should { + val test = testFor(rawPathPrefixTest(Slash ~ "a" / Segment ~ Slash) { echoCaptureAndUnmatchedPath }) + "accept [/a/bc/]" in test("bc:/a/bc/") + "reject [/a/bc]" in test() + "reject [/ab/]" in test() + } + "PathMatchers" should { { val test = testFor(path(Rest.tmap { case Tuple1(s) ⇒ Tuple1(s.split('-').toList) }) { echoComplete }) @@ -270,11 +284,20 @@ class PathDirectivesSpec extends RoutingSpec with Inside { case class testFor(route: Route) { def apply(expectedResponse: String = null): String ⇒ Unit = exampleString ⇒ - "\\[([^\\]]+)\\]".r.findFirstMatchIn(exampleString) match { - case Some(uri) ⇒ Get(uri.group(1)) ~> route ~> check { - if (expectedResponse eq null) handled shouldEqual false - else responseAs[String] shouldEqual expectedResponse - } + """(accept|reject)\s+\[([^\]]+)\]""".r.findFirstMatchIn(exampleString) match { + case Some(uri) ⇒ + uri.group(1) match { + case "accept" if expectedResponse eq null ⇒ + failTest("Example '" + exampleString + "' was missing an expectedResponse") + case "reject" if expectedResponse ne null ⇒ + failTest("Example '" + exampleString + "' had an expectedResponse") + case _ ⇒ + } + + Get(uri.group(2)) ~> route ~> check { + if (expectedResponse eq null) handled shouldEqual false + else responseAs[String] shouldEqual expectedResponse + } case None ⇒ failTest("Example '" + exampleString + "' doesn't contain a test uri") } } diff --git a/akka-http/src/main/scala/akka/http/scaladsl/server/PathMatcher.scala b/akka-http/src/main/scala/akka/http/scaladsl/server/PathMatcher.scala index ddf579c41a..e63d36341e 100644 --- a/akka-http/src/main/scala/akka/http/scaladsl/server/PathMatcher.scala +++ b/akka-http/src/main/scala/akka/http/scaladsl/server/PathMatcher.scala @@ -146,6 +146,7 @@ object PathMatcher extends ImplicitPathMatcherConstruction { else Unmatched } + /** Provoke implicit conversions to PathMatcher to be applied */ def apply[L](magnet: PathMatcher[L]): PathMatcher[L] = magnet implicit class PathMatcher1Ops[T](matcher: PathMatcher1[T]) { From b0ea8c935a2b140c664eb90ea461a2bc9988295d Mon Sep 17 00:00:00 2001 From: Johannes Rudolph Date: Thu, 18 Jun 2015 11:34:29 +0200 Subject: [PATCH 4/5] !htp remove implicit ExecutionContext parameter where possible It seems they were left over from aa8da46068795a1110ed3ce07cf648fedaf1000d. --- .../unmarshalling/MultipartUnmarshallers.scala | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/akka-http/src/main/scala/akka/http/scaladsl/unmarshalling/MultipartUnmarshallers.scala b/akka-http/src/main/scala/akka/http/scaladsl/unmarshalling/MultipartUnmarshallers.scala index e5a6dfe317..36c58a6088 100644 --- a/akka-http/src/main/scala/akka/http/scaladsl/unmarshalling/MultipartUnmarshallers.scala +++ b/akka-http/src/main/scala/akka/http/scaladsl/unmarshalling/MultipartUnmarshallers.scala @@ -6,7 +6,6 @@ package akka.http.scaladsl.unmarshalling import scala.collection.immutable import scala.collection.immutable.VectorBuilder -import scala.concurrent.ExecutionContext import akka.util.ByteString import akka.event.{ NoLogging, LoggingAdapter } import akka.stream.impl.fusing.IteratorInterpreter @@ -21,9 +20,9 @@ import HttpCharsets._ trait MultipartUnmarshallers { - implicit def defaultMultipartGeneralUnmarshaller(implicit ec: ExecutionContext, log: LoggingAdapter = NoLogging): FromEntityUnmarshaller[Multipart.General] = + implicit def defaultMultipartGeneralUnmarshaller(implicit log: LoggingAdapter = NoLogging): FromEntityUnmarshaller[Multipart.General] = multipartGeneralUnmarshaller(`UTF-8`) - def multipartGeneralUnmarshaller(defaultCharset: HttpCharset)(implicit ec: ExecutionContext, log: LoggingAdapter = NoLogging): FromEntityUnmarshaller[Multipart.General] = + def multipartGeneralUnmarshaller(defaultCharset: HttpCharset)(implicit log: LoggingAdapter = NoLogging): FromEntityUnmarshaller[Multipart.General] = multipartUnmarshaller[Multipart.General, Multipart.General.BodyPart, Multipart.General.BodyPart.Strict]( mediaRange = `multipart/*`, defaultContentType = ContentTypes.`text/plain` withCharset defaultCharset, @@ -32,7 +31,7 @@ trait MultipartUnmarshallers { createStrictBodyPart = Multipart.General.BodyPart.Strict, createStrict = Multipart.General.Strict) - implicit def multipartFormDataUnmarshaller(implicit ec: ExecutionContext, log: LoggingAdapter = NoLogging): FromEntityUnmarshaller[Multipart.FormData] = + implicit def multipartFormDataUnmarshaller(implicit log: LoggingAdapter = NoLogging): FromEntityUnmarshaller[Multipart.FormData] = multipartUnmarshaller[Multipart.FormData, Multipart.FormData.BodyPart, Multipart.FormData.BodyPart.Strict]( mediaRange = `multipart/form-data`, defaultContentType = ContentTypes.`application/octet-stream`, @@ -41,9 +40,9 @@ trait MultipartUnmarshallers { createStrictBodyPart = (entity, headers) ⇒ Multipart.General.BodyPart.Strict(entity, headers).toFormDataBodyPart.get, createStrict = (_, parts) ⇒ Multipart.FormData.Strict(parts)) - implicit def defaultMultipartByteRangesUnmarshaller(implicit ec: ExecutionContext, log: LoggingAdapter = NoLogging): FromEntityUnmarshaller[Multipart.ByteRanges] = + implicit def defaultMultipartByteRangesUnmarshaller(implicit log: LoggingAdapter = NoLogging): FromEntityUnmarshaller[Multipart.ByteRanges] = multipartByteRangesUnmarshaller(`UTF-8`) - def multipartByteRangesUnmarshaller(defaultCharset: HttpCharset)(implicit ec: ExecutionContext, log: LoggingAdapter = NoLogging): FromEntityUnmarshaller[Multipart.ByteRanges] = + def multipartByteRangesUnmarshaller(defaultCharset: HttpCharset)(implicit log: LoggingAdapter = NoLogging): FromEntityUnmarshaller[Multipart.ByteRanges] = multipartUnmarshaller[Multipart.ByteRanges, Multipart.ByteRanges.BodyPart, Multipart.ByteRanges.BodyPart.Strict]( mediaRange = `multipart/byteranges`, defaultContentType = ContentTypes.`text/plain` withCharset defaultCharset, @@ -57,7 +56,7 @@ trait MultipartUnmarshallers { createBodyPart: (BodyPartEntity, List[HttpHeader]) ⇒ BP, createStreamed: (MultipartMediaType, Source[BP, Any]) ⇒ T, createStrictBodyPart: (HttpEntity.Strict, List[HttpHeader]) ⇒ BPS, - createStrict: (MultipartMediaType, immutable.Seq[BPS]) ⇒ T)(implicit ec: ExecutionContext, log: LoggingAdapter = NoLogging): FromEntityUnmarshaller[T] = + createStrict: (MultipartMediaType, immutable.Seq[BPS]) ⇒ T)(implicit log: LoggingAdapter = NoLogging): FromEntityUnmarshaller[T] = Unmarshaller { implicit ec ⇒ entity ⇒ if (entity.contentType.mediaType.isMultipart && mediaRange.matches(entity.contentType.mediaType)) { From 10fb88e2bc63fb8c016e2e79145de9f9adeb65f3 Mon Sep 17 00:00:00 2001 From: Johannes Rudolph Date: Thu, 18 Jun 2015 16:20:47 +0200 Subject: [PATCH 5/5] =htp add more explicit type annotations to implicit definitions --- .../scaladsl/server/directives/FormFieldDirectives.scala | 6 +++--- .../scaladsl/server/directives/ParameterDirectives.scala | 2 +- .../scala/akka/http/scaladsl/server/util/TupleOps.scala | 8 +++++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/akka-http/src/main/scala/akka/http/scaladsl/server/directives/FormFieldDirectives.scala b/akka-http/src/main/scala/akka/http/scaladsl/server/directives/FormFieldDirectives.scala index f9c8525f30..c027812a98 100644 --- a/akka-http/src/main/scala/akka/http/scaladsl/server/directives/FormFieldDirectives.scala +++ b/akka-http/src/main/scala/akka/http/scaladsl/server/directives/FormFieldDirectives.scala @@ -115,11 +115,11 @@ object FormFieldDirectives extends FormFieldDirectives { import akka.http.scaladsl.server.util.TupleOps._ import akka.http.scaladsl.server.util.BinaryPolyFunc - implicit def forTuple[T](implicit fold: FoldLeft[Directive0, T, ConvertParamDefAndConcatenate.type]): FieldDefAux[T, fold.Out] = + implicit def forTuple[T](implicit fold: FoldLeft[Directive0, T, ConvertFieldDefAndConcatenate.type]): FieldDefAux[T, fold.Out] = fieldDef[T, fold.Out](fold(pass, _)) - object ConvertParamDefAndConcatenate extends BinaryPolyFunc { - implicit def from[P, TA, TB](implicit fdef: FieldDefAux[P, Directive[TB]], ev: Join[TA, TB]) = + object ConvertFieldDefAndConcatenate extends BinaryPolyFunc { + implicit def from[P, TA, TB](implicit fdef: FieldDefAux[P, Directive[TB]], ev: Join[TA, TB]): BinaryPolyFunc.Case[Directive[TA], P, ConvertFieldDefAndConcatenate.type] { type Out = Directive[ev.Out] } = at[Directive[TA], P] { (a, t) ⇒ a & fdef(t) } } } diff --git a/akka-http/src/main/scala/akka/http/scaladsl/server/directives/ParameterDirectives.scala b/akka-http/src/main/scala/akka/http/scaladsl/server/directives/ParameterDirectives.scala index a9c3b64be0..0f007fa336 100644 --- a/akka-http/src/main/scala/akka/http/scaladsl/server/directives/ParameterDirectives.scala +++ b/akka-http/src/main/scala/akka/http/scaladsl/server/directives/ParameterDirectives.scala @@ -137,7 +137,7 @@ object ParameterDirectives extends ParameterDirectives { paramDef[T, fold.Out](fold(BasicDirectives.pass, _)) object ConvertParamDefAndConcatenate extends BinaryPolyFunc { - implicit def from[P, TA, TB](implicit pdef: ParamDef[P] { type Out = Directive[TB] }, ev: Join[TA, TB]) = + implicit def from[P, TA, TB](implicit pdef: ParamDef[P] { type Out = Directive[TB] }, ev: Join[TA, TB]): BinaryPolyFunc.Case[Directive[TA], P, ConvertParamDefAndConcatenate.type] { type Out = Directive[ev.Out] } = at[Directive[TA], P] { (a, t) ⇒ a & pdef(t) } } } diff --git a/akka-http/src/main/scala/akka/http/scaladsl/server/util/TupleOps.scala b/akka-http/src/main/scala/akka/http/scaladsl/server/util/TupleOps.scala index 2df524f102..e6cf4c8fbf 100644 --- a/akka-http/src/main/scala/akka/http/scaladsl/server/util/TupleOps.scala +++ b/akka-http/src/main/scala/akka/http/scaladsl/server/util/TupleOps.scala @@ -42,20 +42,22 @@ object TupleOps { type Out def apply(prefix: P, suffix: S): Out } + type JoinAux[P, S, O] = Join[P, S] { type Out = O } object Join extends LowLevelJoinImplicits { // O(1) shortcut for the Join[Unit, T] case to avoid O(n) runtime in this case - implicit def join0P[T] = + implicit def join0P[T]: JoinAux[Unit, T, T] = new Join[Unit, T] { type Out = T def apply(prefix: Unit, suffix: T): Out = suffix } // we implement the join by folding over the suffix with the prefix as growing accumulator object Fold extends BinaryPolyFunc { - implicit def step[T, A](implicit append: AppendOne[T, A]) = at[T, A](append(_, _)) + implicit def step[T, A](implicit append: AppendOne[T, A]): BinaryPolyFunc.Case[T, A, Fold.type] { type Out = append.Out } = + at[T, A](append(_, _)) } } sealed abstract class LowLevelJoinImplicits { - implicit def join[P, S](implicit fold: FoldLeft[P, S, Join.Fold.type]) = + implicit def join[P, S](implicit fold: FoldLeft[P, S, Join.Fold.type]): JoinAux[P, S, fold.Out] = new Join[P, S] { type Out = fold.Out def apply(prefix: P, suffix: S): Out = fold(prefix, suffix)