From 8116d2b9bb3a60bf37ee182287dc66838fa8bf53 Mon Sep 17 00:00:00 2001 From: Johannes Rudolph Date: Fri, 17 Oct 2014 10:42:54 +0200 Subject: [PATCH] +htp #15919 import CookieDirectives --- .../directives/CookieDirectivesSpec.scala | 96 +++++++++++++++++++ .../scala/akka/http/server/Directives.scala | 2 +- .../server/directives/CookieDirectives.scala | 58 +++++++++++ 3 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 akka-http-tests/src/test/scala/akka/http/server/directives/CookieDirectivesSpec.scala create mode 100644 akka-http/src/main/scala/akka/http/server/directives/CookieDirectives.scala diff --git a/akka-http-tests/src/test/scala/akka/http/server/directives/CookieDirectivesSpec.scala b/akka-http-tests/src/test/scala/akka/http/server/directives/CookieDirectivesSpec.scala new file mode 100644 index 0000000000..70c70eae63 --- /dev/null +++ b/akka-http-tests/src/test/scala/akka/http/server/directives/CookieDirectivesSpec.scala @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2009-2014 Typesafe Inc. + */ + +package akka.http.server +package directives + +import akka.http.model._ +import StatusCodes.OK +import headers._ +import akka.http.util.DateTime + +class CookieDirectivesSpec extends RoutingSpec { + + val deletedTimeStamp = DateTime.fromIsoDateTimeString("1800-01-01T00:00:00") + + "The 'cookie' directive" should { + "extract the respectively named cookie" in { + Get() ~> addHeader(Cookie(HttpCookie("fancy", "pants"))) ~> { + cookie("fancy") { echoComplete } + } ~> check { responseAs[String] shouldEqual "fancy=pants" } + } + "reject the request if the cookie is not present" in { + Get() ~> { + cookie("fancy") { echoComplete } + } ~> check { rejection shouldEqual MissingCookieRejection("fancy") } + } + "properly pass through inner rejections" in { + Get() ~> addHeader(Cookie(HttpCookie("fancy", "pants"))) ~> { + cookie("fancy") { c ⇒ reject(ValidationRejection("Dont like " + c.content)) } + } ~> check { rejection shouldEqual ValidationRejection("Dont like pants") } + } + } + + "The 'deleteCookie' directive" should { + "add a respective Set-Cookie headers to successful responses" in { + Get() ~> { + deleteCookie("myCookie", "test.com") { completeOk } + } ~> check { + status shouldEqual OK + header[`Set-Cookie`] shouldEqual Some(`Set-Cookie`(HttpCookie("myCookie", "deleted", expires = deletedTimeStamp, + domain = Some("test.com")))) + } + } + + "support deleting multiple cookies at a time" in { + Get() ~> { + deleteCookie(HttpCookie("myCookie", "test.com"), HttpCookie("myCookie2", "foobar.com")) { completeOk } + } ~> check { + status shouldEqual OK + headers.collect { case `Set-Cookie`(x) ⇒ x } shouldEqual List( + HttpCookie("myCookie", "deleted", expires = deletedTimeStamp), + HttpCookie("myCookie2", "deleted", expires = deletedTimeStamp)) + } + } + } + + "The 'optionalCookie' directive" should { + "produce a `Some(cookie)` extraction if the cookie is present" in { + Get() ~> Cookie(HttpCookie("abc", "123")) ~> { + optionalCookie("abc") { echoComplete } + } ~> check { responseAs[String] shouldEqual "Some(abc=123)" } + } + "produce a `None` extraction if the cookie is not present" in { + Get() ~> optionalCookie("abc") { echoComplete } ~> check { responseAs[String] shouldEqual "None" } + } + "let rejections from its inner route pass through" in { + Get() ~> { + optionalCookie("test-cookie") { _ ⇒ + validate(false, "ouch") { completeOk } + } + } ~> check { rejection shouldEqual ValidationRejection("ouch") } + } + } + + "The 'setCookie' directive" should { + "add a respective Set-Cookie headers to successful responses" in { + Get() ~> { + setCookie(HttpCookie("myCookie", "test.com")) { completeOk } + } ~> check { + status shouldEqual OK + header[`Set-Cookie`] shouldEqual Some(`Set-Cookie`(HttpCookie("myCookie", "test.com"))) + } + } + + "support setting multiple cookies at a time" in { + Get() ~> { + setCookie(HttpCookie("myCookie", "test.com"), HttpCookie("myCookie2", "foobar.com")) { completeOk } + } ~> check { + status shouldEqual OK + headers.collect { case `Set-Cookie`(x) ⇒ x } shouldEqual List( + HttpCookie("myCookie", "test.com"), HttpCookie("myCookie2", "foobar.com")) + } + } + } +} diff --git a/akka-http/src/main/scala/akka/http/server/Directives.scala b/akka-http/src/main/scala/akka/http/server/Directives.scala index 8e75e57c87..42d068c626 100644 --- a/akka-http/src/main/scala/akka/http/server/Directives.scala +++ b/akka-http/src/main/scala/akka/http/server/Directives.scala @@ -12,7 +12,7 @@ trait Directives extends RouteConcatenation with BasicDirectives with CacheConditionDirectives //with ChunkingDirectives - //with CookieDirectives + with CookieDirectives //with DebuggingDirectives with CodingDirectives with ExecutionDirectives diff --git a/akka-http/src/main/scala/akka/http/server/directives/CookieDirectives.scala b/akka-http/src/main/scala/akka/http/server/directives/CookieDirectives.scala new file mode 100644 index 0000000000..e5752b98d9 --- /dev/null +++ b/akka-http/src/main/scala/akka/http/server/directives/CookieDirectives.scala @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2009-2014 Typesafe Inc. + */ + +package akka.http.server +package directives + +import akka.http.model._ +import headers._ +import akka.http.util._ + +trait CookieDirectives { + import HeaderDirectives._ + import RespondWithDirectives._ + import RouteDirectives._ + + /** + * Extracts an HttpCookie with the given name. If the cookie is not present the + * request is rejected with a respective [[spray.routing.MissingCookieRejection]]. + */ + def cookie(name: String): Directive1[HttpCookie] = + headerValue(findCookie(name)) | reject(MissingCookieRejection(name)) + + /** + * Extracts an HttpCookie with the given name. + * If the cookie is not present a value of `None` is extracted. + */ + def optionalCookie(name: String): Directive1[Option[HttpCookie]] = + optionalHeaderValue(findCookie(name)) + + private def findCookie(name: String): HttpHeader ⇒ Option[HttpCookie] = { + case Cookie(cookies) ⇒ cookies.find(_.name == name) + case _ ⇒ None + } + + /** + * Adds a Set-Cookie header with the given cookies to all responses of its inner route. + */ + def setCookie(first: HttpCookie, more: HttpCookie*): Directive0 = + respondWithHeaders((first :: more.toList).map(`Set-Cookie`(_))) + + /** + * Adds a Set-Cookie header expiring the given cookies to all responses of its inner route. + */ + def deleteCookie(first: HttpCookie, more: HttpCookie*): Directive0 = + respondWithHeaders((first :: more.toList).map { c ⇒ + `Set-Cookie`(c.copy(content = "deleted", expires = Some(DateTime.MinValue))) + }) + + /** + * Adds a Set-Cookie header expiring the given cookie to all responses of its inner route. + */ + def deleteCookie(name: String, domain: String = "", path: String = ""): Directive0 = + deleteCookie(HttpCookie(name, "", domain = domain.toOption, path = path.toOption)) + +} + +object CookieDirectives extends CookieDirectives