diff --git a/akka-http-core/src/main/scala/akka/http/model/headers/UpgradeProtocol.scala b/akka-http-core/src/main/scala/akka/http/model/headers/UpgradeProtocol.scala new file mode 100644 index 0000000000..f506c1bcda --- /dev/null +++ b/akka-http-core/src/main/scala/akka/http/model/headers/UpgradeProtocol.scala @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2009-2015 Typesafe Inc. + */ + +package akka.http.model.headers + +import akka.http.util.{ Rendering, ValueRenderable } + +final case class UpgradeProtocol(name: String, version: Option[String] = None) extends ValueRenderable { + def render[R <: Rendering](r: R): r.type = { + r ~~ name + version.foreach(v ⇒ r ~~ '/' ~~ v) + r + } +} diff --git a/akka-http-core/src/main/scala/akka/http/model/headers/headers.scala b/akka-http-core/src/main/scala/akka/http/model/headers/headers.scala index 0f0e29eade..55d928b5c4 100644 --- a/akka-http-core/src/main/scala/akka/http/model/headers/headers.scala +++ b/akka-http-core/src/main/scala/akka/http/model/headers/headers.scala @@ -55,6 +55,7 @@ final case class Connection(tokens: immutable.Seq[String]) extends ModeledHeader def renderValue[R <: Rendering](r: R): r.type = r ~~ tokens def hasClose = has("close") def hasKeepAlive = has("keep-alive") + def hasUpgrade = has("upgrade") def append(tokens: immutable.Seq[String]) = Connection(this.tokens ++ tokens) @tailrec private def has(item: String, ix: Int = 0): Boolean = if (ix < tokens.length) @@ -711,6 +712,19 @@ final case class `Transfer-Encoding`(encodings: immutable.Seq[TransferEncoding]) def getEncodings: Iterable[japi.TransferEncoding] = encodings.asJava } +// http://tools.ietf.org/html/rfc7230#section-6.7 +object Upgrade extends ModeledCompanion { + implicit val protocolsRenderer = Renderer.defaultSeqRenderer[UpgradeProtocol] +} +final case class Upgrade(protocols: immutable.Seq[UpgradeProtocol]) extends ModeledHeader { + import Upgrade.protocolsRenderer + protected[http] def renderValue[R <: Rendering](r: R): r.type = r ~~ protocols + + protected def companion: ModeledCompanion = Upgrade + + def hasWebsocket: Boolean = protocols.exists(_.name equalsIgnoreCase "websocket") +} + // http://tools.ietf.org/html/rfc7231#section-5.5.3 object `User-Agent` extends ModeledCompanion { def apply(products: String): `User-Agent` = apply(ProductVersion.parseMultiple(products)) diff --git a/akka-http-core/src/main/scala/akka/http/model/parser/HeaderParser.scala b/akka-http-core/src/main/scala/akka/http/model/parser/HeaderParser.scala index f7d8407cec..4c87d634c8 100644 --- a/akka-http-core/src/main/scala/akka/http/model/parser/HeaderParser.scala +++ b/akka-http-core/src/main/scala/akka/http/model/parser/HeaderParser.scala @@ -119,6 +119,7 @@ private[http] object HeaderParser { "sec-websocket-version", "set-cookie", "transfer-encoding", + "upgrade", "user-agent", "www-authenticate", "x-forwarded-for") diff --git a/akka-http-core/src/main/scala/akka/http/model/parser/SimpleHeaders.scala b/akka-http-core/src/main/scala/akka/http/model/parser/SimpleHeaders.scala index f7af9acefe..0a0142dc46 100644 --- a/akka-http-core/src/main/scala/akka/http/model/parser/SimpleHeaders.scala +++ b/akka-http-core/src/main/scala/akka/http/model/parser/SimpleHeaders.scala @@ -187,6 +187,15 @@ private[parser] trait SimpleHeaders { this: Parser with CommonRules with CommonA `cookie-pair` ~ zeroOrMore(ws(';') ~ `cookie-av`) ~ EOI ~> (`Set-Cookie`(_)) } + // http://tools.ietf.org/html/rfc7230#section-6.7 + def upgrade = rule { + oneOrMore(protocol).separatedBy(listSep) ~> (Upgrade(_)) + } + + def protocol = rule { + token ~ optional(ws("/") ~ token) ~> (UpgradeProtocol(_, _)) + } + // http://tools.ietf.org/html/rfc7231#section-5.5.3 def `user-agent` = rule { products ~> (`User-Agent`(_)) } diff --git a/akka-http-core/src/test/scala/akka/http/model/parser/HttpHeaderSpec.scala b/akka-http-core/src/test/scala/akka/http/model/parser/HttpHeaderSpec.scala index 399a4e8c20..3406f51953 100644 --- a/akka-http-core/src/test/scala/akka/http/model/parser/HttpHeaderSpec.scala +++ b/akka-http-core/src/test/scala/akka/http/model/parser/HttpHeaderSpec.scala @@ -483,6 +483,13 @@ class HttpHeaderSpec extends FreeSpec with Matchers { .renderedTo("PLAY_FLASH=; Expires=Sun, 07 Dec 2014 22:48:47 GMT; Path=/; HttpOnly") } + "Upgrade" in { + "Upgrade: abc, def" =!= Upgrade(Vector(UpgradeProtocol("abc"), UpgradeProtocol("def"))) + "Upgrade: abc, def/38.1" =!= Upgrade(Vector(UpgradeProtocol("abc"), UpgradeProtocol("def", Some("38.1")))) + + "Upgrade: websocket" =!= Upgrade(Vector(UpgradeProtocol("websocket"))) + } + "User-Agent" in { "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_3) AppleWebKit/537.31" =!= `User-Agent`(ProductVersion("Mozilla", "5.0", "Macintosh; Intel Mac OS X 10_8_3"), ProductVersion("AppleWebKit", "537.31"))