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/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); + } } 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 { 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]) { 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 802c758008..e22e658987 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)) {