!htc #16826 introduce HttpCookiePair for usage in Cookie-header

This commit is contained in:
Johannes Rudolph 2015-05-18 16:09:26 +02:00
parent 43cca877fe
commit b2e6b650fd
15 changed files with 115 additions and 59 deletions

View file

@ -13,10 +13,10 @@ class CookieDirectivesExamplesSpec extends RoutingSpec {
"cookie" in { "cookie" in {
val route = val route =
cookie("userName") { nameCookie => cookie("userName") { nameCookie =>
complete(s"The logged in user is '${nameCookie.content}'") complete(s"The logged in user is '${nameCookie.value}'")
} }
Get("/") ~> Cookie(HttpCookie("userName", "paul")) ~> route ~> check { Get("/") ~> Cookie("userName" -> "paul") ~> route ~> check {
responseAs[String] shouldEqual "The logged in user is 'paul'" responseAs[String] shouldEqual "The logged in user is 'paul'"
} }
// missing cookie // missing cookie
@ -30,11 +30,11 @@ class CookieDirectivesExamplesSpec extends RoutingSpec {
"optionalCookie" in { "optionalCookie" in {
val route = val route =
optionalCookie("userName") { optionalCookie("userName") {
case Some(nameCookie) => complete(s"The logged in user is '${nameCookie.content}'") case Some(nameCookie) => complete(s"The logged in user is '${nameCookie.value}'")
case None => complete("No user logged in") case None => complete("No user logged in")
} }
Get("/") ~> Cookie(HttpCookie("userName", "paul")) ~> route ~> check { Get("/") ~> Cookie("userName" -> "paul") ~> route ~> check {
responseAs[String] shouldEqual "The logged in user is 'paul'" responseAs[String] shouldEqual "The logged in user is 'paul'"
} }
Get("/") ~> route ~> check { Get("/") ~> route ~> check {
@ -49,18 +49,18 @@ class CookieDirectivesExamplesSpec extends RoutingSpec {
Get("/") ~> route ~> check { Get("/") ~> route ~> check {
responseAs[String] shouldEqual "The user was logged out" responseAs[String] shouldEqual "The user was logged out"
header[`Set-Cookie`] shouldEqual Some(`Set-Cookie`(HttpCookie("userName", content = "deleted", expires = Some(DateTime.MinValue)))) header[`Set-Cookie`] shouldEqual Some(`Set-Cookie`(HttpCookie("userName", value = "deleted", expires = Some(DateTime.MinValue))))
} }
} }
"setCookie" in { "setCookie" in {
val route = val route =
setCookie(HttpCookie("userName", content = "paul")) { setCookie(HttpCookie("userName", value = "paul")) {
complete("The user was logged in") complete("The user was logged in")
} }
Get("/") ~> route ~> check { Get("/") ~> route ~> check {
responseAs[String] shouldEqual "The user was logged in" responseAs[String] shouldEqual "The user was logged in"
header[`Set-Cookie`] shouldEqual Some(`Set-Cookie`(HttpCookie("userName", content = "paul"))) header[`Set-Cookie`] shouldEqual Some(`Set-Cookie`(HttpCookie("userName", value = "paul")))
} }
} }
} }

View file

@ -9,9 +9,12 @@ package akka.http.javadsl.model.headers;
* Specification: https://tools.ietf.org/html/rfc6265#section-4.2 * Specification: https://tools.ietf.org/html/rfc6265#section-4.2
*/ */
public abstract class Cookie extends akka.http.scaladsl.model.HttpHeader { public abstract class Cookie extends akka.http.scaladsl.model.HttpHeader {
public abstract Iterable<HttpCookie> getCookies(); public abstract Iterable<HttpCookiePair> getCookies();
public static Cookie create(HttpCookie... cookies) { public static Cookie create(HttpCookiePair... cookies) {
return new akka.http.scaladsl.model.headers.Cookie(akka.http.impl.util.Util.<HttpCookie, akka.http.scaladsl.model.headers.HttpCookie>convertArray(cookies)); return new akka.http.scaladsl.model.headers.Cookie(akka.http.impl.util.Util.<HttpCookiePair, akka.http.scaladsl.model.headers.HttpCookiePair>convertArray(cookies));
}
public static Cookie create(String name, String value) {
return create(HttpCookiePair.create(name, value));
} }
} }

View file

@ -10,7 +10,8 @@ import akka.japi.Option;
public abstract class HttpCookie { public abstract class HttpCookie {
public abstract String name(); public abstract String name();
public abstract String content(); public abstract String value();
public abstract HttpCookiePair pair();
public abstract Option<DateTime> getExpires(); public abstract Option<DateTime> getExpires();
public abstract Option<Long> getMaxAge(); public abstract Option<Long> getMaxAge();
@ -20,9 +21,9 @@ public abstract class HttpCookie {
public abstract boolean httpOnly(); public abstract boolean httpOnly();
public abstract Option<String> getExtension(); public abstract Option<String> getExtension();
public static HttpCookie create(String name, String content) { public static HttpCookie create(String name, String value) {
return new akka.http.scaladsl.model.headers.HttpCookie( return new akka.http.scaladsl.model.headers.HttpCookie(
name, content, name, value,
Util.<akka.http.scaladsl.model.DateTime>scalaNone(), Util.scalaNone(), Util.<String>scalaNone(), Util.<String>scalaNone(), Util.<akka.http.scaladsl.model.DateTime>scalaNone(), Util.scalaNone(), Util.<String>scalaNone(), Util.<String>scalaNone(),
false, false, false, false,
Util.<String>scalaNone()); Util.<String>scalaNone());
@ -30,7 +31,7 @@ public abstract class HttpCookie {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static HttpCookie create( public static HttpCookie create(
String name, String name,
String content, String value,
Option<DateTime> expires, Option<DateTime> expires,
Option<Long> maxAge, Option<Long> maxAge,
Option<String> domain, Option<String> domain,
@ -39,7 +40,7 @@ public abstract class HttpCookie {
boolean httpOnly, boolean httpOnly,
Option<String> extension) { Option<String> extension) {
return new akka.http.scaladsl.model.headers.HttpCookie( return new akka.http.scaladsl.model.headers.HttpCookie(
name, content, name, value,
Util.<DateTime, akka.http.scaladsl.model.DateTime>convertOptionToScala(expires), Util.<DateTime, akka.http.scaladsl.model.DateTime>convertOptionToScala(expires),
((Option<Object>) (Option) maxAge).asScala(), ((Option<Object>) (Option) maxAge).asScala(),
domain.asScala(), domain.asScala(),

View file

@ -0,0 +1,24 @@
/*
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.javadsl.model.headers;
/**
* Represents a cookie pair as used in the `Cookie` header as specified in
* http://tools.ietf.org/search/rfc6265#section-4.2.1
*/
public abstract class HttpCookiePair {
public abstract String name();
public abstract String value();
/**
* Converts this cookie pair into an HttpCookie to be used with the
* `Set-Cookie` header.
*/
public abstract HttpCookie toCookie();
public static HttpCookiePair create(String name, String value) {
return new akka.http.scaladsl.model.headers.HttpCookiePair(name, value);
}
}

View file

@ -218,7 +218,7 @@ private[parser] trait CommonRules { this: Parser with StringBuilding ⇒
// https://tools.ietf.org/html/rfc6265#section-4.1.1 // https://tools.ietf.org/html/rfc6265#section-4.1.1
// ****************************************************************************************** // ******************************************************************************************
def `cookie-pair` = rule { def `cookie-pair` = rule {
`cookie-name` ~ ws('=') ~ `cookie-value` ~> (HttpCookie(_, _)) `cookie-name` ~ ws('=') ~ `cookie-value` ~> (HttpCookiePair(_: String, _: String))
} }
def `cookie-name` = rule { token } def `cookie-name` = rule { token }

View file

@ -184,7 +184,7 @@ private[parser] trait SimpleHeaders { this: Parser with CommonRules with CommonA
// https://tools.ietf.org/html/rfc6265 // https://tools.ietf.org/html/rfc6265
def `set-cookie` = rule { def `set-cookie` = rule {
`cookie-pair` ~ zeroOrMore(ws(';') ~ `cookie-av`) ~ EOI ~> (`Set-Cookie`(_)) `cookie-pair` ~> (_.toCookie) ~ zeroOrMore(ws(';') ~ `cookie-av`) ~ EOI ~> (`Set-Cookie`(_))
} }
// http://tools.ietf.org/html/rfc7230#section-6.7 // http://tools.ietf.org/html/rfc7230#section-6.7

View file

@ -53,7 +53,7 @@ private[http] class EnhancedString(val underlying: String) extends AnyVal {
} }
/** /**
* Returns Some(String) if the underlying string is non-emtpy, None otherwise * Returns Some(String) if the underlying string is non-empty, None otherwise
*/ */
def toOption: Option[String] = def toOption: Option[String] =
if ((underlying eq null) || underlying.isEmpty) None else Some(underlying) if ((underlying eq null) || underlying.isEmpty) None else Some(underlying)

View file

@ -132,6 +132,7 @@ object JavaMapping {
implicit object EntityTagRange extends Inherited[jm.headers.EntityTagRange, sm.headers.EntityTagRange] implicit object EntityTagRange extends Inherited[jm.headers.EntityTagRange, sm.headers.EntityTagRange]
implicit object HttpChallenge extends Inherited[jm.headers.HttpChallenge, sm.headers.HttpChallenge] implicit object HttpChallenge extends Inherited[jm.headers.HttpChallenge, sm.headers.HttpChallenge]
implicit object HttpCookie extends Inherited[jm.headers.HttpCookie, sm.headers.HttpCookie] implicit object HttpCookie extends Inherited[jm.headers.HttpCookie, sm.headers.HttpCookie]
implicit object HttpCookiePair extends Inherited[jm.headers.HttpCookiePair, sm.headers.HttpCookiePair]
implicit object HttpCredentials extends Inherited[jm.headers.HttpCredentials, sm.headers.HttpCredentials] implicit object HttpCredentials extends Inherited[jm.headers.HttpCredentials, sm.headers.HttpCredentials]
implicit object HttpEncoding extends Inherited[jm.headers.HttpEncoding, sm.headers.HttpEncoding] implicit object HttpEncoding extends Inherited[jm.headers.HttpEncoding, sm.headers.HttpEncoding]
implicit object HttpEncodingRange extends Inherited[jm.headers.HttpEncodingRange, sm.headers.HttpEncodingRange] implicit object HttpEncodingRange extends Inherited[jm.headers.HttpEncodingRange, sm.headers.HttpEncodingRange]

View file

@ -199,7 +199,7 @@ final case class HttpRequest(method: HttpMethod = HttpMethods.GET,
/** /**
* All cookies provided by the client in one or more `Cookie` headers. * All cookies provided by the client in one or more `Cookie` headers.
*/ */
def cookies: immutable.Seq[HttpCookie] = for (`Cookie`(cookies) headers; cookie cookies) yield cookie def cookies: immutable.Seq[HttpCookiePair] = for (`Cookie`(cookies) headers; cookie cookies) yield cookie
/** /**
* Determines whether the given media-type is accepted by the client. * Determines whether the given media-type is accepted by the client.

View file

@ -11,29 +11,52 @@ import akka.http.impl.util._
import akka.http.javadsl.{ model jm } import akka.http.javadsl.{ model jm }
import akka.http.impl.util.JavaMapping.Implicits._ import akka.http.impl.util.JavaMapping.Implicits._
// see http://tools.ietf.org/html/rfc6265
final case class HttpCookiePair(
name: String,
value: String) extends jm.headers.HttpCookiePair with ToStringRenderable {
HttpCookiePair.validate(name, value)
def render[R <: Rendering](r: R): r.type = r ~~ name ~~ '=' ~~ value
def toCookie: HttpCookie = HttpCookie.fromPair(this)
}
object HttpCookiePair {
def apply(pair: (String, String)): HttpCookiePair = HttpCookiePair(pair._1, pair._2)
private[http] def validate(name: String, value: String): Unit = {
import HttpCookie._
require(nameChars.matchesAll(name), s"'${nameChars.firstMismatch(name).get}' not allowed in cookie name ('$name')")
require(valueChars.matchesAll(value), s"'${valueChars.firstMismatch(value).get}' not allowed in cookie content ('$value')")
}
}
// see http://tools.ietf.org/html/rfc6265 // see http://tools.ietf.org/html/rfc6265
final case class HttpCookie( final case class HttpCookie(
name: String, name: String,
content: String, value: String,
expires: Option[DateTime] = None, expires: Option[DateTime] = None,
maxAge: Option[Long] = None, maxAge: Option[Long] = None,
domain: Option[String] = None, domain: Option[String] = None,
path: Option[String] = None, path: Option[String] = None,
secure: Boolean = false, secure: Boolean = false,
httpOnly: Boolean = false, httpOnly: Boolean = false,
extension: Option[String] = None) extends jm.headers.HttpCookie with ValueRenderable { extension: Option[String] = None) extends jm.headers.HttpCookie with ToStringRenderable {
/** Returns the name/value pair for this cookie, to be used in [[Cookiie]] headers. */
def pair: HttpCookiePair = HttpCookiePair(name, value)
// TODO: suppress running these requires for cookies created from our header parser
import HttpCookie._ import HttpCookie._
// TODO: suppress running these requires for cookies created from our header parser HttpCookiePair.validate(name, value)
require(nameChars.matchesAll(name), s"'${nameChars.firstMismatch(name).get}' not allowed in cookie name ('$name')")
require(contentChars.matchesAll(content), s"'${contentChars.firstMismatch(content).get}' not allowed in cookie content ('$content')")
require(domain.forall(domainChars.matchesAll), s"'${domainChars.firstMismatch(domain.get).get}' not allowed in cookie domain ('${domain.get}')") require(domain.forall(domainChars.matchesAll), s"'${domainChars.firstMismatch(domain.get).get}' not allowed in cookie domain ('${domain.get}')")
require(path.forall(pathOrExtChars.matchesAll), s"'${pathOrExtChars.firstMismatch(path.get).get}' not allowed in cookie path ('${path.get}')") require(path.forall(pathOrExtChars.matchesAll), s"'${pathOrExtChars.firstMismatch(path.get).get}' not allowed in cookie path ('${path.get}')")
require(extension.forall(pathOrExtChars.matchesAll), s"'${pathOrExtChars.firstMismatch(extension.get).get}' not allowed in cookie extension ('${extension.get}')") require(extension.forall(pathOrExtChars.matchesAll), s"'${pathOrExtChars.firstMismatch(extension.get).get}' not allowed in cookie extension ('${extension.get}')")
def render[R <: Rendering](r: R): r.type = { def render[R <: Rendering](r: R): r.type = {
r ~~ name ~~ '=' ~~ content r ~~ name ~~ '=' ~~ value
if (expires.isDefined) expires.get.renderRfc1123DateTimeString(r ~~ "; Expires=") if (expires.isDefined) expires.get.renderRfc1123DateTimeString(r ~~ "; Expires=")
if (maxAge.isDefined) r ~~ "; Max-Age=" ~~ maxAge.get if (maxAge.isDefined) r ~~ "; Max-Age=" ~~ maxAge.get
if (domain.isDefined) r ~~ "; Domain=" ~~ domain.get if (domain.isDefined) r ~~ "; Domain=" ~~ domain.get
@ -57,12 +80,22 @@ final case class HttpCookie(
} }
object HttpCookie { object HttpCookie {
def fromPair(pair: HttpCookiePair,
expires: Option[DateTime] = None,
maxAge: Option[Long] = None,
domain: Option[String] = None,
path: Option[String] = None,
secure: Boolean = false,
httpOnly: Boolean = false,
extension: Option[String] = None): HttpCookie =
HttpCookie(pair.name, pair.value, expires, maxAge, domain, path, secure, httpOnly, extension)
import akka.http.impl.model.parser.CharacterClasses._ import akka.http.impl.model.parser.CharacterClasses._
def nameChars = tchar private[http] def nameChars = tchar
// http://tools.ietf.org/html/rfc6265#section-4.1.1 // http://tools.ietf.org/html/rfc6265#section-4.1.1
// ; US-ASCII characters excluding CTLs, whitespace DQUOTE, comma, semicolon, and backslash // ; US-ASCII characters excluding CTLs, whitespace DQUOTE, comma, semicolon, and backslash
val contentChars = CharPredicate('\u0021', '\u0023' to '\u002B', '\u002D' to '\u003A', '\u003C' to '\u005B', '\u005D' to '\u007E') private[http] val valueChars = CharPredicate('\u0021', '\u0023' to '\u002B', '\u002D' to '\u003A', '\u003C' to '\u005B', '\u005D' to '\u007E')
val domainChars = ALPHANUM ++ ".-" private[http] val domainChars = ALPHANUM ++ ".-"
val pathOrExtChars = VCHAR ++ ' ' -- ';' private[http] val pathOrExtChars = VCHAR ++ ' ' -- ';'
} }

View file

@ -384,20 +384,19 @@ final case class `Content-Type` private[http] (contentType: ContentType) extends
// https://tools.ietf.org/html/rfc6265#section-4.2 // https://tools.ietf.org/html/rfc6265#section-4.2
object Cookie extends ModeledCompanion { object Cookie extends ModeledCompanion {
implicit val cookieNameValueOnlyRenderer: Renderer[HttpCookie] = new Renderer[HttpCookie] { def apply(first: HttpCookiePair, more: HttpCookiePair*): Cookie = apply(immutable.Seq(first +: more: _*))
def render[R <: Rendering](r: R, c: HttpCookie): r.type = r ~~ c.name ~~ '=' ~~ c.content def apply(name: String, value: String): Cookie = apply(HttpCookiePair(name, value))
} def apply(values: (String, String)*): Cookie = apply(values.map(HttpCookiePair(_)).toList)
def apply(first: HttpCookie, more: HttpCookie*): Cookie = apply(immutable.Seq(first +: more: _*)) implicit val cookiePairsRenderer = Renderer.seqRenderer[HttpCookiePair](separator = "; ") // cache
implicit val cookiesRenderer = Renderer.seqRenderer[HttpCookie](separator = "; ") // cache
} }
final case class Cookie(cookies: immutable.Seq[HttpCookie]) extends jm.headers.Cookie with ModeledHeader { final case class Cookie(cookies: immutable.Seq[HttpCookiePair]) extends jm.headers.Cookie with ModeledHeader {
require(cookies.nonEmpty, "cookies must not be empty") require(cookies.nonEmpty, "cookies must not be empty")
import Cookie.cookiesRenderer import Cookie.cookiePairsRenderer
def renderValue[R <: Rendering](r: R): r.type = r ~~ cookies def renderValue[R <: Rendering](r: R): r.type = r ~~ cookies
protected def companion = Cookie protected def companion = Cookie
/** Java API */ /** Java API */
def getCookies: Iterable[jm.headers.HttpCookie] = cookies.asJava def getCookies: Iterable[jm.headers.HttpCookiePair] = cookies.asJava
} }
// http://tools.ietf.org/html/rfc7231#section-7.1.1.2 // http://tools.ietf.org/html/rfc7231#section-7.1.1.2

View file

@ -217,19 +217,12 @@ class HttpHeaderSpec extends FreeSpec with Matchers {
} }
"Cookie" in { "Cookie" in {
"Cookie: SID=31d4d96e407aad42" =!= Cookie(HttpCookie("SID", "31d4d96e407aad42")) "Cookie: SID=31d4d96e407aad42" =!= Cookie("SID" -> "31d4d96e407aad42")
"Cookie: SID=31d4d96e407aad42; lang=en>US" =!= Cookie(HttpCookie("SID", "31d4d96e407aad42"), HttpCookie("lang", "en>US")) "Cookie: SID=31d4d96e407aad42; lang=en>US" =!= Cookie("SID" -> "31d4d96e407aad42", "lang" -> "en>US")
"Cookie: a=1;b=2" =!= Cookie(HttpCookie("a", "1"), HttpCookie("b", "2")).renderedTo("a=1; b=2") "Cookie: a=1;b=2" =!= Cookie("a" -> "1", "b" -> "2").renderedTo("a=1; b=2")
"Cookie: a=1 ;b=2" =!= Cookie(HttpCookie("a", "1"), HttpCookie("b", "2")).renderedTo("a=1; b=2") "Cookie: a=1 ;b=2" =!= Cookie("a" -> "1", "b" -> "2").renderedTo("a=1; b=2")
"Cookie: a=1; b=2" =!= Cookie(HttpCookie("a", "1"), HttpCookie("b", "2")) "Cookie: a=1; b=2" =!= Cookie("a" -> "1", "b" -> "2")
"Cookie: a=1,b=2" =!= Cookie(HttpCookie("a", "1"), HttpCookie("b", "2")).renderedTo("a=1; b=2") "Cookie: a=1,b=2" =!= Cookie("a" -> "1", "b" -> "2").renderedTo("a=1; b=2")
Cookie(HttpCookie("SID", "31d4d96e407aad42",
domain = Some("example.com"),
expires = Some(DateTime(2021, 6, 9, 10, 18, 14)),
path = Some("/hello"),
httpOnly = true,
extension = Some("fancyPants"),
secure = true)).toString shouldEqual "Cookie: SID=31d4d96e407aad42"
} }
"Date" in { "Date" in {

View file

@ -4,6 +4,8 @@
package akka.http.javadsl.model package akka.http.javadsl.model
import akka.http.javadsl.model.headers.Cookie
import scala.collection.immutable import scala.collection.immutable
import org.scalatest.{ MustMatchers, FreeSpec } import org.scalatest.{ MustMatchers, FreeSpec }
import akka.http.scaladsl.model.headers.BasicHttpCredentials import akka.http.scaladsl.model.headers.BasicHttpCredentials
@ -42,7 +44,7 @@ class JavaApiTestCaseSpecs extends FreeSpec with MustMatchers {
model.HttpRequest(headers = immutable.Seq(model.headers.Authorization(BasicHttpCredentials("username", "password"))))) model.HttpRequest(headers = immutable.Seq(model.headers.Authorization(BasicHttpCredentials("username", "password")))))
} }
"removeCookies" in { "removeCookies" in {
val testRequest = model.HttpRequest(headers = immutable.Seq(model.headers.Cookie(model.headers.HttpCookie("test", "blub")))) val testRequest = model.HttpRequest(headers = immutable.Seq(Cookie.create("test", "blub")))
JavaApiTestCases.removeCookies(testRequest) must be( JavaApiTestCases.removeCookies(testRequest) must be(
model.HttpRequest()) model.HttpRequest())
} }

View file

@ -15,7 +15,7 @@ class CookieDirectivesSpec extends RoutingSpec {
"The 'cookie' directive" should { "The 'cookie' directive" should {
"extract the respectively named cookie" in { "extract the respectively named cookie" in {
Get() ~> addHeader(Cookie(HttpCookie("fancy", "pants"))) ~> { Get() ~> addHeader(Cookie("fancy" -> "pants")) ~> {
cookie("fancy") { echoComplete } cookie("fancy") { echoComplete }
} ~> check { responseAs[String] shouldEqual "fancy=pants" } } ~> check { responseAs[String] shouldEqual "fancy=pants" }
} }
@ -25,8 +25,8 @@ class CookieDirectivesSpec extends RoutingSpec {
} ~> check { rejection shouldEqual MissingCookieRejection("fancy") } } ~> check { rejection shouldEqual MissingCookieRejection("fancy") }
} }
"properly pass through inner rejections" in { "properly pass through inner rejections" in {
Get() ~> addHeader(Cookie(HttpCookie("fancy", "pants"))) ~> { Get() ~> addHeader(Cookie("fancy" -> "pants")) ~> {
cookie("fancy") { c reject(ValidationRejection("Dont like " + c.content)) } cookie("fancy") { c reject(ValidationRejection("Dont like " + c.value)) }
} ~> check { rejection shouldEqual ValidationRejection("Dont like pants") } } ~> check { rejection shouldEqual ValidationRejection("Dont like pants") }
} }
} }
@ -56,7 +56,7 @@ class CookieDirectivesSpec extends RoutingSpec {
"The 'optionalCookie' directive" should { "The 'optionalCookie' directive" should {
"produce a `Some(cookie)` extraction if the cookie is present" in { "produce a `Some(cookie)` extraction if the cookie is present" in {
Get() ~> Cookie(HttpCookie("abc", "123")) ~> { Get() ~> Cookie("abc" -> "123") ~> {
optionalCookie("abc") { echoComplete } optionalCookie("abc") { echoComplete }
} ~> check { responseAs[String] shouldEqual "Some(abc=123)" } } ~> check { responseAs[String] shouldEqual "Some(abc=123)" }
} }

View file

@ -18,17 +18,17 @@ trait CookieDirectives {
* Extracts an HttpCookie with the given name. If the cookie is not present the * Extracts an HttpCookie with the given name. If the cookie is not present the
* request is rejected with a respective [[MissingCookieRejection]]. * request is rejected with a respective [[MissingCookieRejection]].
*/ */
def cookie(name: String): Directive1[HttpCookie] = def cookie(name: String): Directive1[HttpCookiePair] =
headerValue(findCookie(name)) | reject(MissingCookieRejection(name)) headerValue(findCookie(name)) | reject(MissingCookieRejection(name))
/** /**
* Extracts an HttpCookie with the given name. * Extracts an HttpCookie with the given name.
* If the cookie is not present a value of `None` is extracted. * If the cookie is not present a value of `None` is extracted.
*/ */
def optionalCookie(name: String): Directive1[Option[HttpCookie]] = def optionalCookie(name: String): Directive1[Option[HttpCookiePair]] =
optionalHeaderValue(findCookie(name)) optionalHeaderValue(findCookie(name))
private def findCookie(name: String): HttpHeader Option[HttpCookie] = { private def findCookie(name: String): HttpHeader Option[HttpCookiePair] = {
case Cookie(cookies) cookies.find(_.name == name) case Cookie(cookies) cookies.find(_.name == name)
case _ None case _ None
} }
@ -44,7 +44,7 @@ trait CookieDirectives {
*/ */
def deleteCookie(first: HttpCookie, more: HttpCookie*): Directive0 = def deleteCookie(first: HttpCookie, more: HttpCookie*): Directive0 =
respondWithHeaders((first :: more.toList).map { c respondWithHeaders((first :: more.toList).map { c
`Set-Cookie`(c.copy(content = "deleted", expires = Some(DateTime.MinValue))) `Set-Cookie`(c.copy(value = "deleted", expires = Some(DateTime.MinValue)))
}) })
/** /**